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