##// END OF EJS Templates
Fixed: auto links ending with right angle bracket shouldn't include the bracket in the URL. #5652...
Jean-Baptiste Barth -
r4173:d642964035e6
parent child
Show More
@@ -1,163 +1,163
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redcloth3'
19 19
20 20 module Redmine
21 21 module WikiFormatting
22 22 module Textile
23 23 class Formatter < RedCloth3
24 24 include ActionView::Helpers::TagHelper
25 25
26 26 # auto_link rule after textile rules so that it doesn't break !image_url! tags
27 27 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc]
28 28
29 29 def initialize(*args)
30 30 super
31 31 self.hard_breaks=true
32 32 self.no_span_caps=true
33 33 self.filter_styles=true
34 34 end
35 35
36 36 def to_html(*rules)
37 37 @toc = []
38 38 super(*RULES).to_s
39 39 end
40 40
41 41 private
42 42
43 43 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
44 44 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
45 45 def hard_break( text )
46 46 text.gsub!( /(.)\n(?!\n|\Z|>| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
47 47 end
48 48
49 49 # Patch to add code highlighting support to RedCloth
50 50 def smooth_offtags( text )
51 51 unless @pre_list.empty?
52 52 ## replace <pre> content
53 53 text.gsub!(/<redpre#(\d+)>/) do
54 54 content = @pre_list[$1.to_i]
55 55 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
56 56 content = "<code class=\"#{$1} syntaxhl\">" +
57 57 Redmine::SyntaxHighlighting.highlight_by_language($2, $1)
58 58 end
59 59 content
60 60 end
61 61 end
62 62 end
63 63
64 64 # Patch to add 'table of content' support to RedCloth
65 65 def textile_p_withtoc(tag, atts, cite, content)
66 66 # removes wiki links from the item
67 67 toc_item = content.gsub(/(\[\[([^\]\|]*)(\|([^\]]*))?\]\])/) { $4 || $2 }
68 68 # sanitizes titles from links
69 69 # see redcloth3.rb, same as "#{pre}#{text}#{post}"
70 70 toc_item.gsub!(LINK_RE) { [$2, $4, $9].join }
71 71 # sanitizes image links from titles
72 72 toc_item.gsub!(IMAGE_RE) { [$5].join }
73 73 # removes styles
74 74 # eg. %{color:red}Triggers% => Triggers
75 75 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
76 76
77 77 # replaces non word caracters by dashes
78 78 anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
79 79
80 80 unless anchor.blank?
81 81 if tag =~ /^h(\d)$/
82 82 @toc << [$1.to_i, anchor, toc_item]
83 83 end
84 84 atts << " id=\"#{anchor}\""
85 85 content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a>"
86 86 end
87 87 textile_p(tag, atts, cite, content)
88 88 end
89 89
90 90 alias :textile_h1 :textile_p_withtoc
91 91 alias :textile_h2 :textile_p_withtoc
92 92 alias :textile_h3 :textile_p_withtoc
93 93
94 94 def inline_toc(text)
95 95 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
96 96 div_class = 'toc'
97 97 div_class << ' right' if $1 == '>'
98 98 div_class << ' left' if $1 == '<'
99 99 out = "<ul class=\"#{div_class}\">"
100 100 @toc.each do |heading|
101 101 level, anchor, toc_item = heading
102 102 out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n"
103 103 end
104 104 out << '</ul>'
105 105 out
106 106 end
107 107 end
108 108
109 109 AUTO_LINK_RE = %r{
110 110 ( # leading text
111 111 <\w+.*?>| # leading HTML tag, or
112 112 [^=<>!:'"/]| # leading punctuation, or
113 113 ^ # beginning of line
114 114 )
115 115 (
116 116 (?:https?://)| # protocol spec, or
117 117 (?:s?ftps?://)|
118 118 (?:www\.) # www.*
119 119 )
120 120 (
121 121 (\S+?) # url
122 122 (\/)? # slash
123 123 )
124 ([^\w\=\/;\(\)]*?) # post
124 ((?:&gt;)?|[^\w\=\/;\(\)]*?) # post
125 125 (?=<|\s|$)
126 126 }x unless const_defined?(:AUTO_LINK_RE)
127 127
128 128 # Turns all urls into clickable links (code from Rails).
129 129 def inline_auto_link(text)
130 130 text.gsub!(AUTO_LINK_RE) do
131 131 all, leading, proto, url, post = $&, $1, $2, $3, $6
132 132 if leading =~ /<a\s/i || leading =~ /![<>=]?/
133 133 # don't replace URL's that are already linked
134 134 # and URL's prefixed with ! !> !< != (textile images)
135 135 all
136 136 else
137 137 # Idea below : an URL with unbalanced parethesis and
138 138 # ending by ')' is put into external parenthesis
139 139 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
140 140 url=url[0..-2] # discard closing parenth from url
141 141 post = ")"+post # add closing parenth to post
142 142 end
143 143 tag = content_tag('a', proto + url, :href => "#{proto=="www."?"http://www.":proto}#{url}", :class => 'external')
144 144 %(#{leading}#{tag}#{post})
145 145 end
146 146 end
147 147 end
148 148
149 149 # Turns all email addresses into clickable links (code from Rails).
150 150 def inline_auto_mailto(text)
151 151 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
152 152 mail = $1
153 153 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
154 154 mail
155 155 else
156 156 content_tag('a', mail, :href => "mailto:#{mail}", :class => "email")
157 157 end
158 158 end
159 159 end
160 160 end
161 161 end
162 162 end
163 163 end
@@ -1,629 +1,631
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../../test_helper'
19 19
20 20 class ApplicationHelperTest < ActionView::TestCase
21 21
22 22 fixtures :projects, :roles, :enabled_modules, :users,
23 23 :repositories, :changesets,
24 24 :trackers, :issue_statuses, :issues, :versions, :documents,
25 25 :wikis, :wiki_pages, :wiki_contents,
26 26 :boards, :messages,
27 27 :attachments,
28 28 :enumerations
29 29
30 30 def setup
31 31 super
32 32 end
33 33
34 34 context "#link_to_if_authorized" do
35 35 context "authorized user" do
36 36 should "be tested"
37 37 end
38 38
39 39 context "unauthorized user" do
40 40 should "be tested"
41 41 end
42 42
43 43 should "allow using the :controller and :action for the target link" do
44 44 User.current = User.find_by_login('admin')
45 45
46 46 @project = Issue.first.project # Used by helper
47 47 response = link_to_if_authorized("By controller/action",
48 48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 49 assert_match /href/, response
50 50 end
51 51
52 52 end
53 53
54 54 def test_auto_links
55 55 to_test = {
56 56 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
57 57 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
58 58 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
59 59 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
60 60 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
61 61 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
62 62 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
63 63 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
64 64 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
65 65 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
66 66 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
67 67 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
68 68 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
69 69 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
70 70 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
71 71 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
72 72 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
73 73 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
74 74 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
75 75 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
76 76 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
77 77 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
78 78 # two exclamation marks
79 79 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
80 80 # escaping
81 81 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
82 # wrap in angle brackets
83 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
82 84 }
83 85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
84 86 end
85 87
86 88 def test_auto_mailto
87 89 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
88 90 textilizable('test@foo.bar')
89 91 end
90 92
91 93 def test_inline_images
92 94 to_test = {
93 95 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
94 96 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
95 97 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
96 98 # inline styles should be stripped
97 99 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
98 100 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
99 101 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
100 102 }
101 103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
102 104 end
103 105
104 106 def test_inline_images_inside_tags
105 107 raw = <<-RAW
106 108 h1. !foo.png! Heading
107 109
108 110 Centered image:
109 111
110 112 p=. !bar.gif!
111 113 RAW
112 114
113 115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
114 116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
115 117 end
116 118
117 119 def test_acronyms
118 120 to_test = {
119 121 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
120 122 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
121 123 }
122 124 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
123 125
124 126 end
125 127
126 128 def test_attached_images
127 129 to_test = {
128 130 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
129 131 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
130 132 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
131 133 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
132 134 # link image
133 135 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
134 136 }
135 137 attachments = Attachment.find(:all)
136 138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
137 139 end
138 140
139 141 def test_textile_external_links
140 142 to_test = {
141 143 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
142 144 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
143 145 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
144 146 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
145 147 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
146 148 # no multiline link text
147 149 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
148 150 # mailto link
149 151 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
150 152 # two exclamation marks
151 153 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
152 154 # escaping
153 155 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
154 156 }
155 157 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
156 158 end
157 159
158 160 def test_redmine_links
159 161 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
160 162 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
161 163
162 164 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
163 165 :class => 'changeset', :title => 'My very first commit')
164 166 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
165 167 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
166 168
167 169 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
168 170 :class => 'document')
169 171
170 172 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
171 173 :class => 'version')
172 174
173 175 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
174 176
175 177 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
176 178
177 179 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
178 180 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
179 181
180 182 to_test = {
181 183 # tickets
182 184 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
183 185 # changesets
184 186 'r1' => changeset_link,
185 187 'r1.' => "#{changeset_link}.",
186 188 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
187 189 'r1,r2' => "#{changeset_link},#{changeset_link2}",
188 190 # documents
189 191 'document#1' => document_link,
190 192 'document:"Test document"' => document_link,
191 193 # versions
192 194 'version#2' => version_link,
193 195 'version:1.0' => version_link,
194 196 'version:"1.0"' => version_link,
195 197 # source
196 198 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
197 199 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
198 200 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
199 201 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
200 202 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
201 203 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
202 204 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
203 205 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
204 206 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
205 207 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
206 208 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
207 209 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
208 210 # message
209 211 'message#4' => link_to('Post 2', message_url, :class => 'message'),
210 212 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
211 213 # project
212 214 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
213 215 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
214 216 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
215 217 # escaping
216 218 '!#3.' => '#3.',
217 219 '!r1' => 'r1',
218 220 '!document#1' => 'document#1',
219 221 '!document:"Test document"' => 'document:"Test document"',
220 222 '!version#2' => 'version#2',
221 223 '!version:1.0' => 'version:1.0',
222 224 '!version:"1.0"' => 'version:"1.0"',
223 225 '!source:/some/file' => 'source:/some/file',
224 226 # not found
225 227 '#0123456789' => '#0123456789',
226 228 # invalid expressions
227 229 'source:' => 'source:',
228 230 # url hash
229 231 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
230 232 }
231 233 @project = Project.find(1)
232 234 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
233 235 end
234 236
235 237 def test_attachment_links
236 238 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
237 239 to_test = {
238 240 'attachment:error281.txt' => attachment_link
239 241 }
240 242 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
241 243 end
242 244
243 245 def test_wiki_links
244 246 to_test = {
245 247 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
246 248 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
247 249 # link with anchor
248 250 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
249 251 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
250 252 # page that doesn't exist
251 253 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
252 254 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
253 255 # link to another project wiki
254 256 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
255 257 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
256 258 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
257 259 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
258 260 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
259 261 # striked through link
260 262 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
261 263 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
262 264 # escaping
263 265 '![[Another page|Page]]' => '[[Another page|Page]]',
264 266 # project does not exist
265 267 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
266 268 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
267 269 }
268 270 @project = Project.find(1)
269 271 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
270 272 end
271 273
272 274 def test_html_tags
273 275 to_test = {
274 276 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
275 277 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
276 278 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
277 279 # do not escape pre/code tags
278 280 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
279 281 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
280 282 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
281 283 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
282 284 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
283 285 # remove attributes except class
284 286 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
285 287 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
286 288 }
287 289 to_test.each { |text, result| assert_equal result, textilizable(text) }
288 290 end
289 291
290 292 def test_allowed_html_tags
291 293 to_test = {
292 294 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
293 295 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
294 296 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
295 297 }
296 298 to_test.each { |text, result| assert_equal result, textilizable(text) }
297 299 end
298 300
299 301 def test_pre_tags
300 302 raw = <<-RAW
301 303 Before
302 304
303 305 <pre>
304 306 <prepared-statement-cache-size>32</prepared-statement-cache-size>
305 307 </pre>
306 308
307 309 After
308 310 RAW
309 311
310 312 expected = <<-EXPECTED
311 313 <p>Before</p>
312 314 <pre>
313 315 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
314 316 </pre>
315 317 <p>After</p>
316 318 EXPECTED
317 319
318 320 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
319 321 end
320 322
321 323 def test_pre_content_should_not_parse_wiki_and_redmine_links
322 324 raw = <<-RAW
323 325 [[CookBook documentation]]
324 326
325 327 #1
326 328
327 329 <pre>
328 330 [[CookBook documentation]]
329 331
330 332 #1
331 333 </pre>
332 334 RAW
333 335
334 336 expected = <<-EXPECTED
335 337 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
336 338 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
337 339 <pre>
338 340 [[CookBook documentation]]
339 341
340 342 #1
341 343 </pre>
342 344 EXPECTED
343 345
344 346 @project = Project.find(1)
345 347 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
346 348 end
347 349
348 350 def test_non_closing_pre_blocks_should_be_closed
349 351 raw = <<-RAW
350 352 <pre><code>
351 353 RAW
352 354
353 355 expected = <<-EXPECTED
354 356 <pre><code>
355 357 </code></pre>
356 358 EXPECTED
357 359
358 360 @project = Project.find(1)
359 361 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
360 362 end
361 363
362 364 def test_syntax_highlight
363 365 raw = <<-RAW
364 366 <pre><code class="ruby">
365 367 # Some ruby code here
366 368 </code></pre>
367 369 RAW
368 370
369 371 expected = <<-EXPECTED
370 372 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
371 373 </code></pre>
372 374 EXPECTED
373 375
374 376 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
375 377 end
376 378
377 379 def test_wiki_links_in_tables
378 380 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
379 381 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
380 382 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
381 383 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
382 384 }
383 385 @project = Project.find(1)
384 386 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
385 387 end
386 388
387 389 def test_text_formatting
388 390 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
389 391 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
390 392 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
391 393 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
392 394 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
393 395 }
394 396 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
395 397 end
396 398
397 399 def test_wiki_horizontal_rule
398 400 assert_equal '<hr />', textilizable('---')
399 401 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
400 402 end
401 403
402 404 def test_acronym
403 405 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
404 406 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
405 407 end
406 408
407 409 def test_footnotes
408 410 raw = <<-RAW
409 411 This is some text[1].
410 412
411 413 fn1. This is the foot note
412 414 RAW
413 415
414 416 expected = <<-EXPECTED
415 417 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
416 418 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
417 419 EXPECTED
418 420
419 421 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
420 422 end
421 423
422 424 def test_table_of_content
423 425 raw = <<-RAW
424 426 {{toc}}
425 427
426 428 h1. Title
427 429
428 430 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
429 431
430 432 h2. Subtitle with a [[Wiki]] link
431 433
432 434 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
433 435
434 436 h2. Subtitle with [[Wiki|another Wiki]] link
435 437
436 438 h2. Subtitle with %{color:red}red text%
437 439
438 440 h1. Another title
439 441
440 442 h2. An "Internet link":http://www.redmine.org/ inside subtitle
441 443
442 444 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
443 445
444 446 RAW
445 447
446 448 expected = '<ul class="toc">' +
447 449 '<li class="heading1"><a href="#Title">Title</a></li>' +
448 450 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
449 451 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
450 452 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
451 453 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
452 454 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
453 455 '<li class="heading2"><a href="#Project-Name">Project Name</a></li>' +
454 456 '</ul>'
455 457
456 458 assert textilizable(raw).gsub("\n", "").include?(expected)
457 459 end
458 460
459 461 def test_blockquote
460 462 # orig raw text
461 463 raw = <<-RAW
462 464 John said:
463 465 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
464 466 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
465 467 > * Donec odio lorem,
466 468 > * sagittis ac,
467 469 > * malesuada in,
468 470 > * adipiscing eu, dolor.
469 471 >
470 472 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
471 473 > Proin a tellus. Nam vel neque.
472 474
473 475 He's right.
474 476 RAW
475 477
476 478 # expected html
477 479 expected = <<-EXPECTED
478 480 <p>John said:</p>
479 481 <blockquote>
480 482 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
481 483 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
482 484 <ul>
483 485 <li>Donec odio lorem,</li>
484 486 <li>sagittis ac,</li>
485 487 <li>malesuada in,</li>
486 488 <li>adipiscing eu, dolor.</li>
487 489 </ul>
488 490 <blockquote>
489 491 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
490 492 </blockquote>
491 493 <p>Proin a tellus. Nam vel neque.</p>
492 494 </blockquote>
493 495 <p>He's right.</p>
494 496 EXPECTED
495 497
496 498 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
497 499 end
498 500
499 501 def test_table
500 502 raw = <<-RAW
501 503 This is a table with empty cells:
502 504
503 505 |cell11|cell12||
504 506 |cell21||cell23|
505 507 |cell31|cell32|cell33|
506 508 RAW
507 509
508 510 expected = <<-EXPECTED
509 511 <p>This is a table with empty cells:</p>
510 512
511 513 <table>
512 514 <tr><td>cell11</td><td>cell12</td><td></td></tr>
513 515 <tr><td>cell21</td><td></td><td>cell23</td></tr>
514 516 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
515 517 </table>
516 518 EXPECTED
517 519
518 520 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
519 521 end
520 522
521 523 def test_table_with_line_breaks
522 524 raw = <<-RAW
523 525 This is a table with line breaks:
524 526
525 527 |cell11
526 528 continued|cell12||
527 529 |-cell21-||cell23
528 530 cell23 line2
529 531 cell23 *line3*|
530 532 |cell31|cell32
531 533 cell32 line2|cell33|
532 534
533 535 RAW
534 536
535 537 expected = <<-EXPECTED
536 538 <p>This is a table with line breaks:</p>
537 539
538 540 <table>
539 541 <tr>
540 542 <td>cell11<br />continued</td>
541 543 <td>cell12</td>
542 544 <td></td>
543 545 </tr>
544 546 <tr>
545 547 <td><del>cell21</del></td>
546 548 <td></td>
547 549 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
548 550 </tr>
549 551 <tr>
550 552 <td>cell31</td>
551 553 <td>cell32<br/>cell32 line2</td>
552 554 <td>cell33</td>
553 555 </tr>
554 556 </table>
555 557 EXPECTED
556 558
557 559 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
558 560 end
559 561
560 562 def test_textile_should_not_mangle_brackets
561 563 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
562 564 end
563 565
564 566 def test_default_formatter
565 567 Setting.text_formatting = 'unknown'
566 568 text = 'a *link*: http://www.example.net/'
567 569 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
568 570 Setting.text_formatting = 'textile'
569 571 end
570 572
571 573 def test_due_date_distance_in_words
572 574 to_test = { Date.today => 'Due in 0 days',
573 575 Date.today + 1 => 'Due in 1 day',
574 576 Date.today + 100 => 'Due in about 3 months',
575 577 Date.today + 20000 => 'Due in over 54 years',
576 578 Date.today - 1 => '1 day late',
577 579 Date.today - 100 => 'about 3 months late',
578 580 Date.today - 20000 => 'over 54 years late',
579 581 }
580 582 to_test.each do |date, expected|
581 583 assert_equal expected, due_date_distance_in_words(date)
582 584 end
583 585 end
584 586
585 587 def test_avatar
586 588 # turn on avatars
587 589 Setting.gravatar_enabled = '1'
588 590 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
589 591 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
590 592 assert_nil avatar('jsmith')
591 593 assert_nil avatar(nil)
592 594
593 595 # turn off avatars
594 596 Setting.gravatar_enabled = '0'
595 597 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
596 598 end
597 599
598 600 def test_link_to_user
599 601 user = User.find(2)
600 602 t = link_to_user(user)
601 603 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
602 604 end
603 605
604 606 def test_link_to_user_should_not_link_to_locked_user
605 607 user = User.find(5)
606 608 assert user.locked?
607 609 t = link_to_user(user)
608 610 assert_equal user.name, t
609 611 end
610 612
611 613 def test_link_to_user_should_not_link_to_anonymous
612 614 user = User.anonymous
613 615 assert user.anonymous?
614 616 t = link_to_user(user)
615 617 assert_equal ::I18n.t(:label_user_anonymous), t
616 618 end
617 619
618 620 def test_link_to_project
619 621 project = Project.find(1)
620 622 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
621 623 link_to_project(project)
622 624 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
623 625 link_to_project(project, :action => 'settings')
624 626 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
625 627 link_to_project(project, {:only_path => false, :jump => 'blah'})
626 628 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
627 629 link_to_project(project, {:action => 'settings'}, :class => "project")
628 630 end
629 631 end
General Comments 0
You need to be logged in to leave comments. Login now