##// END OF EJS Templates
Don't turn #nnn with leading zeros into link (#11494)....
Jean-Philippe Lang -
r9894:28cdc8adfc5d
parent child
Show More
@@ -1,1201 +1,1201
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Displays a link to user's account page if active
46 # Displays a link to user's account page if active
47 def link_to_user(user, options={})
47 def link_to_user(user, options={})
48 if user.is_a?(User)
48 if user.is_a?(User)
49 name = h(user.name(options[:format]))
49 name = h(user.name(options[:format]))
50 if user.active?
50 if user.active?
51 link_to name, :controller => 'users', :action => 'show', :id => user
51 link_to name, :controller => 'users', :action => 'show', :id => user
52 else
52 else
53 name
53 name
54 end
54 end
55 else
55 else
56 h(user.to_s)
56 h(user.to_s)
57 end
57 end
58 end
58 end
59
59
60 # Displays a link to +issue+ with its subject.
60 # Displays a link to +issue+ with its subject.
61 # Examples:
61 # Examples:
62 #
62 #
63 # link_to_issue(issue) # => Defect #6: This is the subject
63 # link_to_issue(issue) # => Defect #6: This is the subject
64 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
64 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
65 # link_to_issue(issue, :subject => false) # => Defect #6
65 # link_to_issue(issue, :subject => false) # => Defect #6
66 # link_to_issue(issue, :project => true) # => Foo - Defect #6
66 # link_to_issue(issue, :project => true) # => Foo - Defect #6
67 #
67 #
68 def link_to_issue(issue, options={})
68 def link_to_issue(issue, options={})
69 title = nil
69 title = nil
70 subject = nil
70 subject = nil
71 if options[:subject] == false
71 if options[:subject] == false
72 title = truncate(issue.subject, :length => 60)
72 title = truncate(issue.subject, :length => 60)
73 else
73 else
74 subject = issue.subject
74 subject = issue.subject
75 if options[:truncate]
75 if options[:truncate]
76 subject = truncate(subject, :length => options[:truncate])
76 subject = truncate(subject, :length => options[:truncate])
77 end
77 end
78 end
78 end
79 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
79 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
80 :class => issue.css_classes,
80 :class => issue.css_classes,
81 :title => title
81 :title => title
82 s << h(": #{subject}") if subject
82 s << h(": #{subject}") if subject
83 s = h("#{issue.project} - ") + s if options[:project]
83 s = h("#{issue.project} - ") + s if options[:project]
84 s
84 s
85 end
85 end
86
86
87 # Generates a link to an attachment.
87 # Generates a link to an attachment.
88 # Options:
88 # Options:
89 # * :text - Link text (default to attachment filename)
89 # * :text - Link text (default to attachment filename)
90 # * :download - Force download (default: false)
90 # * :download - Force download (default: false)
91 def link_to_attachment(attachment, options={})
91 def link_to_attachment(attachment, options={})
92 text = options.delete(:text) || attachment.filename
92 text = options.delete(:text) || attachment.filename
93 action = options.delete(:download) ? 'download' : 'show'
93 action = options.delete(:download) ? 'download' : 'show'
94 opt_only_path = {}
94 opt_only_path = {}
95 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
95 opt_only_path[:only_path] = (options[:only_path] == false ? false : true)
96 options.delete(:only_path)
96 options.delete(:only_path)
97 link_to(h(text),
97 link_to(h(text),
98 {:controller => 'attachments', :action => action,
98 {:controller => 'attachments', :action => action,
99 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
99 :id => attachment, :filename => attachment.filename}.merge(opt_only_path),
100 options)
100 options)
101 end
101 end
102
102
103 # Generates a link to a SCM revision
103 # Generates a link to a SCM revision
104 # Options:
104 # Options:
105 # * :text - Link text (default to the formatted revision)
105 # * :text - Link text (default to the formatted revision)
106 def link_to_revision(revision, repository, options={})
106 def link_to_revision(revision, repository, options={})
107 if repository.is_a?(Project)
107 if repository.is_a?(Project)
108 repository = repository.repository
108 repository = repository.repository
109 end
109 end
110 text = options.delete(:text) || format_revision(revision)
110 text = options.delete(:text) || format_revision(revision)
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
111 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
112 link_to(
112 link_to(
113 h(text),
113 h(text),
114 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
114 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
115 :title => l(:label_revision_id, format_revision(revision))
115 :title => l(:label_revision_id, format_revision(revision))
116 )
116 )
117 end
117 end
118
118
119 # Generates a link to a message
119 # Generates a link to a message
120 def link_to_message(message, options={}, html_options = nil)
120 def link_to_message(message, options={}, html_options = nil)
121 link_to(
121 link_to(
122 h(truncate(message.subject, :length => 60)),
122 h(truncate(message.subject, :length => 60)),
123 { :controller => 'messages', :action => 'show',
123 { :controller => 'messages', :action => 'show',
124 :board_id => message.board_id,
124 :board_id => message.board_id,
125 :id => (message.parent_id || message.id),
125 :id => (message.parent_id || message.id),
126 :r => (message.parent_id && message.id),
126 :r => (message.parent_id && message.id),
127 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
127 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
128 }.merge(options),
128 }.merge(options),
129 html_options
129 html_options
130 )
130 )
131 end
131 end
132
132
133 # Generates a link to a project if active
133 # Generates a link to a project if active
134 # Examples:
134 # Examples:
135 #
135 #
136 # link_to_project(project) # => link to the specified project overview
136 # link_to_project(project) # => link to the specified project overview
137 # link_to_project(project, :action=>'settings') # => link to project settings
137 # link_to_project(project, :action=>'settings') # => link to project settings
138 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
138 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
139 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
139 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
140 #
140 #
141 def link_to_project(project, options={}, html_options = nil)
141 def link_to_project(project, options={}, html_options = nil)
142 if project.archived?
142 if project.archived?
143 h(project)
143 h(project)
144 else
144 else
145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
145 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
146 link_to(h(project), url, html_options)
146 link_to(h(project), url, html_options)
147 end
147 end
148 end
148 end
149
149
150 def thumbnail_tag(attachment)
150 def thumbnail_tag(attachment)
151 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
151 link_to image_tag(url_for(:controller => 'attachments', :action => 'thumbnail', :id => attachment)),
152 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
152 {:controller => 'attachments', :action => 'show', :id => attachment, :filename => attachment.filename},
153 :title => attachment.filename
153 :title => attachment.filename
154 end
154 end
155
155
156 def toggle_link(name, id, options={})
156 def toggle_link(name, id, options={})
157 onclick = "$('##{id}').toggle(); "
157 onclick = "$('##{id}').toggle(); "
158 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
158 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
159 onclick << "return false;"
159 onclick << "return false;"
160 link_to(name, "#", :onclick => onclick)
160 link_to(name, "#", :onclick => onclick)
161 end
161 end
162
162
163 def image_to_function(name, function, html_options = {})
163 def image_to_function(name, function, html_options = {})
164 html_options.symbolize_keys!
164 html_options.symbolize_keys!
165 tag(:input, html_options.merge({
165 tag(:input, html_options.merge({
166 :type => "image", :src => image_path(name),
166 :type => "image", :src => image_path(name),
167 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
167 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
168 }))
168 }))
169 end
169 end
170
170
171 def format_activity_title(text)
171 def format_activity_title(text)
172 h(truncate_single_line(text, :length => 100))
172 h(truncate_single_line(text, :length => 100))
173 end
173 end
174
174
175 def format_activity_day(date)
175 def format_activity_day(date)
176 date == User.current.today ? l(:label_today).titleize : format_date(date)
176 date == User.current.today ? l(:label_today).titleize : format_date(date)
177 end
177 end
178
178
179 def format_activity_description(text)
179 def format_activity_description(text)
180 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
180 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
181 ).gsub(/[\r\n]+/, "<br />").html_safe
181 ).gsub(/[\r\n]+/, "<br />").html_safe
182 end
182 end
183
183
184 def format_version_name(version)
184 def format_version_name(version)
185 if version.project == @project
185 if version.project == @project
186 h(version)
186 h(version)
187 else
187 else
188 h("#{version.project} - #{version}")
188 h("#{version.project} - #{version}")
189 end
189 end
190 end
190 end
191
191
192 def due_date_distance_in_words(date)
192 def due_date_distance_in_words(date)
193 if date
193 if date
194 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
194 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
195 end
195 end
196 end
196 end
197
197
198 def render_page_hierarchy(pages, node=nil, options={})
198 def render_page_hierarchy(pages, node=nil, options={})
199 content = ''
199 content = ''
200 if pages[node]
200 if pages[node]
201 content << "<ul class=\"pages-hierarchy\">\n"
201 content << "<ul class=\"pages-hierarchy\">\n"
202 pages[node].each do |page|
202 pages[node].each do |page|
203 content << "<li>"
203 content << "<li>"
204 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
204 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
205 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
205 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
206 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
206 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
207 content << "</li>\n"
207 content << "</li>\n"
208 end
208 end
209 content << "</ul>\n"
209 content << "</ul>\n"
210 end
210 end
211 content.html_safe
211 content.html_safe
212 end
212 end
213
213
214 # Renders flash messages
214 # Renders flash messages
215 def render_flash_messages
215 def render_flash_messages
216 s = ''
216 s = ''
217 flash.each do |k,v|
217 flash.each do |k,v|
218 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
218 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
219 end
219 end
220 s.html_safe
220 s.html_safe
221 end
221 end
222
222
223 # Renders tabs and their content
223 # Renders tabs and their content
224 def render_tabs(tabs)
224 def render_tabs(tabs)
225 if tabs.any?
225 if tabs.any?
226 render :partial => 'common/tabs', :locals => {:tabs => tabs}
226 render :partial => 'common/tabs', :locals => {:tabs => tabs}
227 else
227 else
228 content_tag 'p', l(:label_no_data), :class => "nodata"
228 content_tag 'p', l(:label_no_data), :class => "nodata"
229 end
229 end
230 end
230 end
231
231
232 # Renders the project quick-jump box
232 # Renders the project quick-jump box
233 def render_project_jump_box
233 def render_project_jump_box
234 return unless User.current.logged?
234 return unless User.current.logged?
235 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
235 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
236 if projects.any?
236 if projects.any?
237 options =
237 options =
238 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
238 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
239 '<option value="" disabled="disabled">---</option>').html_safe
239 '<option value="" disabled="disabled">---</option>').html_safe
240
240
241 options << project_tree_options_for_select(projects, :selected => @project) do |p|
241 options << project_tree_options_for_select(projects, :selected => @project) do |p|
242 { :value => project_path(:id => p, :jump => current_menu_item) }
242 { :value => project_path(:id => p, :jump => current_menu_item) }
243 end
243 end
244
244
245 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
245 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
246 end
246 end
247 end
247 end
248
248
249 def project_tree_options_for_select(projects, options = {})
249 def project_tree_options_for_select(projects, options = {})
250 s = ''
250 s = ''
251 project_tree(projects) do |project, level|
251 project_tree(projects) do |project, level|
252 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
252 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
253 tag_options = {:value => project.id}
253 tag_options = {:value => project.id}
254 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
254 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
255 tag_options[:selected] = 'selected'
255 tag_options[:selected] = 'selected'
256 else
256 else
257 tag_options[:selected] = nil
257 tag_options[:selected] = nil
258 end
258 end
259 tag_options.merge!(yield(project)) if block_given?
259 tag_options.merge!(yield(project)) if block_given?
260 s << content_tag('option', name_prefix + h(project), tag_options)
260 s << content_tag('option', name_prefix + h(project), tag_options)
261 end
261 end
262 s.html_safe
262 s.html_safe
263 end
263 end
264
264
265 # Yields the given block for each project with its level in the tree
265 # Yields the given block for each project with its level in the tree
266 #
266 #
267 # Wrapper for Project#project_tree
267 # Wrapper for Project#project_tree
268 def project_tree(projects, &block)
268 def project_tree(projects, &block)
269 Project.project_tree(projects, &block)
269 Project.project_tree(projects, &block)
270 end
270 end
271
271
272 def project_nested_ul(projects, &block)
272 def project_nested_ul(projects, &block)
273 s = ''
273 s = ''
274 if projects.any?
274 if projects.any?
275 ancestors = []
275 ancestors = []
276 projects.sort_by(&:lft).each do |project|
276 projects.sort_by(&:lft).each do |project|
277 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
277 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
278 s << "<ul>\n"
278 s << "<ul>\n"
279 else
279 else
280 ancestors.pop
280 ancestors.pop
281 s << "</li>"
281 s << "</li>"
282 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
282 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
283 ancestors.pop
283 ancestors.pop
284 s << "</ul></li>\n"
284 s << "</ul></li>\n"
285 end
285 end
286 end
286 end
287 s << "<li>"
287 s << "<li>"
288 s << yield(project).to_s
288 s << yield(project).to_s
289 ancestors << project
289 ancestors << project
290 end
290 end
291 s << ("</li></ul>\n" * ancestors.size)
291 s << ("</li></ul>\n" * ancestors.size)
292 end
292 end
293 s.html_safe
293 s.html_safe
294 end
294 end
295
295
296 def principals_check_box_tags(name, principals)
296 def principals_check_box_tags(name, principals)
297 s = ''
297 s = ''
298 principals.sort.each do |principal|
298 principals.sort.each do |principal|
299 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
299 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
300 end
300 end
301 s.html_safe
301 s.html_safe
302 end
302 end
303
303
304 # Returns a string for users/groups option tags
304 # Returns a string for users/groups option tags
305 def principals_options_for_select(collection, selected=nil)
305 def principals_options_for_select(collection, selected=nil)
306 s = ''
306 s = ''
307 if collection.include?(User.current)
307 if collection.include?(User.current)
308 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
308 s << content_tag('option', "<< #{l(:label_me)} >>".html_safe, :value => User.current.id)
309 end
309 end
310 groups = ''
310 groups = ''
311 collection.sort.each do |element|
311 collection.sort.each do |element|
312 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
312 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
313 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
313 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
314 end
314 end
315 unless groups.empty?
315 unless groups.empty?
316 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
316 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
317 end
317 end
318 s.html_safe
318 s.html_safe
319 end
319 end
320
320
321 # Truncates and returns the string as a single line
321 # Truncates and returns the string as a single line
322 def truncate_single_line(string, *args)
322 def truncate_single_line(string, *args)
323 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
323 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
324 end
324 end
325
325
326 # Truncates at line break after 250 characters or options[:length]
326 # Truncates at line break after 250 characters or options[:length]
327 def truncate_lines(string, options={})
327 def truncate_lines(string, options={})
328 length = options[:length] || 250
328 length = options[:length] || 250
329 if string.to_s =~ /\A(.{#{length}}.*?)$/m
329 if string.to_s =~ /\A(.{#{length}}.*?)$/m
330 "#{$1}..."
330 "#{$1}..."
331 else
331 else
332 string
332 string
333 end
333 end
334 end
334 end
335
335
336 def anchor(text)
336 def anchor(text)
337 text.to_s.gsub(' ', '_')
337 text.to_s.gsub(' ', '_')
338 end
338 end
339
339
340 def html_hours(text)
340 def html_hours(text)
341 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
341 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
342 end
342 end
343
343
344 def authoring(created, author, options={})
344 def authoring(created, author, options={})
345 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
345 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
346 end
346 end
347
347
348 def time_tag(time)
348 def time_tag(time)
349 text = distance_of_time_in_words(Time.now, time)
349 text = distance_of_time_in_words(Time.now, time)
350 if @project
350 if @project
351 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
351 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
352 else
352 else
353 content_tag('acronym', text, :title => format_time(time))
353 content_tag('acronym', text, :title => format_time(time))
354 end
354 end
355 end
355 end
356
356
357 def syntax_highlight_lines(name, content)
357 def syntax_highlight_lines(name, content)
358 lines = []
358 lines = []
359 syntax_highlight(name, content).each_line { |line| lines << line }
359 syntax_highlight(name, content).each_line { |line| lines << line }
360 lines
360 lines
361 end
361 end
362
362
363 def syntax_highlight(name, content)
363 def syntax_highlight(name, content)
364 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
364 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
365 end
365 end
366
366
367 def to_path_param(path)
367 def to_path_param(path)
368 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
368 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
369 str.blank? ? nil : str
369 str.blank? ? nil : str
370 end
370 end
371
371
372 def pagination_links_full(paginator, count=nil, options={})
372 def pagination_links_full(paginator, count=nil, options={})
373 page_param = options.delete(:page_param) || :page
373 page_param = options.delete(:page_param) || :page
374 per_page_links = options.delete(:per_page_links)
374 per_page_links = options.delete(:per_page_links)
375 url_param = params.dup
375 url_param = params.dup
376
376
377 html = ''
377 html = ''
378 if paginator.current.previous
378 if paginator.current.previous
379 # \xc2\xab(utf-8) = &#171;
379 # \xc2\xab(utf-8) = &#171;
380 html << link_to_content_update(
380 html << link_to_content_update(
381 "\xc2\xab " + l(:label_previous),
381 "\xc2\xab " + l(:label_previous),
382 url_param.merge(page_param => paginator.current.previous)) + ' '
382 url_param.merge(page_param => paginator.current.previous)) + ' '
383 end
383 end
384
384
385 html << (pagination_links_each(paginator, options) do |n|
385 html << (pagination_links_each(paginator, options) do |n|
386 link_to_content_update(n.to_s, url_param.merge(page_param => n))
386 link_to_content_update(n.to_s, url_param.merge(page_param => n))
387 end || '')
387 end || '')
388
388
389 if paginator.current.next
389 if paginator.current.next
390 # \xc2\xbb(utf-8) = &#187;
390 # \xc2\xbb(utf-8) = &#187;
391 html << ' ' + link_to_content_update(
391 html << ' ' + link_to_content_update(
392 (l(:label_next) + " \xc2\xbb"),
392 (l(:label_next) + " \xc2\xbb"),
393 url_param.merge(page_param => paginator.current.next))
393 url_param.merge(page_param => paginator.current.next))
394 end
394 end
395
395
396 unless count.nil?
396 unless count.nil?
397 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
397 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
398 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
398 if per_page_links != false && links = per_page_links(paginator.items_per_page, count)
399 html << " | #{links}"
399 html << " | #{links}"
400 end
400 end
401 end
401 end
402
402
403 html.html_safe
403 html.html_safe
404 end
404 end
405
405
406 def per_page_links(selected=nil, item_count=nil)
406 def per_page_links(selected=nil, item_count=nil)
407 values = Setting.per_page_options_array
407 values = Setting.per_page_options_array
408 if item_count && values.any?
408 if item_count && values.any?
409 if item_count > values.first
409 if item_count > values.first
410 max = values.detect {|value| value >= item_count} || item_count
410 max = values.detect {|value| value >= item_count} || item_count
411 else
411 else
412 max = item_count
412 max = item_count
413 end
413 end
414 values = values.select {|value| value <= max || value == selected}
414 values = values.select {|value| value <= max || value == selected}
415 end
415 end
416 if values.empty? || (values.size == 1 && values.first == selected)
416 if values.empty? || (values.size == 1 && values.first == selected)
417 return nil
417 return nil
418 end
418 end
419 links = values.collect do |n|
419 links = values.collect do |n|
420 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
420 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
421 end
421 end
422 l(:label_display_per_page, links.join(', '))
422 l(:label_display_per_page, links.join(', '))
423 end
423 end
424
424
425 def reorder_links(name, url, method = :post)
425 def reorder_links(name, url, method = :post)
426 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
426 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
427 url.merge({"#{name}[move_to]" => 'highest'}),
427 url.merge({"#{name}[move_to]" => 'highest'}),
428 :method => method, :title => l(:label_sort_highest)) +
428 :method => method, :title => l(:label_sort_highest)) +
429 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
429 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
430 url.merge({"#{name}[move_to]" => 'higher'}),
430 url.merge({"#{name}[move_to]" => 'higher'}),
431 :method => method, :title => l(:label_sort_higher)) +
431 :method => method, :title => l(:label_sort_higher)) +
432 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
432 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
433 url.merge({"#{name}[move_to]" => 'lower'}),
433 url.merge({"#{name}[move_to]" => 'lower'}),
434 :method => method, :title => l(:label_sort_lower)) +
434 :method => method, :title => l(:label_sort_lower)) +
435 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
435 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
436 url.merge({"#{name}[move_to]" => 'lowest'}),
436 url.merge({"#{name}[move_to]" => 'lowest'}),
437 :method => method, :title => l(:label_sort_lowest))
437 :method => method, :title => l(:label_sort_lowest))
438 end
438 end
439
439
440 def breadcrumb(*args)
440 def breadcrumb(*args)
441 elements = args.flatten
441 elements = args.flatten
442 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
442 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
443 end
443 end
444
444
445 def other_formats_links(&block)
445 def other_formats_links(&block)
446 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
446 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
447 yield Redmine::Views::OtherFormatsBuilder.new(self)
447 yield Redmine::Views::OtherFormatsBuilder.new(self)
448 concat('</p>'.html_safe)
448 concat('</p>'.html_safe)
449 end
449 end
450
450
451 def page_header_title
451 def page_header_title
452 if @project.nil? || @project.new_record?
452 if @project.nil? || @project.new_record?
453 h(Setting.app_title)
453 h(Setting.app_title)
454 else
454 else
455 b = []
455 b = []
456 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
456 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
457 if ancestors.any?
457 if ancestors.any?
458 root = ancestors.shift
458 root = ancestors.shift
459 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
459 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
460 if ancestors.size > 2
460 if ancestors.size > 2
461 b << "\xe2\x80\xa6"
461 b << "\xe2\x80\xa6"
462 ancestors = ancestors[-2, 2]
462 ancestors = ancestors[-2, 2]
463 end
463 end
464 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
464 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
465 end
465 end
466 b << h(@project)
466 b << h(@project)
467 b.join(" \xc2\xbb ").html_safe
467 b.join(" \xc2\xbb ").html_safe
468 end
468 end
469 end
469 end
470
470
471 def html_title(*args)
471 def html_title(*args)
472 if args.empty?
472 if args.empty?
473 title = @html_title || []
473 title = @html_title || []
474 title << @project.name if @project
474 title << @project.name if @project
475 title << Setting.app_title unless Setting.app_title == title.last
475 title << Setting.app_title unless Setting.app_title == title.last
476 title.select {|t| !t.blank? }.join(' - ')
476 title.select {|t| !t.blank? }.join(' - ')
477 else
477 else
478 @html_title ||= []
478 @html_title ||= []
479 @html_title += args
479 @html_title += args
480 end
480 end
481 end
481 end
482
482
483 # Returns the theme, controller name, and action as css classes for the
483 # Returns the theme, controller name, and action as css classes for the
484 # HTML body.
484 # HTML body.
485 def body_css_classes
485 def body_css_classes
486 css = []
486 css = []
487 if theme = Redmine::Themes.theme(Setting.ui_theme)
487 if theme = Redmine::Themes.theme(Setting.ui_theme)
488 css << 'theme-' + theme.name
488 css << 'theme-' + theme.name
489 end
489 end
490
490
491 css << 'controller-' + controller_name
491 css << 'controller-' + controller_name
492 css << 'action-' + action_name
492 css << 'action-' + action_name
493 css.join(' ')
493 css.join(' ')
494 end
494 end
495
495
496 def accesskey(s)
496 def accesskey(s)
497 Redmine::AccessKeys.key_for s
497 Redmine::AccessKeys.key_for s
498 end
498 end
499
499
500 # Formats text according to system settings.
500 # Formats text according to system settings.
501 # 2 ways to call this method:
501 # 2 ways to call this method:
502 # * with a String: textilizable(text, options)
502 # * with a String: textilizable(text, options)
503 # * with an object and one of its attribute: textilizable(issue, :description, options)
503 # * with an object and one of its attribute: textilizable(issue, :description, options)
504 def textilizable(*args)
504 def textilizable(*args)
505 options = args.last.is_a?(Hash) ? args.pop : {}
505 options = args.last.is_a?(Hash) ? args.pop : {}
506 case args.size
506 case args.size
507 when 1
507 when 1
508 obj = options[:object]
508 obj = options[:object]
509 text = args.shift
509 text = args.shift
510 when 2
510 when 2
511 obj = args.shift
511 obj = args.shift
512 attr = args.shift
512 attr = args.shift
513 text = obj.send(attr).to_s
513 text = obj.send(attr).to_s
514 else
514 else
515 raise ArgumentError, 'invalid arguments to textilizable'
515 raise ArgumentError, 'invalid arguments to textilizable'
516 end
516 end
517 return '' if text.blank?
517 return '' if text.blank?
518 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
518 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
519 only_path = options.delete(:only_path) == false ? false : true
519 only_path = options.delete(:only_path) == false ? false : true
520
520
521 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
521 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
522
522
523 @parsed_headings = []
523 @parsed_headings = []
524 @heading_anchors = {}
524 @heading_anchors = {}
525 @current_section = 0 if options[:edit_section_links]
525 @current_section = 0 if options[:edit_section_links]
526
526
527 parse_sections(text, project, obj, attr, only_path, options)
527 parse_sections(text, project, obj, attr, only_path, options)
528 text = parse_non_pre_blocks(text) do |text|
528 text = parse_non_pre_blocks(text) do |text|
529 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
529 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
530 send method_name, text, project, obj, attr, only_path, options
530 send method_name, text, project, obj, attr, only_path, options
531 end
531 end
532 end
532 end
533 parse_headings(text, project, obj, attr, only_path, options)
533 parse_headings(text, project, obj, attr, only_path, options)
534
534
535 if @parsed_headings.any?
535 if @parsed_headings.any?
536 replace_toc(text, @parsed_headings)
536 replace_toc(text, @parsed_headings)
537 end
537 end
538
538
539 text.html_safe
539 text.html_safe
540 end
540 end
541
541
542 def parse_non_pre_blocks(text)
542 def parse_non_pre_blocks(text)
543 s = StringScanner.new(text)
543 s = StringScanner.new(text)
544 tags = []
544 tags = []
545 parsed = ''
545 parsed = ''
546 while !s.eos?
546 while !s.eos?
547 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
547 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
548 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
548 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
549 if tags.empty?
549 if tags.empty?
550 yield text
550 yield text
551 end
551 end
552 parsed << text
552 parsed << text
553 if tag
553 if tag
554 if closing
554 if closing
555 if tags.last == tag.downcase
555 if tags.last == tag.downcase
556 tags.pop
556 tags.pop
557 end
557 end
558 else
558 else
559 tags << tag.downcase
559 tags << tag.downcase
560 end
560 end
561 parsed << full_tag
561 parsed << full_tag
562 end
562 end
563 end
563 end
564 # Close any non closing tags
564 # Close any non closing tags
565 while tag = tags.pop
565 while tag = tags.pop
566 parsed << "</#{tag}>"
566 parsed << "</#{tag}>"
567 end
567 end
568 parsed
568 parsed
569 end
569 end
570
570
571 def parse_inline_attachments(text, project, obj, attr, only_path, options)
571 def parse_inline_attachments(text, project, obj, attr, only_path, options)
572 # when using an image link, try to use an attachment, if possible
572 # when using an image link, try to use an attachment, if possible
573 if options[:attachments] || (obj && obj.respond_to?(:attachments))
573 if options[:attachments] || (obj && obj.respond_to?(:attachments))
574 attachments = options[:attachments] || obj.attachments
574 attachments = options[:attachments] || obj.attachments
575 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
575 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
576 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
576 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
577 # search for the picture in attachments
577 # search for the picture in attachments
578 if found = Attachment.latest_attach(attachments, filename)
578 if found = Attachment.latest_attach(attachments, filename)
579 image_url = url_for :only_path => only_path, :controller => 'attachments',
579 image_url = url_for :only_path => only_path, :controller => 'attachments',
580 :action => 'download', :id => found
580 :action => 'download', :id => found
581 desc = found.description.to_s.gsub('"', '')
581 desc = found.description.to_s.gsub('"', '')
582 if !desc.blank? && alttext.blank?
582 if !desc.blank? && alttext.blank?
583 alt = " title=\"#{desc}\" alt=\"#{desc}\""
583 alt = " title=\"#{desc}\" alt=\"#{desc}\""
584 end
584 end
585 "src=\"#{image_url}\"#{alt}"
585 "src=\"#{image_url}\"#{alt}"
586 else
586 else
587 m
587 m
588 end
588 end
589 end
589 end
590 end
590 end
591 end
591 end
592
592
593 # Wiki links
593 # Wiki links
594 #
594 #
595 # Examples:
595 # Examples:
596 # [[mypage]]
596 # [[mypage]]
597 # [[mypage|mytext]]
597 # [[mypage|mytext]]
598 # wiki links can refer other project wikis, using project name or identifier:
598 # wiki links can refer other project wikis, using project name or identifier:
599 # [[project:]] -> wiki starting page
599 # [[project:]] -> wiki starting page
600 # [[project:|mytext]]
600 # [[project:|mytext]]
601 # [[project:mypage]]
601 # [[project:mypage]]
602 # [[project:mypage|mytext]]
602 # [[project:mypage|mytext]]
603 def parse_wiki_links(text, project, obj, attr, only_path, options)
603 def parse_wiki_links(text, project, obj, attr, only_path, options)
604 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
604 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
605 link_project = project
605 link_project = project
606 esc, all, page, title = $1, $2, $3, $5
606 esc, all, page, title = $1, $2, $3, $5
607 if esc.nil?
607 if esc.nil?
608 if page =~ /^([^\:]+)\:(.*)$/
608 if page =~ /^([^\:]+)\:(.*)$/
609 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
609 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
610 page = $2
610 page = $2
611 title ||= $1 if page.blank?
611 title ||= $1 if page.blank?
612 end
612 end
613
613
614 if link_project && link_project.wiki
614 if link_project && link_project.wiki
615 # extract anchor
615 # extract anchor
616 anchor = nil
616 anchor = nil
617 if page =~ /^(.+?)\#(.+)$/
617 if page =~ /^(.+?)\#(.+)$/
618 page, anchor = $1, $2
618 page, anchor = $1, $2
619 end
619 end
620 anchor = sanitize_anchor_name(anchor) if anchor.present?
620 anchor = sanitize_anchor_name(anchor) if anchor.present?
621 # check if page exists
621 # check if page exists
622 wiki_page = link_project.wiki.find_page(page)
622 wiki_page = link_project.wiki.find_page(page)
623 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
623 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
624 "##{anchor}"
624 "##{anchor}"
625 else
625 else
626 case options[:wiki_links]
626 case options[:wiki_links]
627 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
627 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
628 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
628 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
629 else
629 else
630 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
630 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
631 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
631 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
632 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
632 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
633 :id => wiki_page_id, :anchor => anchor, :parent => parent)
633 :id => wiki_page_id, :anchor => anchor, :parent => parent)
634 end
634 end
635 end
635 end
636 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
636 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
637 else
637 else
638 # project or wiki doesn't exist
638 # project or wiki doesn't exist
639 all
639 all
640 end
640 end
641 else
641 else
642 all
642 all
643 end
643 end
644 end
644 end
645 end
645 end
646
646
647 # Redmine links
647 # Redmine links
648 #
648 #
649 # Examples:
649 # Examples:
650 # Issues:
650 # Issues:
651 # #52 -> Link to issue #52
651 # #52 -> Link to issue #52
652 # Changesets:
652 # Changesets:
653 # r52 -> Link to revision 52
653 # r52 -> Link to revision 52
654 # commit:a85130f -> Link to scmid starting with a85130f
654 # commit:a85130f -> Link to scmid starting with a85130f
655 # Documents:
655 # Documents:
656 # document#17 -> Link to document with id 17
656 # document#17 -> Link to document with id 17
657 # document:Greetings -> Link to the document with title "Greetings"
657 # document:Greetings -> Link to the document with title "Greetings"
658 # document:"Some document" -> Link to the document with title "Some document"
658 # document:"Some document" -> Link to the document with title "Some document"
659 # Versions:
659 # Versions:
660 # version#3 -> Link to version with id 3
660 # version#3 -> Link to version with id 3
661 # version:1.0.0 -> Link to version named "1.0.0"
661 # version:1.0.0 -> Link to version named "1.0.0"
662 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
662 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
663 # Attachments:
663 # Attachments:
664 # attachment:file.zip -> Link to the attachment of the current object named file.zip
664 # attachment:file.zip -> Link to the attachment of the current object named file.zip
665 # Source files:
665 # Source files:
666 # source:some/file -> Link to the file located at /some/file in the project's repository
666 # source:some/file -> Link to the file located at /some/file in the project's repository
667 # source:some/file@52 -> Link to the file's revision 52
667 # source:some/file@52 -> Link to the file's revision 52
668 # source:some/file#L120 -> Link to line 120 of the file
668 # source:some/file#L120 -> Link to line 120 of the file
669 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
669 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
670 # export:some/file -> Force the download of the file
670 # export:some/file -> Force the download of the file
671 # Forum messages:
671 # Forum messages:
672 # message#1218 -> Link to message with id 1218
672 # message#1218 -> Link to message with id 1218
673 #
673 #
674 # Links can refer other objects from other projects, using project identifier:
674 # Links can refer other objects from other projects, using project identifier:
675 # identifier:r52
675 # identifier:r52
676 # identifier:document:"Some document"
676 # identifier:document:"Some document"
677 # identifier:version:1.0.0
677 # identifier:version:1.0.0
678 # identifier:source:some/file
678 # identifier:source:some/file
679 def parse_redmine_links(text, project, obj, attr, only_path, options)
679 def parse_redmine_links(text, project, obj, attr, only_path, options)
680 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
680 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
681 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
681 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
682 link = nil
682 link = nil
683 if project_identifier
683 if project_identifier
684 project = Project.visible.find_by_identifier(project_identifier)
684 project = Project.visible.find_by_identifier(project_identifier)
685 end
685 end
686 if esc.nil?
686 if esc.nil?
687 if prefix.nil? && sep == 'r'
687 if prefix.nil? && sep == 'r'
688 if project
688 if project
689 repository = nil
689 repository = nil
690 if repo_identifier
690 if repo_identifier
691 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
691 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
692 else
692 else
693 repository = project.repository
693 repository = project.repository
694 end
694 end
695 # project.changesets.visible raises an SQL error because of a double join on repositories
695 # project.changesets.visible raises an SQL error because of a double join on repositories
696 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
696 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
697 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
697 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
698 :class => 'changeset',
698 :class => 'changeset',
699 :title => truncate_single_line(changeset.comments, :length => 100))
699 :title => truncate_single_line(changeset.comments, :length => 100))
700 end
700 end
701 end
701 end
702 elsif sep == '#'
702 elsif sep == '#'
703 oid = identifier.to_i
703 oid = identifier.to_i
704 case prefix
704 case prefix
705 when nil
705 when nil
706 if issue = Issue.visible.find_by_id(oid, :include => :status)
706 if oid.to_s == identifier && issue = Issue.visible.find_by_id(oid, :include => :status)
707 anchor = comment_id ? "note-#{comment_id}" : nil
707 anchor = comment_id ? "note-#{comment_id}" : nil
708 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
708 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
709 :class => issue.css_classes,
709 :class => issue.css_classes,
710 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
710 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
711 end
711 end
712 when 'document'
712 when 'document'
713 if document = Document.visible.find_by_id(oid)
713 if document = Document.visible.find_by_id(oid)
714 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
714 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
715 :class => 'document'
715 :class => 'document'
716 end
716 end
717 when 'version'
717 when 'version'
718 if version = Version.visible.find_by_id(oid)
718 if version = Version.visible.find_by_id(oid)
719 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
719 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
720 :class => 'version'
720 :class => 'version'
721 end
721 end
722 when 'message'
722 when 'message'
723 if message = Message.visible.find_by_id(oid, :include => :parent)
723 if message = Message.visible.find_by_id(oid, :include => :parent)
724 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
724 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
725 end
725 end
726 when 'forum'
726 when 'forum'
727 if board = Board.visible.find_by_id(oid)
727 if board = Board.visible.find_by_id(oid)
728 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
728 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
729 :class => 'board'
729 :class => 'board'
730 end
730 end
731 when 'news'
731 when 'news'
732 if news = News.visible.find_by_id(oid)
732 if news = News.visible.find_by_id(oid)
733 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
733 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
734 :class => 'news'
734 :class => 'news'
735 end
735 end
736 when 'project'
736 when 'project'
737 if p = Project.visible.find_by_id(oid)
737 if p = Project.visible.find_by_id(oid)
738 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
738 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
739 end
739 end
740 end
740 end
741 elsif sep == ':'
741 elsif sep == ':'
742 # removes the double quotes if any
742 # removes the double quotes if any
743 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
743 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
744 case prefix
744 case prefix
745 when 'document'
745 when 'document'
746 if project && document = project.documents.visible.find_by_title(name)
746 if project && document = project.documents.visible.find_by_title(name)
747 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
747 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
748 :class => 'document'
748 :class => 'document'
749 end
749 end
750 when 'version'
750 when 'version'
751 if project && version = project.versions.visible.find_by_name(name)
751 if project && version = project.versions.visible.find_by_name(name)
752 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
752 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
753 :class => 'version'
753 :class => 'version'
754 end
754 end
755 when 'forum'
755 when 'forum'
756 if project && board = project.boards.visible.find_by_name(name)
756 if project && board = project.boards.visible.find_by_name(name)
757 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
757 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
758 :class => 'board'
758 :class => 'board'
759 end
759 end
760 when 'news'
760 when 'news'
761 if project && news = project.news.visible.find_by_title(name)
761 if project && news = project.news.visible.find_by_title(name)
762 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
762 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
763 :class => 'news'
763 :class => 'news'
764 end
764 end
765 when 'commit', 'source', 'export'
765 when 'commit', 'source', 'export'
766 if project
766 if project
767 repository = nil
767 repository = nil
768 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
768 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
769 repo_prefix, repo_identifier, name = $1, $2, $3
769 repo_prefix, repo_identifier, name = $1, $2, $3
770 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
770 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
771 else
771 else
772 repository = project.repository
772 repository = project.repository
773 end
773 end
774 if prefix == 'commit'
774 if prefix == 'commit'
775 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
775 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
776 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
776 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
777 :class => 'changeset',
777 :class => 'changeset',
778 :title => truncate_single_line(h(changeset.comments), :length => 100)
778 :title => truncate_single_line(h(changeset.comments), :length => 100)
779 end
779 end
780 else
780 else
781 if repository && User.current.allowed_to?(:browse_repository, project)
781 if repository && User.current.allowed_to?(:browse_repository, project)
782 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
782 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
783 path, rev, anchor = $1, $3, $5
783 path, rev, anchor = $1, $3, $5
784 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
784 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
785 :path => to_path_param(path),
785 :path => to_path_param(path),
786 :rev => rev,
786 :rev => rev,
787 :anchor => anchor,
787 :anchor => anchor,
788 :format => (prefix == 'export' ? 'raw' : nil)},
788 :format => (prefix == 'export' ? 'raw' : nil)},
789 :class => (prefix == 'export' ? 'source download' : 'source')
789 :class => (prefix == 'export' ? 'source download' : 'source')
790 end
790 end
791 end
791 end
792 repo_prefix = nil
792 repo_prefix = nil
793 end
793 end
794 when 'attachment'
794 when 'attachment'
795 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
795 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
796 if attachments && attachment = attachments.detect {|a| a.filename == name }
796 if attachments && attachment = attachments.detect {|a| a.filename == name }
797 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
797 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
798 :class => 'attachment'
798 :class => 'attachment'
799 end
799 end
800 when 'project'
800 when 'project'
801 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
801 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
802 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
802 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
803 end
803 end
804 end
804 end
805 end
805 end
806 end
806 end
807 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
807 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
808 end
808 end
809 end
809 end
810
810
811 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
811 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
812
812
813 def parse_sections(text, project, obj, attr, only_path, options)
813 def parse_sections(text, project, obj, attr, only_path, options)
814 return unless options[:edit_section_links]
814 return unless options[:edit_section_links]
815 text.gsub!(HEADING_RE) do
815 text.gsub!(HEADING_RE) do
816 heading = $1
816 heading = $1
817 @current_section += 1
817 @current_section += 1
818 if @current_section > 1
818 if @current_section > 1
819 content_tag('div',
819 content_tag('div',
820 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
820 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
821 :class => 'contextual',
821 :class => 'contextual',
822 :title => l(:button_edit_section)) + heading.html_safe
822 :title => l(:button_edit_section)) + heading.html_safe
823 else
823 else
824 heading
824 heading
825 end
825 end
826 end
826 end
827 end
827 end
828
828
829 # Headings and TOC
829 # Headings and TOC
830 # Adds ids and links to headings unless options[:headings] is set to false
830 # Adds ids and links to headings unless options[:headings] is set to false
831 def parse_headings(text, project, obj, attr, only_path, options)
831 def parse_headings(text, project, obj, attr, only_path, options)
832 return if options[:headings] == false
832 return if options[:headings] == false
833
833
834 text.gsub!(HEADING_RE) do
834 text.gsub!(HEADING_RE) do
835 level, attrs, content = $2.to_i, $3, $4
835 level, attrs, content = $2.to_i, $3, $4
836 item = strip_tags(content).strip
836 item = strip_tags(content).strip
837 anchor = sanitize_anchor_name(item)
837 anchor = sanitize_anchor_name(item)
838 # used for single-file wiki export
838 # used for single-file wiki export
839 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
839 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
840 @heading_anchors[anchor] ||= 0
840 @heading_anchors[anchor] ||= 0
841 idx = (@heading_anchors[anchor] += 1)
841 idx = (@heading_anchors[anchor] += 1)
842 if idx > 1
842 if idx > 1
843 anchor = "#{anchor}-#{idx}"
843 anchor = "#{anchor}-#{idx}"
844 end
844 end
845 @parsed_headings << [level, anchor, item]
845 @parsed_headings << [level, anchor, item]
846 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
846 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
847 end
847 end
848 end
848 end
849
849
850 MACROS_RE = /
850 MACROS_RE = /
851 (!)? # escaping
851 (!)? # escaping
852 (
852 (
853 \{\{ # opening tag
853 \{\{ # opening tag
854 ([\w]+) # macro name
854 ([\w]+) # macro name
855 (\(([^\}]*)\))? # optional arguments
855 (\(([^\}]*)\))? # optional arguments
856 \}\} # closing tag
856 \}\} # closing tag
857 )
857 )
858 /x unless const_defined?(:MACROS_RE)
858 /x unless const_defined?(:MACROS_RE)
859
859
860 # Macros substitution
860 # Macros substitution
861 def parse_macros(text, project, obj, attr, only_path, options)
861 def parse_macros(text, project, obj, attr, only_path, options)
862 text.gsub!(MACROS_RE) do
862 text.gsub!(MACROS_RE) do
863 esc, all, macro = $1, $2, $3.downcase
863 esc, all, macro = $1, $2, $3.downcase
864 args = ($5 || '').split(',').each(&:strip)
864 args = ($5 || '').split(',').each(&:strip)
865 if esc.nil?
865 if esc.nil?
866 begin
866 begin
867 exec_macro(macro, obj, args)
867 exec_macro(macro, obj, args)
868 rescue => e
868 rescue => e
869 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
869 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
870 end || all
870 end || all
871 else
871 else
872 all
872 all
873 end
873 end
874 end
874 end
875 end
875 end
876
876
877 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
877 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
878
878
879 # Renders the TOC with given headings
879 # Renders the TOC with given headings
880 def replace_toc(text, headings)
880 def replace_toc(text, headings)
881 text.gsub!(TOC_RE) do
881 text.gsub!(TOC_RE) do
882 if headings.empty?
882 if headings.empty?
883 ''
883 ''
884 else
884 else
885 div_class = 'toc'
885 div_class = 'toc'
886 div_class << ' right' if $1 == '>'
886 div_class << ' right' if $1 == '>'
887 div_class << ' left' if $1 == '<'
887 div_class << ' left' if $1 == '<'
888 out = "<ul class=\"#{div_class}\"><li>"
888 out = "<ul class=\"#{div_class}\"><li>"
889 root = headings.map(&:first).min
889 root = headings.map(&:first).min
890 current = root
890 current = root
891 started = false
891 started = false
892 headings.each do |level, anchor, item|
892 headings.each do |level, anchor, item|
893 if level > current
893 if level > current
894 out << '<ul><li>' * (level - current)
894 out << '<ul><li>' * (level - current)
895 elsif level < current
895 elsif level < current
896 out << "</li></ul>\n" * (current - level) + "</li><li>"
896 out << "</li></ul>\n" * (current - level) + "</li><li>"
897 elsif started
897 elsif started
898 out << '</li><li>'
898 out << '</li><li>'
899 end
899 end
900 out << "<a href=\"##{anchor}\">#{item}</a>"
900 out << "<a href=\"##{anchor}\">#{item}</a>"
901 current = level
901 current = level
902 started = true
902 started = true
903 end
903 end
904 out << '</li></ul>' * (current - root)
904 out << '</li></ul>' * (current - root)
905 out << '</li></ul>'
905 out << '</li></ul>'
906 end
906 end
907 end
907 end
908 end
908 end
909
909
910 # Same as Rails' simple_format helper without using paragraphs
910 # Same as Rails' simple_format helper without using paragraphs
911 def simple_format_without_paragraph(text)
911 def simple_format_without_paragraph(text)
912 text.to_s.
912 text.to_s.
913 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
913 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
914 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
914 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
915 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
915 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
916 html_safe
916 html_safe
917 end
917 end
918
918
919 def lang_options_for_select(blank=true)
919 def lang_options_for_select(blank=true)
920 (blank ? [["(auto)", ""]] : []) +
920 (blank ? [["(auto)", ""]] : []) +
921 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
921 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
922 end
922 end
923
923
924 def label_tag_for(name, option_tags = nil, options = {})
924 def label_tag_for(name, option_tags = nil, options = {})
925 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
925 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
926 content_tag("label", label_text)
926 content_tag("label", label_text)
927 end
927 end
928
928
929 def labelled_form_for(*args, &proc)
929 def labelled_form_for(*args, &proc)
930 args << {} unless args.last.is_a?(Hash)
930 args << {} unless args.last.is_a?(Hash)
931 options = args.last
931 options = args.last
932 if args.first.is_a?(Symbol)
932 if args.first.is_a?(Symbol)
933 options.merge!(:as => args.shift)
933 options.merge!(:as => args.shift)
934 end
934 end
935 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
935 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
936 form_for(*args, &proc)
936 form_for(*args, &proc)
937 end
937 end
938
938
939 def labelled_fields_for(*args, &proc)
939 def labelled_fields_for(*args, &proc)
940 args << {} unless args.last.is_a?(Hash)
940 args << {} unless args.last.is_a?(Hash)
941 options = args.last
941 options = args.last
942 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
942 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
943 fields_for(*args, &proc)
943 fields_for(*args, &proc)
944 end
944 end
945
945
946 def labelled_remote_form_for(*args, &proc)
946 def labelled_remote_form_for(*args, &proc)
947 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
947 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
948 args << {} unless args.last.is_a?(Hash)
948 args << {} unless args.last.is_a?(Hash)
949 options = args.last
949 options = args.last
950 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
950 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
951 form_for(*args, &proc)
951 form_for(*args, &proc)
952 end
952 end
953
953
954 def error_messages_for(*objects)
954 def error_messages_for(*objects)
955 html = ""
955 html = ""
956 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
956 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
957 errors = objects.map {|o| o.errors.full_messages}.flatten
957 errors = objects.map {|o| o.errors.full_messages}.flatten
958 if errors.any?
958 if errors.any?
959 html << "<div id='errorExplanation'><ul>\n"
959 html << "<div id='errorExplanation'><ul>\n"
960 errors.each do |error|
960 errors.each do |error|
961 html << "<li>#{h error}</li>\n"
961 html << "<li>#{h error}</li>\n"
962 end
962 end
963 html << "</ul></div>\n"
963 html << "</ul></div>\n"
964 end
964 end
965 html.html_safe
965 html.html_safe
966 end
966 end
967
967
968 def delete_link(url, options={})
968 def delete_link(url, options={})
969 options = {
969 options = {
970 :method => :delete,
970 :method => :delete,
971 :data => {:confirm => l(:text_are_you_sure)},
971 :data => {:confirm => l(:text_are_you_sure)},
972 :class => 'icon icon-del'
972 :class => 'icon icon-del'
973 }.merge(options)
973 }.merge(options)
974
974
975 link_to l(:button_delete), url, options
975 link_to l(:button_delete), url, options
976 end
976 end
977
977
978 def preview_link(url, form, target='preview', options={})
978 def preview_link(url, form, target='preview', options={})
979 content_tag 'a', l(:label_preview), {
979 content_tag 'a', l(:label_preview), {
980 :href => "#",
980 :href => "#",
981 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
981 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
982 :accesskey => accesskey(:preview)
982 :accesskey => accesskey(:preview)
983 }.merge(options)
983 }.merge(options)
984 end
984 end
985
985
986 def back_url_hidden_field_tag
986 def back_url_hidden_field_tag
987 back_url = params[:back_url] || request.env['HTTP_REFERER']
987 back_url = params[:back_url] || request.env['HTTP_REFERER']
988 back_url = CGI.unescape(back_url.to_s)
988 back_url = CGI.unescape(back_url.to_s)
989 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
989 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
990 end
990 end
991
991
992 def check_all_links(form_name)
992 def check_all_links(form_name)
993 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
993 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
994 " | ".html_safe +
994 " | ".html_safe +
995 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
995 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
996 end
996 end
997
997
998 def progress_bar(pcts, options={})
998 def progress_bar(pcts, options={})
999 pcts = [pcts, pcts] unless pcts.is_a?(Array)
999 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1000 pcts = pcts.collect(&:round)
1000 pcts = pcts.collect(&:round)
1001 pcts[1] = pcts[1] - pcts[0]
1001 pcts[1] = pcts[1] - pcts[0]
1002 pcts << (100 - pcts[1] - pcts[0])
1002 pcts << (100 - pcts[1] - pcts[0])
1003 width = options[:width] || '100px;'
1003 width = options[:width] || '100px;'
1004 legend = options[:legend] || ''
1004 legend = options[:legend] || ''
1005 content_tag('table',
1005 content_tag('table',
1006 content_tag('tr',
1006 content_tag('tr',
1007 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1007 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1008 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1008 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1009 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1009 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1010 ), :class => 'progress', :style => "width: #{width};").html_safe +
1010 ), :class => 'progress', :style => "width: #{width};").html_safe +
1011 content_tag('p', legend, :class => 'pourcent').html_safe
1011 content_tag('p', legend, :class => 'pourcent').html_safe
1012 end
1012 end
1013
1013
1014 def checked_image(checked=true)
1014 def checked_image(checked=true)
1015 if checked
1015 if checked
1016 image_tag 'toggle_check.png'
1016 image_tag 'toggle_check.png'
1017 end
1017 end
1018 end
1018 end
1019
1019
1020 def context_menu(url)
1020 def context_menu(url)
1021 unless @context_menu_included
1021 unless @context_menu_included
1022 content_for :header_tags do
1022 content_for :header_tags do
1023 javascript_include_tag('context_menu') +
1023 javascript_include_tag('context_menu') +
1024 stylesheet_link_tag('context_menu')
1024 stylesheet_link_tag('context_menu')
1025 end
1025 end
1026 if l(:direction) == 'rtl'
1026 if l(:direction) == 'rtl'
1027 content_for :header_tags do
1027 content_for :header_tags do
1028 stylesheet_link_tag('context_menu_rtl')
1028 stylesheet_link_tag('context_menu_rtl')
1029 end
1029 end
1030 end
1030 end
1031 @context_menu_included = true
1031 @context_menu_included = true
1032 end
1032 end
1033 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1033 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1034 end
1034 end
1035
1035
1036 def calendar_for(field_id)
1036 def calendar_for(field_id)
1037 include_calendar_headers_tags
1037 include_calendar_headers_tags
1038 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1038 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1039 end
1039 end
1040
1040
1041 def include_calendar_headers_tags
1041 def include_calendar_headers_tags
1042 unless @calendar_headers_tags_included
1042 unless @calendar_headers_tags_included
1043 @calendar_headers_tags_included = true
1043 @calendar_headers_tags_included = true
1044 content_for :header_tags do
1044 content_for :header_tags do
1045 tags = javascript_tag("var datepickerOptions={dateFormat: 'yy-mm-dd', showOn: 'button', buttonImageOnly: true, buttonImage: '" + path_to_image('/images/calendar.png') + "'};")
1045 tags = javascript_tag("var datepickerOptions={dateFormat: 'yy-mm-dd', showOn: 'button', buttonImageOnly: true, buttonImage: '" + path_to_image('/images/calendar.png') + "'};")
1046 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1046 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1047 unless jquery_locale == 'en'
1047 unless jquery_locale == 'en'
1048 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1048 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1049 end
1049 end
1050 tags
1050 tags
1051 end
1051 end
1052 end
1052 end
1053 end
1053 end
1054
1054
1055 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1055 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1056 # Examples:
1056 # Examples:
1057 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1057 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1058 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1058 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1059 #
1059 #
1060 def stylesheet_link_tag(*sources)
1060 def stylesheet_link_tag(*sources)
1061 options = sources.last.is_a?(Hash) ? sources.pop : {}
1061 options = sources.last.is_a?(Hash) ? sources.pop : {}
1062 plugin = options.delete(:plugin)
1062 plugin = options.delete(:plugin)
1063 sources = sources.map do |source|
1063 sources = sources.map do |source|
1064 if plugin
1064 if plugin
1065 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1065 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1066 elsif current_theme && current_theme.stylesheets.include?(source)
1066 elsif current_theme && current_theme.stylesheets.include?(source)
1067 current_theme.stylesheet_path(source)
1067 current_theme.stylesheet_path(source)
1068 else
1068 else
1069 source
1069 source
1070 end
1070 end
1071 end
1071 end
1072 super sources, options
1072 super sources, options
1073 end
1073 end
1074
1074
1075 # Overrides Rails' image_tag with themes and plugins support.
1075 # Overrides Rails' image_tag with themes and plugins support.
1076 # Examples:
1076 # Examples:
1077 # image_tag('image.png') # => picks image.png from the current theme or defaults
1077 # image_tag('image.png') # => picks image.png from the current theme or defaults
1078 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1078 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1079 #
1079 #
1080 def image_tag(source, options={})
1080 def image_tag(source, options={})
1081 if plugin = options.delete(:plugin)
1081 if plugin = options.delete(:plugin)
1082 source = "/plugin_assets/#{plugin}/images/#{source}"
1082 source = "/plugin_assets/#{plugin}/images/#{source}"
1083 elsif current_theme && current_theme.images.include?(source)
1083 elsif current_theme && current_theme.images.include?(source)
1084 source = current_theme.image_path(source)
1084 source = current_theme.image_path(source)
1085 end
1085 end
1086 super source, options
1086 super source, options
1087 end
1087 end
1088
1088
1089 # Overrides Rails' javascript_include_tag with plugins support
1089 # Overrides Rails' javascript_include_tag with plugins support
1090 # Examples:
1090 # Examples:
1091 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1091 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1092 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1092 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1093 #
1093 #
1094 def javascript_include_tag(*sources)
1094 def javascript_include_tag(*sources)
1095 options = sources.last.is_a?(Hash) ? sources.pop : {}
1095 options = sources.last.is_a?(Hash) ? sources.pop : {}
1096 if plugin = options.delete(:plugin)
1096 if plugin = options.delete(:plugin)
1097 sources = sources.map do |source|
1097 sources = sources.map do |source|
1098 if plugin
1098 if plugin
1099 "/plugin_assets/#{plugin}/javascripts/#{source}"
1099 "/plugin_assets/#{plugin}/javascripts/#{source}"
1100 else
1100 else
1101 source
1101 source
1102 end
1102 end
1103 end
1103 end
1104 end
1104 end
1105 super sources, options
1105 super sources, options
1106 end
1106 end
1107
1107
1108 def content_for(name, content = nil, &block)
1108 def content_for(name, content = nil, &block)
1109 @has_content ||= {}
1109 @has_content ||= {}
1110 @has_content[name] = true
1110 @has_content[name] = true
1111 super(name, content, &block)
1111 super(name, content, &block)
1112 end
1112 end
1113
1113
1114 def has_content?(name)
1114 def has_content?(name)
1115 (@has_content && @has_content[name]) || false
1115 (@has_content && @has_content[name]) || false
1116 end
1116 end
1117
1117
1118 def sidebar_content?
1118 def sidebar_content?
1119 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1119 has_content?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1120 end
1120 end
1121
1121
1122 def view_layouts_base_sidebar_hook_response
1122 def view_layouts_base_sidebar_hook_response
1123 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1123 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1124 end
1124 end
1125
1125
1126 def email_delivery_enabled?
1126 def email_delivery_enabled?
1127 !!ActionMailer::Base.perform_deliveries
1127 !!ActionMailer::Base.perform_deliveries
1128 end
1128 end
1129
1129
1130 # Returns the avatar image tag for the given +user+ if avatars are enabled
1130 # Returns the avatar image tag for the given +user+ if avatars are enabled
1131 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1131 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1132 def avatar(user, options = { })
1132 def avatar(user, options = { })
1133 if Setting.gravatar_enabled?
1133 if Setting.gravatar_enabled?
1134 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1134 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1135 email = nil
1135 email = nil
1136 if user.respond_to?(:mail)
1136 if user.respond_to?(:mail)
1137 email = user.mail
1137 email = user.mail
1138 elsif user.to_s =~ %r{<(.+?)>}
1138 elsif user.to_s =~ %r{<(.+?)>}
1139 email = $1
1139 email = $1
1140 end
1140 end
1141 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1141 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1142 else
1142 else
1143 ''
1143 ''
1144 end
1144 end
1145 end
1145 end
1146
1146
1147 def sanitize_anchor_name(anchor)
1147 def sanitize_anchor_name(anchor)
1148 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1148 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1149 end
1149 end
1150
1150
1151 # Returns the javascript tags that are included in the html layout head
1151 # Returns the javascript tags that are included in the html layout head
1152 def javascript_heads
1152 def javascript_heads
1153 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.2', 'application')
1153 tags = javascript_include_tag('jquery-1.7.2-ui-1.8.21-ujs-2.0.2', 'application')
1154 unless User.current.pref.warn_on_leaving_unsaved == '0'
1154 unless User.current.pref.warn_on_leaving_unsaved == '0'
1155 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1155 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1156 end
1156 end
1157 tags
1157 tags
1158 end
1158 end
1159
1159
1160 def favicon
1160 def favicon
1161 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1161 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1162 end
1162 end
1163
1163
1164 def robot_exclusion_tag
1164 def robot_exclusion_tag
1165 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1165 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1166 end
1166 end
1167
1167
1168 # Returns true if arg is expected in the API response
1168 # Returns true if arg is expected in the API response
1169 def include_in_api_response?(arg)
1169 def include_in_api_response?(arg)
1170 unless @included_in_api_response
1170 unless @included_in_api_response
1171 param = params[:include]
1171 param = params[:include]
1172 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1172 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1173 @included_in_api_response.collect!(&:strip)
1173 @included_in_api_response.collect!(&:strip)
1174 end
1174 end
1175 @included_in_api_response.include?(arg.to_s)
1175 @included_in_api_response.include?(arg.to_s)
1176 end
1176 end
1177
1177
1178 # Returns options or nil if nometa param or X-Redmine-Nometa header
1178 # Returns options or nil if nometa param or X-Redmine-Nometa header
1179 # was set in the request
1179 # was set in the request
1180 def api_meta(options)
1180 def api_meta(options)
1181 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1181 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1182 # compatibility mode for activeresource clients that raise
1182 # compatibility mode for activeresource clients that raise
1183 # an error when unserializing an array with attributes
1183 # an error when unserializing an array with attributes
1184 nil
1184 nil
1185 else
1185 else
1186 options
1186 options
1187 end
1187 end
1188 end
1188 end
1189
1189
1190 private
1190 private
1191
1191
1192 def wiki_helper
1192 def wiki_helper
1193 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1193 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1194 extend helper
1194 extend helper
1195 return self
1195 return self
1196 end
1196 end
1197
1197
1198 def link_to_content_update(text, url_params = {}, html_options = {})
1198 def link_to_content_update(text, url_params = {}, html_options = {})
1199 link_to(text, url_params, html_options)
1199 link_to(text, url_params, html_options)
1200 end
1200 end
1201 end
1201 end
@@ -1,1114 +1,1116
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
19
20 class ApplicationHelperTest < ActionView::TestCase
20 class ApplicationHelperTest < ActionView::TestCase
21 include ERB::Util
21 include ERB::Util
22
22
23 fixtures :projects, :roles, :enabled_modules, :users,
23 fixtures :projects, :roles, :enabled_modules, :users,
24 :repositories, :changesets,
24 :repositories, :changesets,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
26 :wikis, :wiki_pages, :wiki_contents,
26 :wikis, :wiki_pages, :wiki_contents,
27 :boards, :messages, :news,
27 :boards, :messages, :news,
28 :attachments, :enumerations
28 :attachments, :enumerations
29
29
30 def setup
30 def setup
31 super
31 super
32 set_tmp_attachments_directory
32 set_tmp_attachments_directory
33 end
33 end
34
34
35 context "#link_to_if_authorized" do
35 context "#link_to_if_authorized" do
36 context "authorized user" do
36 context "authorized user" do
37 should "be tested"
37 should "be tested"
38 end
38 end
39
39
40 context "unauthorized user" do
40 context "unauthorized user" do
41 should "be tested"
41 should "be tested"
42 end
42 end
43
43
44 should "allow using the :controller and :action for the target link" do
44 should "allow using the :controller and :action for the target link" do
45 User.current = User.find_by_login('admin')
45 User.current = User.find_by_login('admin')
46
46
47 @project = Issue.first.project # Used by helper
47 @project = Issue.first.project # Used by helper
48 response = link_to_if_authorized("By controller/action",
48 response = link_to_if_authorized("By controller/action",
49 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
50 assert_match /href/, response
50 assert_match /href/, response
51 end
51 end
52
52
53 end
53 end
54
54
55 def test_auto_links
55 def test_auto_links
56 to_test = {
56 to_test = {
57 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
57 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
58 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
58 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
59 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
59 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
60 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
60 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
61 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
61 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
62 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
62 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
63 '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 '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>.',
64 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
64 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
65 '(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 '(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>)',
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>)',
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 '(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 '(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>).',
68 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
68 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
69 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
69 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
70 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
70 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
71 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
71 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
72 '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 '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>',
73 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
73 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
74 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
74 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
75 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
75 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
76 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
76 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
77 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
77 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
78 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
78 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
79 # two exclamation marks
79 # two exclamation marks
80 '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 '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>',
81 # escaping
81 # escaping
82 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
82 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
83 # wrap in angle brackets
83 # wrap in angle brackets
84 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
84 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
85 }
85 }
86 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
86 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
87 end
87 end
88
88
89 def test_auto_mailto
89 def test_auto_mailto
90 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
90 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
91 textilizable('test@foo.bar')
91 textilizable('test@foo.bar')
92 end
92 end
93
93
94 def test_inline_images
94 def test_inline_images
95 to_test = {
95 to_test = {
96 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
96 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
97 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
97 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
98 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
98 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
99 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
99 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
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" />',
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" />',
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;" />',
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;" />',
102 }
102 }
103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
103 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
104 end
104 end
105
105
106 def test_inline_images_inside_tags
106 def test_inline_images_inside_tags
107 raw = <<-RAW
107 raw = <<-RAW
108 h1. !foo.png! Heading
108 h1. !foo.png! Heading
109
109
110 Centered image:
110 Centered image:
111
111
112 p=. !bar.gif!
112 p=. !bar.gif!
113 RAW
113 RAW
114
114
115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
115 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
116 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
117 end
117 end
118
118
119 def test_attached_images
119 def test_attached_images
120 to_test = {
120 to_test = {
121 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
121 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
123 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
123 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
124 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
124 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
125 # link image
125 # link image
126 '!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>',
126 '!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>',
127 }
127 }
128 attachments = Attachment.find(:all)
128 attachments = Attachment.find(:all)
129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
130 end
130 end
131
131
132 def test_attached_images_filename_extension
132 def test_attached_images_filename_extension
133 set_tmp_attachments_directory
133 set_tmp_attachments_directory
134 a1 = Attachment.new(
134 a1 = Attachment.new(
135 :container => Issue.find(1),
135 :container => Issue.find(1),
136 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
136 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
137 :author => User.find(1))
137 :author => User.find(1))
138 assert a1.save
138 assert a1.save
139 assert_equal "testtest.JPG", a1.filename
139 assert_equal "testtest.JPG", a1.filename
140 assert_equal "image/jpeg", a1.content_type
140 assert_equal "image/jpeg", a1.content_type
141 assert a1.image?
141 assert a1.image?
142
142
143 a2 = Attachment.new(
143 a2 = Attachment.new(
144 :container => Issue.find(1),
144 :container => Issue.find(1),
145 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
145 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
146 :author => User.find(1))
146 :author => User.find(1))
147 assert a2.save
147 assert a2.save
148 assert_equal "testtest.jpeg", a2.filename
148 assert_equal "testtest.jpeg", a2.filename
149 assert_equal "image/jpeg", a2.content_type
149 assert_equal "image/jpeg", a2.content_type
150 assert a2.image?
150 assert a2.image?
151
151
152 a3 = Attachment.new(
152 a3 = Attachment.new(
153 :container => Issue.find(1),
153 :container => Issue.find(1),
154 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
154 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
155 :author => User.find(1))
155 :author => User.find(1))
156 assert a3.save
156 assert a3.save
157 assert_equal "testtest.JPE", a3.filename
157 assert_equal "testtest.JPE", a3.filename
158 assert_equal "image/jpeg", a3.content_type
158 assert_equal "image/jpeg", a3.content_type
159 assert a3.image?
159 assert a3.image?
160
160
161 a4 = Attachment.new(
161 a4 = Attachment.new(
162 :container => Issue.find(1),
162 :container => Issue.find(1),
163 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
163 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
164 :author => User.find(1))
164 :author => User.find(1))
165 assert a4.save
165 assert a4.save
166 assert_equal "Testtest.BMP", a4.filename
166 assert_equal "Testtest.BMP", a4.filename
167 assert_equal "image/x-ms-bmp", a4.content_type
167 assert_equal "image/x-ms-bmp", a4.content_type
168 assert a4.image?
168 assert a4.image?
169
169
170 to_test = {
170 to_test = {
171 'Inline image: !testtest.jpg!' =>
171 'Inline image: !testtest.jpg!' =>
172 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '" alt="" />',
172 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '" alt="" />',
173 'Inline image: !testtest.jpeg!' =>
173 'Inline image: !testtest.jpeg!' =>
174 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
174 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
175 'Inline image: !testtest.jpe!' =>
175 'Inline image: !testtest.jpe!' =>
176 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '" alt="" />',
176 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '" alt="" />',
177 'Inline image: !testtest.bmp!' =>
177 'Inline image: !testtest.bmp!' =>
178 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '" alt="" />',
178 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '" alt="" />',
179 }
179 }
180
180
181 attachments = [a1, a2, a3, a4]
181 attachments = [a1, a2, a3, a4]
182 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
182 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
183 end
183 end
184
184
185 def test_attached_images_should_read_later
185 def test_attached_images_should_read_later
186 set_fixtures_attachments_directory
186 set_fixtures_attachments_directory
187 a1 = Attachment.find(16)
187 a1 = Attachment.find(16)
188 assert_equal "testfile.png", a1.filename
188 assert_equal "testfile.png", a1.filename
189 assert a1.readable?
189 assert a1.readable?
190 assert (! a1.visible?(User.anonymous))
190 assert (! a1.visible?(User.anonymous))
191 assert a1.visible?(User.find(2))
191 assert a1.visible?(User.find(2))
192 a2 = Attachment.find(17)
192 a2 = Attachment.find(17)
193 assert_equal "testfile.PNG", a2.filename
193 assert_equal "testfile.PNG", a2.filename
194 assert a2.readable?
194 assert a2.readable?
195 assert (! a2.visible?(User.anonymous))
195 assert (! a2.visible?(User.anonymous))
196 assert a2.visible?(User.find(2))
196 assert a2.visible?(User.find(2))
197 assert a1.created_on < a2.created_on
197 assert a1.created_on < a2.created_on
198
198
199 to_test = {
199 to_test = {
200 'Inline image: !testfile.png!' =>
200 'Inline image: !testfile.png!' =>
201 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
201 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
202 'Inline image: !Testfile.PNG!' =>
202 'Inline image: !Testfile.PNG!' =>
203 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
203 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
204 }
204 }
205 attachments = [a1, a2]
205 attachments = [a1, a2]
206 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
206 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
207 set_tmp_attachments_directory
207 set_tmp_attachments_directory
208 end
208 end
209
209
210 def test_textile_external_links
210 def test_textile_external_links
211 to_test = {
211 to_test = {
212 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
212 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
213 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
213 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
214 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
214 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
215 '"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>',
215 '"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>',
216 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
216 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
217 # no multiline link text
217 # no multiline link text
218 "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",
218 "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",
219 # mailto link
219 # mailto link
220 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
220 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
221 # two exclamation marks
221 # two exclamation marks
222 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
222 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
223 # escaping
223 # escaping
224 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
224 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
225 }
225 }
226 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
226 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
227 end
227 end
228
228
229 def test_redmine_links
229 def test_redmine_links
230 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
230 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
231 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
231 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
232 note_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
232 note_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
233 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
233 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
234
234
235 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
235 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
236 :class => 'changeset', :title => 'My very first commit')
236 :class => 'changeset', :title => 'My very first commit')
237 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
237 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
238 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
238 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
239
239
240 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
240 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
241 :class => 'document')
241 :class => 'document')
242
242
243 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
243 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
244 :class => 'version')
244 :class => 'version')
245
245
246 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
246 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
247
247
248 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
248 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
249
249
250 news_url = {:controller => 'news', :action => 'show', :id => 1}
250 news_url = {:controller => 'news', :action => 'show', :id => 1}
251
251
252 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
252 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
253
253
254 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
254 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
255 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
255 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
256
256
257 to_test = {
257 to_test = {
258 # tickets
258 # tickets
259 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
259 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
260 # ticket notes
260 # ticket notes
261 '#3-14' => note_link,
261 '#3-14' => note_link,
262 '#3#note-14' => note_link,
262 '#3#note-14' => note_link,
263 # should not ignore leading zero
264 '#03' => '#03',
263 # changesets
265 # changesets
264 'r1' => changeset_link,
266 'r1' => changeset_link,
265 'r1.' => "#{changeset_link}.",
267 'r1.' => "#{changeset_link}.",
266 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
268 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
267 'r1,r2' => "#{changeset_link},#{changeset_link2}",
269 'r1,r2' => "#{changeset_link},#{changeset_link2}",
268 # documents
270 # documents
269 'document#1' => document_link,
271 'document#1' => document_link,
270 'document:"Test document"' => document_link,
272 'document:"Test document"' => document_link,
271 # versions
273 # versions
272 'version#2' => version_link,
274 'version#2' => version_link,
273 'version:1.0' => version_link,
275 'version:1.0' => version_link,
274 'version:"1.0"' => version_link,
276 'version:"1.0"' => version_link,
275 # source
277 # source
276 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
278 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
277 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
279 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
278 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
280 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
279 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
281 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
280 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
282 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
281 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
283 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
282 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
284 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
283 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
285 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
284 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
286 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
285 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
287 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
286 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
288 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
287 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
289 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
288 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
290 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
289 # forum
291 # forum
290 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
292 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
291 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
293 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
292 # message
294 # message
293 'message#4' => link_to('Post 2', message_url, :class => 'message'),
295 'message#4' => link_to('Post 2', message_url, :class => 'message'),
294 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
296 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
295 # news
297 # news
296 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
298 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
297 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
299 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
298 # project
300 # project
299 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
301 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
300 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
302 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
301 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
303 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
302 # not found
304 # not found
303 '#0123456789' => '#0123456789',
305 '#0123456789' => '#0123456789',
304 # invalid expressions
306 # invalid expressions
305 'source:' => 'source:',
307 'source:' => 'source:',
306 # url hash
308 # url hash
307 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
309 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
308 }
310 }
309 @project = Project.find(1)
311 @project = Project.find(1)
310 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
312 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
311 end
313 end
312
314
313 def test_escaped_redmine_links_should_not_be_parsed
315 def test_escaped_redmine_links_should_not_be_parsed
314 to_test = [
316 to_test = [
315 '#3.',
317 '#3.',
316 '#3-14.',
318 '#3-14.',
317 '#3#-note14.',
319 '#3#-note14.',
318 'r1',
320 'r1',
319 'document#1',
321 'document#1',
320 'document:"Test document"',
322 'document:"Test document"',
321 'version#2',
323 'version#2',
322 'version:1.0',
324 'version:1.0',
323 'version:"1.0"',
325 'version:"1.0"',
324 'source:/some/file'
326 'source:/some/file'
325 ]
327 ]
326 @project = Project.find(1)
328 @project = Project.find(1)
327 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
329 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
328 end
330 end
329
331
330 def test_cross_project_redmine_links
332 def test_cross_project_redmine_links
331 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
333 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
332 :class => 'source')
334 :class => 'source')
333
335
334 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
336 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
335 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
337 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
336
338
337 to_test = {
339 to_test = {
338 # documents
340 # documents
339 'document:"Test document"' => 'document:"Test document"',
341 'document:"Test document"' => 'document:"Test document"',
340 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
342 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
341 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
343 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
342 # versions
344 # versions
343 'version:"1.0"' => 'version:"1.0"',
345 'version:"1.0"' => 'version:"1.0"',
344 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
346 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
345 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
347 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
346 # changeset
348 # changeset
347 'r2' => 'r2',
349 'r2' => 'r2',
348 'ecookbook:r2' => changeset_link,
350 'ecookbook:r2' => changeset_link,
349 'invalid:r2' => 'invalid:r2',
351 'invalid:r2' => 'invalid:r2',
350 # source
352 # source
351 'source:/some/file' => 'source:/some/file',
353 'source:/some/file' => 'source:/some/file',
352 'ecookbook:source:/some/file' => source_link,
354 'ecookbook:source:/some/file' => source_link,
353 'invalid:source:/some/file' => 'invalid:source:/some/file',
355 'invalid:source:/some/file' => 'invalid:source:/some/file',
354 }
356 }
355 @project = Project.find(3)
357 @project = Project.find(3)
356 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
358 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
357 end
359 end
358
360
359 def test_multiple_repositories_redmine_links
361 def test_multiple_repositories_redmine_links
360 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
362 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
361 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
363 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
362 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
364 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
363 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
365 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
364
366
365 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
367 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
366 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
368 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
367 svn_changeset_link = link_to('svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
369 svn_changeset_link = link_to('svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
368 :class => 'changeset', :title => '')
370 :class => 'changeset', :title => '')
369 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
371 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
370 :class => 'changeset', :title => '')
372 :class => 'changeset', :title => '')
371
373
372 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
374 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
373 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
375 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
374
376
375 to_test = {
377 to_test = {
376 'r2' => changeset_link,
378 'r2' => changeset_link,
377 'svn1|r123' => svn_changeset_link,
379 'svn1|r123' => svn_changeset_link,
378 'invalid|r123' => 'invalid|r123',
380 'invalid|r123' => 'invalid|r123',
379 'commit:hg1|abcd' => hg_changeset_link,
381 'commit:hg1|abcd' => hg_changeset_link,
380 'commit:invalid|abcd' => 'commit:invalid|abcd',
382 'commit:invalid|abcd' => 'commit:invalid|abcd',
381 # source
383 # source
382 'source:some/file' => source_link,
384 'source:some/file' => source_link,
383 'source:hg1|some/file' => hg_source_link,
385 'source:hg1|some/file' => hg_source_link,
384 'source:invalid|some/file' => 'source:invalid|some/file',
386 'source:invalid|some/file' => 'source:invalid|some/file',
385 }
387 }
386
388
387 @project = Project.find(1)
389 @project = Project.find(1)
388 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
390 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
389 end
391 end
390
392
391 def test_cross_project_multiple_repositories_redmine_links
393 def test_cross_project_multiple_repositories_redmine_links
392 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
394 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
393 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
395 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
394 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
396 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
395 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
397 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
396
398
397 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
399 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
398 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
400 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
399 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
401 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
400 :class => 'changeset', :title => '')
402 :class => 'changeset', :title => '')
401 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
403 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
402 :class => 'changeset', :title => '')
404 :class => 'changeset', :title => '')
403
405
404 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
406 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
405 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
407 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
406
408
407 to_test = {
409 to_test = {
408 'ecookbook:r2' => changeset_link,
410 'ecookbook:r2' => changeset_link,
409 'ecookbook:svn1|r123' => svn_changeset_link,
411 'ecookbook:svn1|r123' => svn_changeset_link,
410 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
412 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
411 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
413 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
412 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
414 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
413 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
415 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
414 # source
416 # source
415 'ecookbook:source:some/file' => source_link,
417 'ecookbook:source:some/file' => source_link,
416 'ecookbook:source:hg1|some/file' => hg_source_link,
418 'ecookbook:source:hg1|some/file' => hg_source_link,
417 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
419 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
418 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
420 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
419 }
421 }
420
422
421 @project = Project.find(3)
423 @project = Project.find(3)
422 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
424 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
423 end
425 end
424
426
425 def test_redmine_links_git_commit
427 def test_redmine_links_git_commit
426 changeset_link = link_to('abcd',
428 changeset_link = link_to('abcd',
427 {
429 {
428 :controller => 'repositories',
430 :controller => 'repositories',
429 :action => 'revision',
431 :action => 'revision',
430 :id => 'subproject1',
432 :id => 'subproject1',
431 :rev => 'abcd',
433 :rev => 'abcd',
432 },
434 },
433 :class => 'changeset', :title => 'test commit')
435 :class => 'changeset', :title => 'test commit')
434 to_test = {
436 to_test = {
435 'commit:abcd' => changeset_link,
437 'commit:abcd' => changeset_link,
436 }
438 }
437 @project = Project.find(3)
439 @project = Project.find(3)
438 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
440 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
439 assert r
441 assert r
440 c = Changeset.new(:repository => r,
442 c = Changeset.new(:repository => r,
441 :committed_on => Time.now,
443 :committed_on => Time.now,
442 :revision => 'abcd',
444 :revision => 'abcd',
443 :scmid => 'abcd',
445 :scmid => 'abcd',
444 :comments => 'test commit')
446 :comments => 'test commit')
445 assert( c.save )
447 assert( c.save )
446 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
448 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
447 end
449 end
448
450
449 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
451 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
450 def test_redmine_links_darcs_commit
452 def test_redmine_links_darcs_commit
451 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
453 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
452 {
454 {
453 :controller => 'repositories',
455 :controller => 'repositories',
454 :action => 'revision',
456 :action => 'revision',
455 :id => 'subproject1',
457 :id => 'subproject1',
456 :rev => '123',
458 :rev => '123',
457 },
459 },
458 :class => 'changeset', :title => 'test commit')
460 :class => 'changeset', :title => 'test commit')
459 to_test = {
461 to_test = {
460 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
462 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
461 }
463 }
462 @project = Project.find(3)
464 @project = Project.find(3)
463 r = Repository::Darcs.create!(
465 r = Repository::Darcs.create!(
464 :project => @project, :url => '/tmp/test/darcs',
466 :project => @project, :url => '/tmp/test/darcs',
465 :log_encoding => 'UTF-8')
467 :log_encoding => 'UTF-8')
466 assert r
468 assert r
467 c = Changeset.new(:repository => r,
469 c = Changeset.new(:repository => r,
468 :committed_on => Time.now,
470 :committed_on => Time.now,
469 :revision => '123',
471 :revision => '123',
470 :scmid => '20080308225258-98289-abcd456efg.gz',
472 :scmid => '20080308225258-98289-abcd456efg.gz',
471 :comments => 'test commit')
473 :comments => 'test commit')
472 assert( c.save )
474 assert( c.save )
473 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
475 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
474 end
476 end
475
477
476 def test_redmine_links_mercurial_commit
478 def test_redmine_links_mercurial_commit
477 changeset_link_rev = link_to('r123',
479 changeset_link_rev = link_to('r123',
478 {
480 {
479 :controller => 'repositories',
481 :controller => 'repositories',
480 :action => 'revision',
482 :action => 'revision',
481 :id => 'subproject1',
483 :id => 'subproject1',
482 :rev => '123' ,
484 :rev => '123' ,
483 },
485 },
484 :class => 'changeset', :title => 'test commit')
486 :class => 'changeset', :title => 'test commit')
485 changeset_link_commit = link_to('abcd',
487 changeset_link_commit = link_to('abcd',
486 {
488 {
487 :controller => 'repositories',
489 :controller => 'repositories',
488 :action => 'revision',
490 :action => 'revision',
489 :id => 'subproject1',
491 :id => 'subproject1',
490 :rev => 'abcd' ,
492 :rev => 'abcd' ,
491 },
493 },
492 :class => 'changeset', :title => 'test commit')
494 :class => 'changeset', :title => 'test commit')
493 to_test = {
495 to_test = {
494 'r123' => changeset_link_rev,
496 'r123' => changeset_link_rev,
495 'commit:abcd' => changeset_link_commit,
497 'commit:abcd' => changeset_link_commit,
496 }
498 }
497 @project = Project.find(3)
499 @project = Project.find(3)
498 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
500 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
499 assert r
501 assert r
500 c = Changeset.new(:repository => r,
502 c = Changeset.new(:repository => r,
501 :committed_on => Time.now,
503 :committed_on => Time.now,
502 :revision => '123',
504 :revision => '123',
503 :scmid => 'abcd',
505 :scmid => 'abcd',
504 :comments => 'test commit')
506 :comments => 'test commit')
505 assert( c.save )
507 assert( c.save )
506 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
508 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
507 end
509 end
508
510
509 def test_attachment_links
511 def test_attachment_links
510 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
512 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
511 to_test = {
513 to_test = {
512 'attachment:error281.txt' => attachment_link
514 'attachment:error281.txt' => attachment_link
513 }
515 }
514 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
516 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
515 end
517 end
516
518
517 def test_wiki_links
519 def test_wiki_links
518 to_test = {
520 to_test = {
519 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
521 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
520 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
522 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
521 # title content should be formatted
523 # title content should be formatted
522 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
524 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
523 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;</a>',
525 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;</a>',
524 # link with anchor
526 # link with anchor
525 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
527 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
526 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
528 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
527 # page that doesn't exist
529 # page that doesn't exist
528 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
530 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
529 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
531 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
530 # link to another project wiki
532 # link to another project wiki
531 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
533 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
532 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
534 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
533 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
535 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
534 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
536 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
535 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
537 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
536 # striked through link
538 # striked through link
537 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
539 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
538 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
540 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
539 # escaping
541 # escaping
540 '![[Another page|Page]]' => '[[Another page|Page]]',
542 '![[Another page|Page]]' => '[[Another page|Page]]',
541 # project does not exist
543 # project does not exist
542 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
544 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
543 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
545 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
544 }
546 }
545
547
546 @project = Project.find(1)
548 @project = Project.find(1)
547 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
549 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
548 end
550 end
549
551
550 def test_wiki_links_within_local_file_generation_context
552 def test_wiki_links_within_local_file_generation_context
551
553
552 to_test = {
554 to_test = {
553 # link to a page
555 # link to a page
554 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
556 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
555 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
557 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
556 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
558 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
557 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
559 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
558 # page that doesn't exist
560 # page that doesn't exist
559 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
561 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
560 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
562 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
561 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
563 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
562 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
564 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
563 }
565 }
564
566
565 @project = Project.find(1)
567 @project = Project.find(1)
566
568
567 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
569 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
568 end
570 end
569
571
570 def test_wiki_links_within_wiki_page_context
572 def test_wiki_links_within_wiki_page_context
571
573
572 page = WikiPage.find_by_title('Another_page' )
574 page = WikiPage.find_by_title('Another_page' )
573
575
574 to_test = {
576 to_test = {
575 # link to another page
577 # link to another page
576 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
578 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
577 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
579 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
578 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
580 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
579 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
581 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
580 # link to the current page
582 # link to the current page
581 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
583 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
582 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
584 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
583 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
585 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
584 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
586 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
585 # page that doesn't exist
587 # page that doesn't exist
586 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">Unknown page</a>',
588 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">Unknown page</a>',
587 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">404</a>',
589 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">404</a>',
588 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">Unknown page</a>',
590 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">Unknown page</a>',
589 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">404</a>',
591 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">404</a>',
590 }
592 }
591
593
592 @project = Project.find(1)
594 @project = Project.find(1)
593
595
594 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.new( :text => text, :page => page ), :text) }
596 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.new( :text => text, :page => page ), :text) }
595 end
597 end
596
598
597 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
599 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
598
600
599 to_test = {
601 to_test = {
600 # link to a page
602 # link to a page
601 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
603 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
602 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
604 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
603 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
605 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
604 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
606 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
605 # page that doesn't exist
607 # page that doesn't exist
606 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
608 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
607 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
609 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
608 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
610 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
609 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
611 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
610 }
612 }
611
613
612 @project = Project.find(1)
614 @project = Project.find(1)
613
615
614 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
616 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
615 end
617 end
616
618
617 def test_html_tags
619 def test_html_tags
618 to_test = {
620 to_test = {
619 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
621 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
620 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
622 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
621 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
623 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
622 # do not escape pre/code tags
624 # do not escape pre/code tags
623 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
625 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
624 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
626 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
625 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
627 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
626 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
628 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
627 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
629 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
628 # remove attributes except class
630 # remove attributes except class
629 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
631 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
630 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
632 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
631 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
633 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
632 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
634 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
633 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
635 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
634 # xss
636 # xss
635 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
637 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
636 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
638 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
637 }
639 }
638 to_test.each { |text, result| assert_equal result, textilizable(text) }
640 to_test.each { |text, result| assert_equal result, textilizable(text) }
639 end
641 end
640
642
641 def test_allowed_html_tags
643 def test_allowed_html_tags
642 to_test = {
644 to_test = {
643 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
645 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
644 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
646 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
645 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
647 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
646 }
648 }
647 to_test.each { |text, result| assert_equal result, textilizable(text) }
649 to_test.each { |text, result| assert_equal result, textilizable(text) }
648 end
650 end
649
651
650 def test_pre_tags
652 def test_pre_tags
651 raw = <<-RAW
653 raw = <<-RAW
652 Before
654 Before
653
655
654 <pre>
656 <pre>
655 <prepared-statement-cache-size>32</prepared-statement-cache-size>
657 <prepared-statement-cache-size>32</prepared-statement-cache-size>
656 </pre>
658 </pre>
657
659
658 After
660 After
659 RAW
661 RAW
660
662
661 expected = <<-EXPECTED
663 expected = <<-EXPECTED
662 <p>Before</p>
664 <p>Before</p>
663 <pre>
665 <pre>
664 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
666 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
665 </pre>
667 </pre>
666 <p>After</p>
668 <p>After</p>
667 EXPECTED
669 EXPECTED
668
670
669 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
671 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
670 end
672 end
671
673
672 def test_pre_content_should_not_parse_wiki_and_redmine_links
674 def test_pre_content_should_not_parse_wiki_and_redmine_links
673 raw = <<-RAW
675 raw = <<-RAW
674 [[CookBook documentation]]
676 [[CookBook documentation]]
675
677
676 #1
678 #1
677
679
678 <pre>
680 <pre>
679 [[CookBook documentation]]
681 [[CookBook documentation]]
680
682
681 #1
683 #1
682 </pre>
684 </pre>
683 RAW
685 RAW
684
686
685 expected = <<-EXPECTED
687 expected = <<-EXPECTED
686 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
688 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
687 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
689 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
688 <pre>
690 <pre>
689 [[CookBook documentation]]
691 [[CookBook documentation]]
690
692
691 #1
693 #1
692 </pre>
694 </pre>
693 EXPECTED
695 EXPECTED
694
696
695 @project = Project.find(1)
697 @project = Project.find(1)
696 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
698 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
697 end
699 end
698
700
699 def test_non_closing_pre_blocks_should_be_closed
701 def test_non_closing_pre_blocks_should_be_closed
700 raw = <<-RAW
702 raw = <<-RAW
701 <pre><code>
703 <pre><code>
702 RAW
704 RAW
703
705
704 expected = <<-EXPECTED
706 expected = <<-EXPECTED
705 <pre><code>
707 <pre><code>
706 </code></pre>
708 </code></pre>
707 EXPECTED
709 EXPECTED
708
710
709 @project = Project.find(1)
711 @project = Project.find(1)
710 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
712 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
711 end
713 end
712
714
713 def test_syntax_highlight
715 def test_syntax_highlight
714 raw = <<-RAW
716 raw = <<-RAW
715 <pre><code class="ruby">
717 <pre><code class="ruby">
716 # Some ruby code here
718 # Some ruby code here
717 </code></pre>
719 </code></pre>
718 RAW
720 RAW
719
721
720 expected = <<-EXPECTED
722 expected = <<-EXPECTED
721 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
723 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
722 </code></pre>
724 </code></pre>
723 EXPECTED
725 EXPECTED
724
726
725 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
727 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
726 end
728 end
727
729
728 def test_to_path_param
730 def test_to_path_param
729 assert_equal 'test1/test2', to_path_param('test1/test2')
731 assert_equal 'test1/test2', to_path_param('test1/test2')
730 assert_equal 'test1/test2', to_path_param('/test1/test2/')
732 assert_equal 'test1/test2', to_path_param('/test1/test2/')
731 assert_equal 'test1/test2', to_path_param('//test1/test2/')
733 assert_equal 'test1/test2', to_path_param('//test1/test2/')
732 assert_equal nil, to_path_param('/')
734 assert_equal nil, to_path_param('/')
733 end
735 end
734
736
735 def test_wiki_links_in_tables
737 def test_wiki_links_in_tables
736 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
738 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
737 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
739 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
738 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
740 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
739 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
741 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
740 }
742 }
741 @project = Project.find(1)
743 @project = Project.find(1)
742 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
744 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
743 end
745 end
744
746
745 def test_text_formatting
747 def test_text_formatting
746 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
748 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
747 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
749 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
748 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
750 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
749 '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>',
751 '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>',
750 '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',
752 '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',
751 }
753 }
752 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
754 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
753 end
755 end
754
756
755 def test_wiki_horizontal_rule
757 def test_wiki_horizontal_rule
756 assert_equal '<hr />', textilizable('---')
758 assert_equal '<hr />', textilizable('---')
757 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
759 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
758 end
760 end
759
761
760 def test_footnotes
762 def test_footnotes
761 raw = <<-RAW
763 raw = <<-RAW
762 This is some text[1].
764 This is some text[1].
763
765
764 fn1. This is the foot note
766 fn1. This is the foot note
765 RAW
767 RAW
766
768
767 expected = <<-EXPECTED
769 expected = <<-EXPECTED
768 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
770 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
769 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
771 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
770 EXPECTED
772 EXPECTED
771
773
772 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
774 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
773 end
775 end
774
776
775 def test_headings
777 def test_headings
776 raw = 'h1. Some heading'
778 raw = 'h1. Some heading'
777 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
779 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
778
780
779 assert_equal expected, textilizable(raw)
781 assert_equal expected, textilizable(raw)
780 end
782 end
781
783
782 def test_headings_with_special_chars
784 def test_headings_with_special_chars
783 # This test makes sure that the generated anchor names match the expected
785 # This test makes sure that the generated anchor names match the expected
784 # ones even if the heading text contains unconventional characters
786 # ones even if the heading text contains unconventional characters
785 raw = 'h1. Some heading related to version 0.5'
787 raw = 'h1. Some heading related to version 0.5'
786 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
788 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
787 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
789 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
788
790
789 assert_equal expected, textilizable(raw)
791 assert_equal expected, textilizable(raw)
790 end
792 end
791
793
792 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
794 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
793 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
795 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
794 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
796 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
795
797
796 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
798 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
797
799
798 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
800 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
799 end
801 end
800
802
801 def test_table_of_content
803 def test_table_of_content
802 raw = <<-RAW
804 raw = <<-RAW
803 {{toc}}
805 {{toc}}
804
806
805 h1. Title
807 h1. Title
806
808
807 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
809 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
808
810
809 h2. Subtitle with a [[Wiki]] link
811 h2. Subtitle with a [[Wiki]] link
810
812
811 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
813 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
812
814
813 h2. Subtitle with [[Wiki|another Wiki]] link
815 h2. Subtitle with [[Wiki|another Wiki]] link
814
816
815 h2. Subtitle with %{color:red}red text%
817 h2. Subtitle with %{color:red}red text%
816
818
817 <pre>
819 <pre>
818 some code
820 some code
819 </pre>
821 </pre>
820
822
821 h3. Subtitle with *some* _modifiers_
823 h3. Subtitle with *some* _modifiers_
822
824
823 h3. Subtitle with @inline code@
825 h3. Subtitle with @inline code@
824
826
825 h1. Another title
827 h1. Another title
826
828
827 h3. An "Internet link":http://www.redmine.org/ inside subtitle
829 h3. An "Internet link":http://www.redmine.org/ inside subtitle
828
830
829 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
831 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
830
832
831 RAW
833 RAW
832
834
833 expected = '<ul class="toc">' +
835 expected = '<ul class="toc">' +
834 '<li><a href="#Title">Title</a>' +
836 '<li><a href="#Title">Title</a>' +
835 '<ul>' +
837 '<ul>' +
836 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
838 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
837 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
839 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
838 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
840 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
839 '<ul>' +
841 '<ul>' +
840 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
842 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
841 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
843 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
842 '</ul>' +
844 '</ul>' +
843 '</li>' +
845 '</li>' +
844 '</ul>' +
846 '</ul>' +
845 '</li>' +
847 '</li>' +
846 '<li><a href="#Another-title">Another title</a>' +
848 '<li><a href="#Another-title">Another title</a>' +
847 '<ul>' +
849 '<ul>' +
848 '<li>' +
850 '<li>' +
849 '<ul>' +
851 '<ul>' +
850 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
852 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
851 '</ul>' +
853 '</ul>' +
852 '</li>' +
854 '</li>' +
853 '<li><a href="#Project-Name">Project Name</a></li>' +
855 '<li><a href="#Project-Name">Project Name</a></li>' +
854 '</ul>' +
856 '</ul>' +
855 '</li>' +
857 '</li>' +
856 '</ul>'
858 '</ul>'
857
859
858 @project = Project.find(1)
860 @project = Project.find(1)
859 assert textilizable(raw).gsub("\n", "").include?(expected)
861 assert textilizable(raw).gsub("\n", "").include?(expected)
860 end
862 end
861
863
862 def test_table_of_content_should_generate_unique_anchors
864 def test_table_of_content_should_generate_unique_anchors
863 raw = <<-RAW
865 raw = <<-RAW
864 {{toc}}
866 {{toc}}
865
867
866 h1. Title
868 h1. Title
867
869
868 h2. Subtitle
870 h2. Subtitle
869
871
870 h2. Subtitle
872 h2. Subtitle
871 RAW
873 RAW
872
874
873 expected = '<ul class="toc">' +
875 expected = '<ul class="toc">' +
874 '<li><a href="#Title">Title</a>' +
876 '<li><a href="#Title">Title</a>' +
875 '<ul>' +
877 '<ul>' +
876 '<li><a href="#Subtitle">Subtitle</a></li>' +
878 '<li><a href="#Subtitle">Subtitle</a></li>' +
877 '<li><a href="#Subtitle-2">Subtitle</a></li>'
879 '<li><a href="#Subtitle-2">Subtitle</a></li>'
878 '</ul>'
880 '</ul>'
879 '</li>' +
881 '</li>' +
880 '</ul>'
882 '</ul>'
881
883
882 @project = Project.find(1)
884 @project = Project.find(1)
883 result = textilizable(raw).gsub("\n", "")
885 result = textilizable(raw).gsub("\n", "")
884 assert_include expected, result
886 assert_include expected, result
885 assert_include '<a name="Subtitle">', result
887 assert_include '<a name="Subtitle">', result
886 assert_include '<a name="Subtitle-2">', result
888 assert_include '<a name="Subtitle-2">', result
887 end
889 end
888
890
889 def test_table_of_content_should_contain_included_page_headings
891 def test_table_of_content_should_contain_included_page_headings
890 raw = <<-RAW
892 raw = <<-RAW
891 {{toc}}
893 {{toc}}
892
894
893 h1. Included
895 h1. Included
894
896
895 {{include(Child_1)}}
897 {{include(Child_1)}}
896 RAW
898 RAW
897
899
898 expected = '<ul class="toc">' +
900 expected = '<ul class="toc">' +
899 '<li><a href="#Included">Included</a></li>' +
901 '<li><a href="#Included">Included</a></li>' +
900 '<li><a href="#Child-page-1">Child page 1</a></li>' +
902 '<li><a href="#Child-page-1">Child page 1</a></li>' +
901 '</ul>'
903 '</ul>'
902
904
903 @project = Project.find(1)
905 @project = Project.find(1)
904 assert textilizable(raw).gsub("\n", "").include?(expected)
906 assert textilizable(raw).gsub("\n", "").include?(expected)
905 end
907 end
906
908
907 def test_section_edit_links
909 def test_section_edit_links
908 raw = <<-RAW
910 raw = <<-RAW
909 h1. Title
911 h1. Title
910
912
911 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
913 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
912
914
913 h2. Subtitle with a [[Wiki]] link
915 h2. Subtitle with a [[Wiki]] link
914
916
915 h2. Subtitle with *some* _modifiers_
917 h2. Subtitle with *some* _modifiers_
916
918
917 h2. Subtitle with @inline code@
919 h2. Subtitle with @inline code@
918
920
919 <pre>
921 <pre>
920 some code
922 some code
921
923
922 h2. heading inside pre
924 h2. heading inside pre
923
925
924 <h2>html heading inside pre</h2>
926 <h2>html heading inside pre</h2>
925 </pre>
927 </pre>
926
928
927 h2. Subtitle after pre tag
929 h2. Subtitle after pre tag
928 RAW
930 RAW
929
931
930 @project = Project.find(1)
932 @project = Project.find(1)
931 set_language_if_valid 'en'
933 set_language_if_valid 'en'
932 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
934 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
933
935
934 # heading that contains inline code
936 # heading that contains inline code
935 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
937 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
936 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
938 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
937 '<a name="Subtitle-with-inline-code"></a>' +
939 '<a name="Subtitle-with-inline-code"></a>' +
938 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
940 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
939 result
941 result
940
942
941 # last heading
943 # last heading
942 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
944 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
943 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
945 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
944 '<a name="Subtitle-after-pre-tag"></a>' +
946 '<a name="Subtitle-after-pre-tag"></a>' +
945 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
947 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
946 result
948 result
947 end
949 end
948
950
949 def test_default_formatter
951 def test_default_formatter
950 with_settings :text_formatting => 'unknown' do
952 with_settings :text_formatting => 'unknown' do
951 text = 'a *link*: http://www.example.net/'
953 text = 'a *link*: http://www.example.net/'
952 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
954 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
953 end
955 end
954 end
956 end
955
957
956 def test_due_date_distance_in_words
958 def test_due_date_distance_in_words
957 to_test = { Date.today => 'Due in 0 days',
959 to_test = { Date.today => 'Due in 0 days',
958 Date.today + 1 => 'Due in 1 day',
960 Date.today + 1 => 'Due in 1 day',
959 Date.today + 100 => 'Due in about 3 months',
961 Date.today + 100 => 'Due in about 3 months',
960 Date.today + 20000 => 'Due in over 54 years',
962 Date.today + 20000 => 'Due in over 54 years',
961 Date.today - 1 => '1 day late',
963 Date.today - 1 => '1 day late',
962 Date.today - 100 => 'about 3 months late',
964 Date.today - 100 => 'about 3 months late',
963 Date.today - 20000 => 'over 54 years late',
965 Date.today - 20000 => 'over 54 years late',
964 }
966 }
965 ::I18n.locale = :en
967 ::I18n.locale = :en
966 to_test.each do |date, expected|
968 to_test.each do |date, expected|
967 assert_equal expected, due_date_distance_in_words(date)
969 assert_equal expected, due_date_distance_in_words(date)
968 end
970 end
969 end
971 end
970
972
971 def test_avatar
973 def test_avatar
972 # turn on avatars
974 # turn on avatars
973 Setting.gravatar_enabled = '1'
975 Setting.gravatar_enabled = '1'
974 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
976 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
975 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
977 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
976 # Default size is 50
978 # Default size is 50
977 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
979 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
978 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
980 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
979 # Non-avatar options should be considered html options
981 # Non-avatar options should be considered html options
980 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
982 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
981 # The default class of the img tag should be gravatar
983 # The default class of the img tag should be gravatar
982 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
984 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
983 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
985 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
984 assert_nil avatar('jsmith')
986 assert_nil avatar('jsmith')
985 assert_nil avatar(nil)
987 assert_nil avatar(nil)
986
988
987 # turn off avatars
989 # turn off avatars
988 Setting.gravatar_enabled = '0'
990 Setting.gravatar_enabled = '0'
989 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
991 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
990 end
992 end
991
993
992 def test_link_to_user
994 def test_link_to_user
993 user = User.find(2)
995 user = User.find(2)
994 t = link_to_user(user)
996 t = link_to_user(user)
995 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
997 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
996 end
998 end
997
999
998 def test_link_to_user_should_not_link_to_locked_user
1000 def test_link_to_user_should_not_link_to_locked_user
999 user = User.find(5)
1001 user = User.find(5)
1000 assert user.locked?
1002 assert user.locked?
1001 t = link_to_user(user)
1003 t = link_to_user(user)
1002 assert_equal user.name, t
1004 assert_equal user.name, t
1003 end
1005 end
1004
1006
1005 def test_link_to_user_should_not_link_to_anonymous
1007 def test_link_to_user_should_not_link_to_anonymous
1006 user = User.anonymous
1008 user = User.anonymous
1007 assert user.anonymous?
1009 assert user.anonymous?
1008 t = link_to_user(user)
1010 t = link_to_user(user)
1009 assert_equal ::I18n.t(:label_user_anonymous), t
1011 assert_equal ::I18n.t(:label_user_anonymous), t
1010 end
1012 end
1011
1013
1012 def test_link_to_project
1014 def test_link_to_project
1013 project = Project.find(1)
1015 project = Project.find(1)
1014 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1016 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1015 link_to_project(project)
1017 link_to_project(project)
1016 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1018 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1017 link_to_project(project, :action => 'settings')
1019 link_to_project(project, :action => 'settings')
1018 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1020 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1019 link_to_project(project, {:only_path => false, :jump => 'blah'})
1021 link_to_project(project, {:only_path => false, :jump => 'blah'})
1020 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
1022 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
1021 link_to_project(project, {:action => 'settings'}, :class => "project")
1023 link_to_project(project, {:action => 'settings'}, :class => "project")
1022 end
1024 end
1023
1025
1024 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1026 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1025 # numeric identifier are no longer allowed
1027 # numeric identifier are no longer allowed
1026 Project.update_all "identifier=25", "id=1"
1028 Project.update_all "identifier=25", "id=1"
1027
1029
1028 assert_equal '<a href="/projects/1">eCookbook</a>',
1030 assert_equal '<a href="/projects/1">eCookbook</a>',
1029 link_to_project(Project.find(1))
1031 link_to_project(Project.find(1))
1030 end
1032 end
1031
1033
1032 def test_principals_options_for_select_with_users
1034 def test_principals_options_for_select_with_users
1033 User.current = nil
1035 User.current = nil
1034 users = [User.find(2), User.find(4)]
1036 users = [User.find(2), User.find(4)]
1035 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1037 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1036 principals_options_for_select(users)
1038 principals_options_for_select(users)
1037 end
1039 end
1038
1040
1039 def test_principals_options_for_select_with_selected
1041 def test_principals_options_for_select_with_selected
1040 User.current = nil
1042 User.current = nil
1041 users = [User.find(2), User.find(4)]
1043 users = [User.find(2), User.find(4)]
1042 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1044 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1043 principals_options_for_select(users, User.find(4))
1045 principals_options_for_select(users, User.find(4))
1044 end
1046 end
1045
1047
1046 def test_principals_options_for_select_with_users_and_groups
1048 def test_principals_options_for_select_with_users_and_groups
1047 User.current = nil
1049 User.current = nil
1048 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1050 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1049 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1051 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1050 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1052 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1051 principals_options_for_select(users)
1053 principals_options_for_select(users)
1052 end
1054 end
1053
1055
1054 def test_principals_options_for_select_with_empty_collection
1056 def test_principals_options_for_select_with_empty_collection
1055 assert_equal '', principals_options_for_select([])
1057 assert_equal '', principals_options_for_select([])
1056 end
1058 end
1057
1059
1058 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1060 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1059 users = [User.find(2), User.find(4)]
1061 users = [User.find(2), User.find(4)]
1060 User.current = User.find(4)
1062 User.current = User.find(4)
1061 assert_include '<option value="4"><< me >></option>', principals_options_for_select(users)
1063 assert_include '<option value="4"><< me >></option>', principals_options_for_select(users)
1062 end
1064 end
1063
1065
1064 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1066 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1065 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1067 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1066 end
1068 end
1067
1069
1068 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1070 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1069 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1071 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1070 end
1072 end
1071
1073
1072 def test_image_tag_should_pick_the_default_image
1074 def test_image_tag_should_pick_the_default_image
1073 assert_match 'src="/images/image.png"', image_tag("image.png")
1075 assert_match 'src="/images/image.png"', image_tag("image.png")
1074 end
1076 end
1075
1077
1076 def test_image_tag_should_pick_the_theme_image_if_it_exists
1078 def test_image_tag_should_pick_the_theme_image_if_it_exists
1077 theme = Redmine::Themes.themes.last
1079 theme = Redmine::Themes.themes.last
1078 theme.images << 'image.png'
1080 theme.images << 'image.png'
1079
1081
1080 with_settings :ui_theme => theme.id do
1082 with_settings :ui_theme => theme.id do
1081 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1083 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1082 assert_match %|src="/images/other.png"|, image_tag("other.png")
1084 assert_match %|src="/images/other.png"|, image_tag("other.png")
1083 end
1085 end
1084 ensure
1086 ensure
1085 theme.images.delete 'image.png'
1087 theme.images.delete 'image.png'
1086 end
1088 end
1087
1089
1088 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1090 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1089 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1091 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1090 end
1092 end
1091
1093
1092 def test_javascript_include_tag_should_pick_the_default_javascript
1094 def test_javascript_include_tag_should_pick_the_default_javascript
1093 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1095 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1094 end
1096 end
1095
1097
1096 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1098 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1097 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1099 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1098 end
1100 end
1099
1101
1100 def test_per_page_links_should_show_usefull_values
1102 def test_per_page_links_should_show_usefull_values
1101 set_language_if_valid 'en'
1103 set_language_if_valid 'en'
1102 stubs(:link_to).returns("[link]")
1104 stubs(:link_to).returns("[link]")
1103
1105
1104 with_settings :per_page_options => '10, 25, 50, 100' do
1106 with_settings :per_page_options => '10, 25, 50, 100' do
1105 assert_nil per_page_links(10, 3)
1107 assert_nil per_page_links(10, 3)
1106 assert_nil per_page_links(25, 3)
1108 assert_nil per_page_links(25, 3)
1107 assert_equal "Per page: 10, [link]", per_page_links(10, 22)
1109 assert_equal "Per page: 10, [link]", per_page_links(10, 22)
1108 assert_equal "Per page: [link], 25", per_page_links(25, 22)
1110 assert_equal "Per page: [link], 25", per_page_links(25, 22)
1109 assert_equal "Per page: [link], [link], 50", per_page_links(50, 22)
1111 assert_equal "Per page: [link], [link], 50", per_page_links(50, 22)
1110 assert_equal "Per page: [link], 25, [link]", per_page_links(25, 26)
1112 assert_equal "Per page: [link], 25, [link]", per_page_links(25, 26)
1111 assert_equal "Per page: [link], 25, [link], [link]", per_page_links(25, 120)
1113 assert_equal "Per page: [link], 25, [link], [link]", per_page_links(25, 120)
1112 end
1114 end
1113 end
1115 end
1114 end
1116 end
General Comments 0
You need to be logged in to leave comments. Login now