##// END OF EJS Templates
Adds support for :plugin option to image_tag helper....
Jean-Philippe Lang -
r9377:da43f785bea5
parent child
Show More
@@ -1,1172 +1,1184
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 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}"))
223 s << (content_tag('div', v.html_safe, :class => "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 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
372 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
373 end
373 end
374
374
375 def pagination_links_full(paginator, count=nil, options={})
375 def pagination_links_full(paginator, count=nil, options={})
376 page_param = options.delete(:page_param) || :page
376 page_param = options.delete(:page_param) || :page
377 per_page_links = options.delete(:per_page_links)
377 per_page_links = options.delete(:per_page_links)
378 url_param = params.dup
378 url_param = params.dup
379
379
380 html = ''
380 html = ''
381 if paginator.current.previous
381 if paginator.current.previous
382 # \xc2\xab(utf-8) = &#171;
382 # \xc2\xab(utf-8) = &#171;
383 html << link_to_content_update(
383 html << link_to_content_update(
384 "\xc2\xab " + l(:label_previous),
384 "\xc2\xab " + l(:label_previous),
385 url_param.merge(page_param => paginator.current.previous)) + ' '
385 url_param.merge(page_param => paginator.current.previous)) + ' '
386 end
386 end
387
387
388 html << (pagination_links_each(paginator, options) do |n|
388 html << (pagination_links_each(paginator, options) do |n|
389 link_to_content_update(n.to_s, url_param.merge(page_param => n))
389 link_to_content_update(n.to_s, url_param.merge(page_param => n))
390 end || '')
390 end || '')
391
391
392 if paginator.current.next
392 if paginator.current.next
393 # \xc2\xbb(utf-8) = &#187;
393 # \xc2\xbb(utf-8) = &#187;
394 html << ' ' + link_to_content_update(
394 html << ' ' + link_to_content_update(
395 (l(:label_next) + " \xc2\xbb"),
395 (l(:label_next) + " \xc2\xbb"),
396 url_param.merge(page_param => paginator.current.next))
396 url_param.merge(page_param => paginator.current.next))
397 end
397 end
398
398
399 unless count.nil?
399 unless count.nil?
400 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
400 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
401 if per_page_links != false && links = per_page_links(paginator.items_per_page)
401 if per_page_links != false && links = per_page_links(paginator.items_per_page)
402 html << " | #{links}"
402 html << " | #{links}"
403 end
403 end
404 end
404 end
405
405
406 html.html_safe
406 html.html_safe
407 end
407 end
408
408
409 def per_page_links(selected=nil)
409 def per_page_links(selected=nil)
410 links = Setting.per_page_options_array.collect do |n|
410 links = Setting.per_page_options_array.collect do |n|
411 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
411 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
412 end
412 end
413 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
413 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
414 end
414 end
415
415
416 def reorder_links(name, url, method = :post)
416 def reorder_links(name, url, method = :post)
417 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
417 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
418 url.merge({"#{name}[move_to]" => 'highest'}),
418 url.merge({"#{name}[move_to]" => 'highest'}),
419 :method => method, :title => l(:label_sort_highest)) +
419 :method => method, :title => l(:label_sort_highest)) +
420 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
420 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
421 url.merge({"#{name}[move_to]" => 'higher'}),
421 url.merge({"#{name}[move_to]" => 'higher'}),
422 :method => method, :title => l(:label_sort_higher)) +
422 :method => method, :title => l(:label_sort_higher)) +
423 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
423 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
424 url.merge({"#{name}[move_to]" => 'lower'}),
424 url.merge({"#{name}[move_to]" => 'lower'}),
425 :method => method, :title => l(:label_sort_lower)) +
425 :method => method, :title => l(:label_sort_lower)) +
426 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
426 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
427 url.merge({"#{name}[move_to]" => 'lowest'}),
427 url.merge({"#{name}[move_to]" => 'lowest'}),
428 :method => method, :title => l(:label_sort_lowest))
428 :method => method, :title => l(:label_sort_lowest))
429 end
429 end
430
430
431 def breadcrumb(*args)
431 def breadcrumb(*args)
432 elements = args.flatten
432 elements = args.flatten
433 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
433 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
434 end
434 end
435
435
436 def other_formats_links(&block)
436 def other_formats_links(&block)
437 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
437 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
438 yield Redmine::Views::OtherFormatsBuilder.new(self)
438 yield Redmine::Views::OtherFormatsBuilder.new(self)
439 concat('</p>'.html_safe)
439 concat('</p>'.html_safe)
440 end
440 end
441
441
442 def page_header_title
442 def page_header_title
443 if @project.nil? || @project.new_record?
443 if @project.nil? || @project.new_record?
444 h(Setting.app_title)
444 h(Setting.app_title)
445 else
445 else
446 b = []
446 b = []
447 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
447 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
448 if ancestors.any?
448 if ancestors.any?
449 root = ancestors.shift
449 root = ancestors.shift
450 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
450 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
451 if ancestors.size > 2
451 if ancestors.size > 2
452 b << "\xe2\x80\xa6"
452 b << "\xe2\x80\xa6"
453 ancestors = ancestors[-2, 2]
453 ancestors = ancestors[-2, 2]
454 end
454 end
455 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
455 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
456 end
456 end
457 b << h(@project)
457 b << h(@project)
458 b.join(" \xc2\xbb ").html_safe
458 b.join(" \xc2\xbb ").html_safe
459 end
459 end
460 end
460 end
461
461
462 def html_title(*args)
462 def html_title(*args)
463 if args.empty?
463 if args.empty?
464 title = @html_title || []
464 title = @html_title || []
465 title << @project.name if @project
465 title << @project.name if @project
466 title << Setting.app_title unless Setting.app_title == title.last
466 title << Setting.app_title unless Setting.app_title == title.last
467 title.select {|t| !t.blank? }.join(' - ')
467 title.select {|t| !t.blank? }.join(' - ')
468 else
468 else
469 @html_title ||= []
469 @html_title ||= []
470 @html_title += args
470 @html_title += args
471 end
471 end
472 end
472 end
473
473
474 # Returns the theme, controller name, and action as css classes for the
474 # Returns the theme, controller name, and action as css classes for the
475 # HTML body.
475 # HTML body.
476 def body_css_classes
476 def body_css_classes
477 css = []
477 css = []
478 if theme = Redmine::Themes.theme(Setting.ui_theme)
478 if theme = Redmine::Themes.theme(Setting.ui_theme)
479 css << 'theme-' + theme.name
479 css << 'theme-' + theme.name
480 end
480 end
481
481
482 css << 'controller-' + controller_name
482 css << 'controller-' + controller_name
483 css << 'action-' + action_name
483 css << 'action-' + action_name
484 css.join(' ')
484 css.join(' ')
485 end
485 end
486
486
487 def accesskey(s)
487 def accesskey(s)
488 Redmine::AccessKeys.key_for s
488 Redmine::AccessKeys.key_for s
489 end
489 end
490
490
491 # Formats text according to system settings.
491 # Formats text according to system settings.
492 # 2 ways to call this method:
492 # 2 ways to call this method:
493 # * with a String: textilizable(text, options)
493 # * with a String: textilizable(text, options)
494 # * with an object and one of its attribute: textilizable(issue, :description, options)
494 # * with an object and one of its attribute: textilizable(issue, :description, options)
495 def textilizable(*args)
495 def textilizable(*args)
496 options = args.last.is_a?(Hash) ? args.pop : {}
496 options = args.last.is_a?(Hash) ? args.pop : {}
497 case args.size
497 case args.size
498 when 1
498 when 1
499 obj = options[:object]
499 obj = options[:object]
500 text = args.shift
500 text = args.shift
501 when 2
501 when 2
502 obj = args.shift
502 obj = args.shift
503 attr = args.shift
503 attr = args.shift
504 text = obj.send(attr).to_s
504 text = obj.send(attr).to_s
505 else
505 else
506 raise ArgumentError, 'invalid arguments to textilizable'
506 raise ArgumentError, 'invalid arguments to textilizable'
507 end
507 end
508 return '' if text.blank?
508 return '' if text.blank?
509 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
509 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
510 only_path = options.delete(:only_path) == false ? false : true
510 only_path = options.delete(:only_path) == false ? false : true
511
511
512 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
512 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
513
513
514 @parsed_headings = []
514 @parsed_headings = []
515 @heading_anchors = {}
515 @heading_anchors = {}
516 @current_section = 0 if options[:edit_section_links]
516 @current_section = 0 if options[:edit_section_links]
517
517
518 parse_sections(text, project, obj, attr, only_path, options)
518 parse_sections(text, project, obj, attr, only_path, options)
519 text = parse_non_pre_blocks(text) do |text|
519 text = parse_non_pre_blocks(text) do |text|
520 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
520 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros].each do |method_name|
521 send method_name, text, project, obj, attr, only_path, options
521 send method_name, text, project, obj, attr, only_path, options
522 end
522 end
523 end
523 end
524 parse_headings(text, project, obj, attr, only_path, options)
524 parse_headings(text, project, obj, attr, only_path, options)
525
525
526 if @parsed_headings.any?
526 if @parsed_headings.any?
527 replace_toc(text, @parsed_headings)
527 replace_toc(text, @parsed_headings)
528 end
528 end
529
529
530 text.html_safe
530 text.html_safe
531 end
531 end
532
532
533 def parse_non_pre_blocks(text)
533 def parse_non_pre_blocks(text)
534 s = StringScanner.new(text)
534 s = StringScanner.new(text)
535 tags = []
535 tags = []
536 parsed = ''
536 parsed = ''
537 while !s.eos?
537 while !s.eos?
538 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
538 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
539 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
539 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
540 if tags.empty?
540 if tags.empty?
541 yield text
541 yield text
542 end
542 end
543 parsed << text
543 parsed << text
544 if tag
544 if tag
545 if closing
545 if closing
546 if tags.last == tag.downcase
546 if tags.last == tag.downcase
547 tags.pop
547 tags.pop
548 end
548 end
549 else
549 else
550 tags << tag.downcase
550 tags << tag.downcase
551 end
551 end
552 parsed << full_tag
552 parsed << full_tag
553 end
553 end
554 end
554 end
555 # Close any non closing tags
555 # Close any non closing tags
556 while tag = tags.pop
556 while tag = tags.pop
557 parsed << "</#{tag}>"
557 parsed << "</#{tag}>"
558 end
558 end
559 parsed
559 parsed
560 end
560 end
561
561
562 def parse_inline_attachments(text, project, obj, attr, only_path, options)
562 def parse_inline_attachments(text, project, obj, attr, only_path, options)
563 # when using an image link, try to use an attachment, if possible
563 # when using an image link, try to use an attachment, if possible
564 if options[:attachments] || (obj && obj.respond_to?(:attachments))
564 if options[:attachments] || (obj && obj.respond_to?(:attachments))
565 attachments = options[:attachments] || obj.attachments
565 attachments = options[:attachments] || obj.attachments
566 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
566 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
567 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
567 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
568 # search for the picture in attachments
568 # search for the picture in attachments
569 if found = Attachment.latest_attach(attachments, filename)
569 if found = Attachment.latest_attach(attachments, filename)
570 image_url = url_for :only_path => only_path, :controller => 'attachments',
570 image_url = url_for :only_path => only_path, :controller => 'attachments',
571 :action => 'download', :id => found
571 :action => 'download', :id => found
572 desc = found.description.to_s.gsub('"', '')
572 desc = found.description.to_s.gsub('"', '')
573 if !desc.blank? && alttext.blank?
573 if !desc.blank? && alttext.blank?
574 alt = " title=\"#{desc}\" alt=\"#{desc}\""
574 alt = " title=\"#{desc}\" alt=\"#{desc}\""
575 end
575 end
576 "src=\"#{image_url}\"#{alt}"
576 "src=\"#{image_url}\"#{alt}"
577 else
577 else
578 m
578 m
579 end
579 end
580 end
580 end
581 end
581 end
582 end
582 end
583
583
584 # Wiki links
584 # Wiki links
585 #
585 #
586 # Examples:
586 # Examples:
587 # [[mypage]]
587 # [[mypage]]
588 # [[mypage|mytext]]
588 # [[mypage|mytext]]
589 # wiki links can refer other project wikis, using project name or identifier:
589 # wiki links can refer other project wikis, using project name or identifier:
590 # [[project:]] -> wiki starting page
590 # [[project:]] -> wiki starting page
591 # [[project:|mytext]]
591 # [[project:|mytext]]
592 # [[project:mypage]]
592 # [[project:mypage]]
593 # [[project:mypage|mytext]]
593 # [[project:mypage|mytext]]
594 def parse_wiki_links(text, project, obj, attr, only_path, options)
594 def parse_wiki_links(text, project, obj, attr, only_path, options)
595 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
595 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
596 link_project = project
596 link_project = project
597 esc, all, page, title = $1, $2, $3, $5
597 esc, all, page, title = $1, $2, $3, $5
598 if esc.nil?
598 if esc.nil?
599 if page =~ /^([^\:]+)\:(.*)$/
599 if page =~ /^([^\:]+)\:(.*)$/
600 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
600 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
601 page = $2
601 page = $2
602 title ||= $1 if page.blank?
602 title ||= $1 if page.blank?
603 end
603 end
604
604
605 if link_project && link_project.wiki
605 if link_project && link_project.wiki
606 # extract anchor
606 # extract anchor
607 anchor = nil
607 anchor = nil
608 if page =~ /^(.+?)\#(.+)$/
608 if page =~ /^(.+?)\#(.+)$/
609 page, anchor = $1, $2
609 page, anchor = $1, $2
610 end
610 end
611 anchor = sanitize_anchor_name(anchor) if anchor.present?
611 anchor = sanitize_anchor_name(anchor) if anchor.present?
612 # check if page exists
612 # check if page exists
613 wiki_page = link_project.wiki.find_page(page)
613 wiki_page = link_project.wiki.find_page(page)
614 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
614 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
615 "##{anchor}"
615 "##{anchor}"
616 else
616 else
617 case options[:wiki_links]
617 case options[:wiki_links]
618 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
618 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
619 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
619 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
620 else
620 else
621 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
621 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
622 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
622 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
623 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
623 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
624 :id => wiki_page_id, :anchor => anchor, :parent => parent)
624 :id => wiki_page_id, :anchor => anchor, :parent => parent)
625 end
625 end
626 end
626 end
627 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
627 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
628 else
628 else
629 # project or wiki doesn't exist
629 # project or wiki doesn't exist
630 all
630 all
631 end
631 end
632 else
632 else
633 all
633 all
634 end
634 end
635 end
635 end
636 end
636 end
637
637
638 # Redmine links
638 # Redmine links
639 #
639 #
640 # Examples:
640 # Examples:
641 # Issues:
641 # Issues:
642 # #52 -> Link to issue #52
642 # #52 -> Link to issue #52
643 # Changesets:
643 # Changesets:
644 # r52 -> Link to revision 52
644 # r52 -> Link to revision 52
645 # commit:a85130f -> Link to scmid starting with a85130f
645 # commit:a85130f -> Link to scmid starting with a85130f
646 # Documents:
646 # Documents:
647 # document#17 -> Link to document with id 17
647 # document#17 -> Link to document with id 17
648 # document:Greetings -> Link to the document with title "Greetings"
648 # document:Greetings -> Link to the document with title "Greetings"
649 # document:"Some document" -> Link to the document with title "Some document"
649 # document:"Some document" -> Link to the document with title "Some document"
650 # Versions:
650 # Versions:
651 # version#3 -> Link to version with id 3
651 # version#3 -> Link to version with id 3
652 # version:1.0.0 -> Link to version named "1.0.0"
652 # version:1.0.0 -> Link to version named "1.0.0"
653 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
653 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
654 # Attachments:
654 # Attachments:
655 # attachment:file.zip -> Link to the attachment of the current object named file.zip
655 # attachment:file.zip -> Link to the attachment of the current object named file.zip
656 # Source files:
656 # Source files:
657 # source:some/file -> Link to the file located at /some/file in the project's repository
657 # source:some/file -> Link to the file located at /some/file in the project's repository
658 # source:some/file@52 -> Link to the file's revision 52
658 # source:some/file@52 -> Link to the file's revision 52
659 # source:some/file#L120 -> Link to line 120 of the file
659 # source:some/file#L120 -> Link to line 120 of the file
660 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
660 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
661 # export:some/file -> Force the download of the file
661 # export:some/file -> Force the download of the file
662 # Forum messages:
662 # Forum messages:
663 # message#1218 -> Link to message with id 1218
663 # message#1218 -> Link to message with id 1218
664 #
664 #
665 # Links can refer other objects from other projects, using project identifier:
665 # Links can refer other objects from other projects, using project identifier:
666 # identifier:r52
666 # identifier:r52
667 # identifier:document:"Some document"
667 # identifier:document:"Some document"
668 # identifier:version:1.0.0
668 # identifier:version:1.0.0
669 # identifier:source:some/file
669 # identifier:source:some/file
670 def parse_redmine_links(text, project, obj, attr, only_path, options)
670 def parse_redmine_links(text, project, obj, attr, only_path, options)
671 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|
671 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|
672 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
672 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
673 link = nil
673 link = nil
674 if project_identifier
674 if project_identifier
675 project = Project.visible.find_by_identifier(project_identifier)
675 project = Project.visible.find_by_identifier(project_identifier)
676 end
676 end
677 if esc.nil?
677 if esc.nil?
678 if prefix.nil? && sep == 'r'
678 if prefix.nil? && sep == 'r'
679 if project
679 if project
680 repository = nil
680 repository = nil
681 if repo_identifier
681 if repo_identifier
682 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
682 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
683 else
683 else
684 repository = project.repository
684 repository = project.repository
685 end
685 end
686 # project.changesets.visible raises an SQL error because of a double join on repositories
686 # project.changesets.visible raises an SQL error because of a double join on repositories
687 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
687 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
688 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},
688 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},
689 :class => 'changeset',
689 :class => 'changeset',
690 :title => truncate_single_line(changeset.comments, :length => 100))
690 :title => truncate_single_line(changeset.comments, :length => 100))
691 end
691 end
692 end
692 end
693 elsif sep == '#'
693 elsif sep == '#'
694 oid = identifier.to_i
694 oid = identifier.to_i
695 case prefix
695 case prefix
696 when nil
696 when nil
697 if issue = Issue.visible.find_by_id(oid, :include => :status)
697 if issue = Issue.visible.find_by_id(oid, :include => :status)
698 anchor = comment_id ? "note-#{comment_id}" : nil
698 anchor = comment_id ? "note-#{comment_id}" : nil
699 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
699 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid, :anchor => anchor},
700 :class => issue.css_classes,
700 :class => issue.css_classes,
701 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
701 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
702 end
702 end
703 when 'document'
703 when 'document'
704 if document = Document.visible.find_by_id(oid)
704 if document = Document.visible.find_by_id(oid)
705 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
705 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
706 :class => 'document'
706 :class => 'document'
707 end
707 end
708 when 'version'
708 when 'version'
709 if version = Version.visible.find_by_id(oid)
709 if version = Version.visible.find_by_id(oid)
710 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
710 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
711 :class => 'version'
711 :class => 'version'
712 end
712 end
713 when 'message'
713 when 'message'
714 if message = Message.visible.find_by_id(oid, :include => :parent)
714 if message = Message.visible.find_by_id(oid, :include => :parent)
715 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
715 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
716 end
716 end
717 when 'forum'
717 when 'forum'
718 if board = Board.visible.find_by_id(oid)
718 if board = Board.visible.find_by_id(oid)
719 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
719 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
720 :class => 'board'
720 :class => 'board'
721 end
721 end
722 when 'news'
722 when 'news'
723 if news = News.visible.find_by_id(oid)
723 if news = News.visible.find_by_id(oid)
724 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
724 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
725 :class => 'news'
725 :class => 'news'
726 end
726 end
727 when 'project'
727 when 'project'
728 if p = Project.visible.find_by_id(oid)
728 if p = Project.visible.find_by_id(oid)
729 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
729 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
730 end
730 end
731 end
731 end
732 elsif sep == ':'
732 elsif sep == ':'
733 # removes the double quotes if any
733 # removes the double quotes if any
734 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
734 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
735 case prefix
735 case prefix
736 when 'document'
736 when 'document'
737 if project && document = project.documents.visible.find_by_title(name)
737 if project && document = project.documents.visible.find_by_title(name)
738 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
738 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
739 :class => 'document'
739 :class => 'document'
740 end
740 end
741 when 'version'
741 when 'version'
742 if project && version = project.versions.visible.find_by_name(name)
742 if project && version = project.versions.visible.find_by_name(name)
743 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
743 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
744 :class => 'version'
744 :class => 'version'
745 end
745 end
746 when 'forum'
746 when 'forum'
747 if project && board = project.boards.visible.find_by_name(name)
747 if project && board = project.boards.visible.find_by_name(name)
748 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
748 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
749 :class => 'board'
749 :class => 'board'
750 end
750 end
751 when 'news'
751 when 'news'
752 if project && news = project.news.visible.find_by_title(name)
752 if project && news = project.news.visible.find_by_title(name)
753 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
753 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
754 :class => 'news'
754 :class => 'news'
755 end
755 end
756 when 'commit', 'source', 'export'
756 when 'commit', 'source', 'export'
757 if project
757 if project
758 repository = nil
758 repository = nil
759 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
759 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
760 repo_prefix, repo_identifier, name = $1, $2, $3
760 repo_prefix, repo_identifier, name = $1, $2, $3
761 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
761 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
762 else
762 else
763 repository = project.repository
763 repository = project.repository
764 end
764 end
765 if prefix == 'commit'
765 if prefix == 'commit'
766 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
766 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
767 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},
767 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},
768 :class => 'changeset',
768 :class => 'changeset',
769 :title => truncate_single_line(h(changeset.comments), :length => 100)
769 :title => truncate_single_line(h(changeset.comments), :length => 100)
770 end
770 end
771 else
771 else
772 if repository && User.current.allowed_to?(:browse_repository, project)
772 if repository && User.current.allowed_to?(:browse_repository, project)
773 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
773 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
774 path, rev, anchor = $1, $3, $5
774 path, rev, anchor = $1, $3, $5
775 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
775 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
776 :path => to_path_param(path),
776 :path => to_path_param(path),
777 :rev => rev,
777 :rev => rev,
778 :anchor => anchor,
778 :anchor => anchor,
779 :format => (prefix == 'export' ? 'raw' : nil)},
779 :format => (prefix == 'export' ? 'raw' : nil)},
780 :class => (prefix == 'export' ? 'source download' : 'source')
780 :class => (prefix == 'export' ? 'source download' : 'source')
781 end
781 end
782 end
782 end
783 repo_prefix = nil
783 repo_prefix = nil
784 end
784 end
785 when 'attachment'
785 when 'attachment'
786 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
786 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
787 if attachments && attachment = attachments.detect {|a| a.filename == name }
787 if attachments && attachment = attachments.detect {|a| a.filename == name }
788 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
788 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
789 :class => 'attachment'
789 :class => 'attachment'
790 end
790 end
791 when 'project'
791 when 'project'
792 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
792 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
793 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
793 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
794 end
794 end
795 end
795 end
796 end
796 end
797 end
797 end
798 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
798 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
799 end
799 end
800 end
800 end
801
801
802 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
802 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
803
803
804 def parse_sections(text, project, obj, attr, only_path, options)
804 def parse_sections(text, project, obj, attr, only_path, options)
805 return unless options[:edit_section_links]
805 return unless options[:edit_section_links]
806 text.gsub!(HEADING_RE) do
806 text.gsub!(HEADING_RE) do
807 heading = $1
807 heading = $1
808 @current_section += 1
808 @current_section += 1
809 if @current_section > 1
809 if @current_section > 1
810 content_tag('div',
810 content_tag('div',
811 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
811 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
812 :class => 'contextual',
812 :class => 'contextual',
813 :title => l(:button_edit_section)) + heading.html_safe
813 :title => l(:button_edit_section)) + heading.html_safe
814 else
814 else
815 heading
815 heading
816 end
816 end
817 end
817 end
818 end
818 end
819
819
820 # Headings and TOC
820 # Headings and TOC
821 # Adds ids and links to headings unless options[:headings] is set to false
821 # Adds ids and links to headings unless options[:headings] is set to false
822 def parse_headings(text, project, obj, attr, only_path, options)
822 def parse_headings(text, project, obj, attr, only_path, options)
823 return if options[:headings] == false
823 return if options[:headings] == false
824
824
825 text.gsub!(HEADING_RE) do
825 text.gsub!(HEADING_RE) do
826 level, attrs, content = $2.to_i, $3, $4
826 level, attrs, content = $2.to_i, $3, $4
827 item = strip_tags(content).strip
827 item = strip_tags(content).strip
828 anchor = sanitize_anchor_name(item)
828 anchor = sanitize_anchor_name(item)
829 # used for single-file wiki export
829 # used for single-file wiki export
830 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
830 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
831 @heading_anchors[anchor] ||= 0
831 @heading_anchors[anchor] ||= 0
832 idx = (@heading_anchors[anchor] += 1)
832 idx = (@heading_anchors[anchor] += 1)
833 if idx > 1
833 if idx > 1
834 anchor = "#{anchor}-#{idx}"
834 anchor = "#{anchor}-#{idx}"
835 end
835 end
836 @parsed_headings << [level, anchor, item]
836 @parsed_headings << [level, anchor, item]
837 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
837 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
838 end
838 end
839 end
839 end
840
840
841 MACROS_RE = /
841 MACROS_RE = /
842 (!)? # escaping
842 (!)? # escaping
843 (
843 (
844 \{\{ # opening tag
844 \{\{ # opening tag
845 ([\w]+) # macro name
845 ([\w]+) # macro name
846 (\(([^\}]*)\))? # optional arguments
846 (\(([^\}]*)\))? # optional arguments
847 \}\} # closing tag
847 \}\} # closing tag
848 )
848 )
849 /x unless const_defined?(:MACROS_RE)
849 /x unless const_defined?(:MACROS_RE)
850
850
851 # Macros substitution
851 # Macros substitution
852 def parse_macros(text, project, obj, attr, only_path, options)
852 def parse_macros(text, project, obj, attr, only_path, options)
853 text.gsub!(MACROS_RE) do
853 text.gsub!(MACROS_RE) do
854 esc, all, macro = $1, $2, $3.downcase
854 esc, all, macro = $1, $2, $3.downcase
855 args = ($5 || '').split(',').each(&:strip)
855 args = ($5 || '').split(',').each(&:strip)
856 if esc.nil?
856 if esc.nil?
857 begin
857 begin
858 exec_macro(macro, obj, args)
858 exec_macro(macro, obj, args)
859 rescue => e
859 rescue => e
860 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
860 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
861 end || all
861 end || all
862 else
862 else
863 all
863 all
864 end
864 end
865 end
865 end
866 end
866 end
867
867
868 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
868 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
869
869
870 # Renders the TOC with given headings
870 # Renders the TOC with given headings
871 def replace_toc(text, headings)
871 def replace_toc(text, headings)
872 text.gsub!(TOC_RE) do
872 text.gsub!(TOC_RE) do
873 if headings.empty?
873 if headings.empty?
874 ''
874 ''
875 else
875 else
876 div_class = 'toc'
876 div_class = 'toc'
877 div_class << ' right' if $1 == '>'
877 div_class << ' right' if $1 == '>'
878 div_class << ' left' if $1 == '<'
878 div_class << ' left' if $1 == '<'
879 out = "<ul class=\"#{div_class}\"><li>"
879 out = "<ul class=\"#{div_class}\"><li>"
880 root = headings.map(&:first).min
880 root = headings.map(&:first).min
881 current = root
881 current = root
882 started = false
882 started = false
883 headings.each do |level, anchor, item|
883 headings.each do |level, anchor, item|
884 if level > current
884 if level > current
885 out << '<ul><li>' * (level - current)
885 out << '<ul><li>' * (level - current)
886 elsif level < current
886 elsif level < current
887 out << "</li></ul>\n" * (current - level) + "</li><li>"
887 out << "</li></ul>\n" * (current - level) + "</li><li>"
888 elsif started
888 elsif started
889 out << '</li><li>'
889 out << '</li><li>'
890 end
890 end
891 out << "<a href=\"##{anchor}\">#{item}</a>"
891 out << "<a href=\"##{anchor}\">#{item}</a>"
892 current = level
892 current = level
893 started = true
893 started = true
894 end
894 end
895 out << '</li></ul>' * (current - root)
895 out << '</li></ul>' * (current - root)
896 out << '</li></ul>'
896 out << '</li></ul>'
897 end
897 end
898 end
898 end
899 end
899 end
900
900
901 # Same as Rails' simple_format helper without using paragraphs
901 # Same as Rails' simple_format helper without using paragraphs
902 def simple_format_without_paragraph(text)
902 def simple_format_without_paragraph(text)
903 text.to_s.
903 text.to_s.
904 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
904 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
905 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
905 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
906 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
906 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
907 html_safe
907 html_safe
908 end
908 end
909
909
910 def lang_options_for_select(blank=true)
910 def lang_options_for_select(blank=true)
911 (blank ? [["(auto)", ""]] : []) +
911 (blank ? [["(auto)", ""]] : []) +
912 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
912 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
913 end
913 end
914
914
915 def label_tag_for(name, option_tags = nil, options = {})
915 def label_tag_for(name, option_tags = nil, options = {})
916 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
916 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
917 content_tag("label", label_text)
917 content_tag("label", label_text)
918 end
918 end
919
919
920 def labelled_tabular_form_for(*args, &proc)
920 def labelled_tabular_form_for(*args, &proc)
921 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
921 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
922 args << {} unless args.last.is_a?(Hash)
922 args << {} unless args.last.is_a?(Hash)
923 options = args.last
923 options = args.last
924 options[:html] ||= {}
924 options[:html] ||= {}
925 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
925 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
926 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
926 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
927 form_for(*args, &proc)
927 form_for(*args, &proc)
928 end
928 end
929
929
930 def labelled_form_for(*args, &proc)
930 def labelled_form_for(*args, &proc)
931 args << {} unless args.last.is_a?(Hash)
931 args << {} unless args.last.is_a?(Hash)
932 options = args.last
932 options = args.last
933 if args.first.is_a?(Symbol)
933 if args.first.is_a?(Symbol)
934 options.merge!(:as => args.shift)
934 options.merge!(:as => args.shift)
935 end
935 end
936 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
936 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
937 form_for(*args, &proc)
937 form_for(*args, &proc)
938 end
938 end
939
939
940 def labelled_fields_for(*args, &proc)
940 def labelled_fields_for(*args, &proc)
941 args << {} unless args.last.is_a?(Hash)
941 args << {} unless args.last.is_a?(Hash)
942 options = args.last
942 options = args.last
943 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
943 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
944 fields_for(*args, &proc)
944 fields_for(*args, &proc)
945 end
945 end
946
946
947 def labelled_remote_form_for(*args, &proc)
947 def labelled_remote_form_for(*args, &proc)
948 args << {} unless args.last.is_a?(Hash)
948 args << {} unless args.last.is_a?(Hash)
949 options = args.last
949 options = args.last
950 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
950 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
951 remote_form_for(*args, &proc)
951 remote_form_for(*args, &proc)
952 end
952 end
953
953
954 def error_messages_for(*objects)
954 def error_messages_for(*objects)
955 html = ""
955 html = ""
956 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
956 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
957 errors = objects.map {|o| o.errors.full_messages}.flatten
957 errors = objects.map {|o| o.errors.full_messages}.flatten
958 if errors.any?
958 if errors.any?
959 html << "<div id='errorExplanation'><ul>\n"
959 html << "<div id='errorExplanation'><ul>\n"
960 errors.each do |error|
960 errors.each do |error|
961 html << "<li>#{h error}</li>\n"
961 html << "<li>#{h error}</li>\n"
962 end
962 end
963 html << "</ul></div>\n"
963 html << "</ul></div>\n"
964 end
964 end
965 html.html_safe
965 html.html_safe
966 end
966 end
967
967
968 def back_url_hidden_field_tag
968 def back_url_hidden_field_tag
969 back_url = params[:back_url] || request.env['HTTP_REFERER']
969 back_url = params[:back_url] || request.env['HTTP_REFERER']
970 back_url = CGI.unescape(back_url.to_s)
970 back_url = CGI.unescape(back_url.to_s)
971 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
971 hidden_field_tag('back_url', CGI.escape(back_url), :id => nil) unless back_url.blank?
972 end
972 end
973
973
974 def check_all_links(form_name)
974 def check_all_links(form_name)
975 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
975 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
976 " | ".html_safe +
976 " | ".html_safe +
977 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
977 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
978 end
978 end
979
979
980 def progress_bar(pcts, options={})
980 def progress_bar(pcts, options={})
981 pcts = [pcts, pcts] unless pcts.is_a?(Array)
981 pcts = [pcts, pcts] unless pcts.is_a?(Array)
982 pcts = pcts.collect(&:round)
982 pcts = pcts.collect(&:round)
983 pcts[1] = pcts[1] - pcts[0]
983 pcts[1] = pcts[1] - pcts[0]
984 pcts << (100 - pcts[1] - pcts[0])
984 pcts << (100 - pcts[1] - pcts[0])
985 width = options[:width] || '100px;'
985 width = options[:width] || '100px;'
986 legend = options[:legend] || ''
986 legend = options[:legend] || ''
987 content_tag('table',
987 content_tag('table',
988 content_tag('tr',
988 content_tag('tr',
989 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
989 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
990 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
990 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
991 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
991 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
992 ), :class => 'progress', :style => "width: #{width};").html_safe +
992 ), :class => 'progress', :style => "width: #{width};").html_safe +
993 content_tag('p', legend, :class => 'pourcent').html_safe
993 content_tag('p', legend, :class => 'pourcent').html_safe
994 end
994 end
995
995
996 def checked_image(checked=true)
996 def checked_image(checked=true)
997 if checked
997 if checked
998 image_tag 'toggle_check.png'
998 image_tag 'toggle_check.png'
999 end
999 end
1000 end
1000 end
1001
1001
1002 def context_menu(url)
1002 def context_menu(url)
1003 unless @context_menu_included
1003 unless @context_menu_included
1004 content_for :header_tags do
1004 content_for :header_tags do
1005 javascript_include_tag('context_menu') +
1005 javascript_include_tag('context_menu') +
1006 stylesheet_link_tag('context_menu')
1006 stylesheet_link_tag('context_menu')
1007 end
1007 end
1008 if l(:direction) == 'rtl'
1008 if l(:direction) == 'rtl'
1009 content_for :header_tags do
1009 content_for :header_tags do
1010 stylesheet_link_tag('context_menu_rtl')
1010 stylesheet_link_tag('context_menu_rtl')
1011 end
1011 end
1012 end
1012 end
1013 @context_menu_included = true
1013 @context_menu_included = true
1014 end
1014 end
1015 javascript_tag "new ContextMenu('#{ url_for(url) }')"
1015 javascript_tag "new ContextMenu('#{ url_for(url) }')"
1016 end
1016 end
1017
1017
1018 def calendar_for(field_id)
1018 def calendar_for(field_id)
1019 include_calendar_headers_tags
1019 include_calendar_headers_tags
1020 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
1020 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
1021 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1021 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1022 end
1022 end
1023
1023
1024 def include_calendar_headers_tags
1024 def include_calendar_headers_tags
1025 unless @calendar_headers_tags_included
1025 unless @calendar_headers_tags_included
1026 @calendar_headers_tags_included = true
1026 @calendar_headers_tags_included = true
1027 content_for :header_tags do
1027 content_for :header_tags do
1028 start_of_week = case Setting.start_of_week.to_i
1028 start_of_week = case Setting.start_of_week.to_i
1029 when 1
1029 when 1
1030 'Calendar._FD = 1;' # Monday
1030 'Calendar._FD = 1;' # Monday
1031 when 7
1031 when 7
1032 'Calendar._FD = 0;' # Sunday
1032 'Calendar._FD = 0;' # Sunday
1033 when 6
1033 when 6
1034 'Calendar._FD = 6;' # Saturday
1034 'Calendar._FD = 6;' # Saturday
1035 else
1035 else
1036 '' # use language
1036 '' # use language
1037 end
1037 end
1038
1038
1039 javascript_include_tag('calendar/calendar') +
1039 javascript_include_tag('calendar/calendar') +
1040 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1040 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1041 javascript_tag(start_of_week) +
1041 javascript_tag(start_of_week) +
1042 javascript_include_tag('calendar/calendar-setup') +
1042 javascript_include_tag('calendar/calendar-setup') +
1043 stylesheet_link_tag('calendar')
1043 stylesheet_link_tag('calendar')
1044 end
1044 end
1045 end
1045 end
1046 end
1046 end
1047
1047
1048 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1048 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1049 # Examples:
1049 # Examples:
1050 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1050 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1051 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1051 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1052 #
1052 #
1053 def stylesheet_link_tag(*sources)
1053 def stylesheet_link_tag(*sources)
1054 options = sources.last.is_a?(Hash) ? sources.pop : {}
1054 options = sources.last.is_a?(Hash) ? sources.pop : {}
1055 plugin = options.delete(:plugin)
1055 plugin = options.delete(:plugin)
1056 sources = sources.map do |source|
1056 sources = sources.map do |source|
1057 if plugin
1057 if plugin
1058 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1058 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1059 elsif current_theme && current_theme.stylesheets.include?(source)
1059 elsif current_theme && current_theme.stylesheets.include?(source)
1060 current_theme.stylesheet_path(source)
1060 current_theme.stylesheet_path(source)
1061 else
1061 else
1062 source
1062 source
1063 end
1063 end
1064 end
1064 end
1065 super sources, options
1065 super sources, options
1066 end
1066 end
1067
1067
1068 # Overrides Rails' image_tag with plugins support.
1069 # Examples:
1070 # image_tag('image.png') # => picks defaults image.png
1071 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1072 #
1073 def image_tag(source, options={})
1074 if plugin = options.delete(:plugin)
1075 source = "/plugin_assets/#{plugin}/images/#{source}"
1076 end
1077 super source, options
1078 end
1079
1068 # Overrides Rails' javascript_include_tag with plugins support
1080 # Overrides Rails' javascript_include_tag with plugins support
1069 # Examples:
1081 # Examples:
1070 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1082 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1071 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1083 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1072 #
1084 #
1073 def javascript_include_tag(*sources)
1085 def javascript_include_tag(*sources)
1074 options = sources.last.is_a?(Hash) ? sources.pop : {}
1086 options = sources.last.is_a?(Hash) ? sources.pop : {}
1075 if plugin = options.delete(:plugin)
1087 if plugin = options.delete(:plugin)
1076 sources = sources.map do |source|
1088 sources = sources.map do |source|
1077 if plugin
1089 if plugin
1078 "/plugin_assets/#{plugin}/javascripts/#{source}"
1090 "/plugin_assets/#{plugin}/javascripts/#{source}"
1079 else
1091 else
1080 source
1092 source
1081 end
1093 end
1082 end
1094 end
1083 end
1095 end
1084 super sources, options
1096 super sources, options
1085 end
1097 end
1086
1098
1087 def content_for(name, content = nil, &block)
1099 def content_for(name, content = nil, &block)
1088 @has_content ||= {}
1100 @has_content ||= {}
1089 @has_content[name] = true
1101 @has_content[name] = true
1090 super(name, content, &block)
1102 super(name, content, &block)
1091 end
1103 end
1092
1104
1093 def has_content?(name)
1105 def has_content?(name)
1094 (@has_content && @has_content[name]) || false
1106 (@has_content && @has_content[name]) || false
1095 end
1107 end
1096
1108
1097 def email_delivery_enabled?
1109 def email_delivery_enabled?
1098 !!ActionMailer::Base.perform_deliveries
1110 !!ActionMailer::Base.perform_deliveries
1099 end
1111 end
1100
1112
1101 # Returns the avatar image tag for the given +user+ if avatars are enabled
1113 # Returns the avatar image tag for the given +user+ if avatars are enabled
1102 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1114 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1103 def avatar(user, options = { })
1115 def avatar(user, options = { })
1104 if Setting.gravatar_enabled?
1116 if Setting.gravatar_enabled?
1105 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1117 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1106 email = nil
1118 email = nil
1107 if user.respond_to?(:mail)
1119 if user.respond_to?(:mail)
1108 email = user.mail
1120 email = user.mail
1109 elsif user.to_s =~ %r{<(.+?)>}
1121 elsif user.to_s =~ %r{<(.+?)>}
1110 email = $1
1122 email = $1
1111 end
1123 end
1112 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1124 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1113 else
1125 else
1114 ''
1126 ''
1115 end
1127 end
1116 end
1128 end
1117
1129
1118 def sanitize_anchor_name(anchor)
1130 def sanitize_anchor_name(anchor)
1119 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1131 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1120 end
1132 end
1121
1133
1122 # Returns the javascript tags that are included in the html layout head
1134 # Returns the javascript tags that are included in the html layout head
1123 def javascript_heads
1135 def javascript_heads
1124 tags = javascript_include_tag('prototype', 'effects', 'dragdrop', 'controls', 'rails', 'application')
1136 tags = javascript_include_tag('prototype', 'effects', 'dragdrop', 'controls', 'rails', 'application')
1125 unless User.current.pref.warn_on_leaving_unsaved == '0'
1137 unless User.current.pref.warn_on_leaving_unsaved == '0'
1126 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1138 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1127 end
1139 end
1128 tags
1140 tags
1129 end
1141 end
1130
1142
1131 def favicon
1143 def favicon
1132 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1144 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1133 end
1145 end
1134
1146
1135 def robot_exclusion_tag
1147 def robot_exclusion_tag
1136 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1148 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1137 end
1149 end
1138
1150
1139 # Returns true if arg is expected in the API response
1151 # Returns true if arg is expected in the API response
1140 def include_in_api_response?(arg)
1152 def include_in_api_response?(arg)
1141 unless @included_in_api_response
1153 unless @included_in_api_response
1142 param = params[:include]
1154 param = params[:include]
1143 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1155 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1144 @included_in_api_response.collect!(&:strip)
1156 @included_in_api_response.collect!(&:strip)
1145 end
1157 end
1146 @included_in_api_response.include?(arg.to_s)
1158 @included_in_api_response.include?(arg.to_s)
1147 end
1159 end
1148
1160
1149 # Returns options or nil if nometa param or X-Redmine-Nometa header
1161 # Returns options or nil if nometa param or X-Redmine-Nometa header
1150 # was set in the request
1162 # was set in the request
1151 def api_meta(options)
1163 def api_meta(options)
1152 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1164 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1153 # compatibility mode for activeresource clients that raise
1165 # compatibility mode for activeresource clients that raise
1154 # an error when unserializing an array with attributes
1166 # an error when unserializing an array with attributes
1155 nil
1167 nil
1156 else
1168 else
1157 options
1169 options
1158 end
1170 end
1159 end
1171 end
1160
1172
1161 private
1173 private
1162
1174
1163 def wiki_helper
1175 def wiki_helper
1164 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1176 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1165 extend helper
1177 extend helper
1166 return self
1178 return self
1167 end
1179 end
1168
1180
1169 def link_to_content_update(text, url_params = {}, html_options = {})
1181 def link_to_content_update(text, url_params = {}, html_options = {})
1170 link_to(text, url_params, html_options)
1182 link_to(text, url_params, html_options)
1171 end
1183 end
1172 end
1184 end
@@ -1,1064 +1,1072
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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_wiki_links_in_tables
728 def test_wiki_links_in_tables
729 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
729 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
730 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
730 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
731 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
731 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
732 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
732 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
733 }
733 }
734 @project = Project.find(1)
734 @project = Project.find(1)
735 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
735 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
736 end
736 end
737
737
738 def test_text_formatting
738 def test_text_formatting
739 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
739 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
740 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
740 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
741 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
741 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
742 '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>',
742 '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>',
743 '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',
743 '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',
744 }
744 }
745 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
745 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
746 end
746 end
747
747
748 def test_wiki_horizontal_rule
748 def test_wiki_horizontal_rule
749 assert_equal '<hr />', textilizable('---')
749 assert_equal '<hr />', textilizable('---')
750 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
750 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
751 end
751 end
752
752
753 def test_footnotes
753 def test_footnotes
754 raw = <<-RAW
754 raw = <<-RAW
755 This is some text[1].
755 This is some text[1].
756
756
757 fn1. This is the foot note
757 fn1. This is the foot note
758 RAW
758 RAW
759
759
760 expected = <<-EXPECTED
760 expected = <<-EXPECTED
761 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
761 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
762 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
762 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
763 EXPECTED
763 EXPECTED
764
764
765 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
765 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
766 end
766 end
767
767
768 def test_headings
768 def test_headings
769 raw = 'h1. Some heading'
769 raw = 'h1. Some heading'
770 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
770 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
771
771
772 assert_equal expected, textilizable(raw)
772 assert_equal expected, textilizable(raw)
773 end
773 end
774
774
775 def test_headings_with_special_chars
775 def test_headings_with_special_chars
776 # This test makes sure that the generated anchor names match the expected
776 # This test makes sure that the generated anchor names match the expected
777 # ones even if the heading text contains unconventional characters
777 # ones even if the heading text contains unconventional characters
778 raw = 'h1. Some heading related to version 0.5'
778 raw = 'h1. Some heading related to version 0.5'
779 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
779 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
780 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
780 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
781
781
782 assert_equal expected, textilizable(raw)
782 assert_equal expected, textilizable(raw)
783 end
783 end
784
784
785 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
785 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
786 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
786 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
787 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
787 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
788
788
789 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
789 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
790
790
791 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
791 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
792 end
792 end
793
793
794 def test_table_of_content
794 def test_table_of_content
795 raw = <<-RAW
795 raw = <<-RAW
796 {{toc}}
796 {{toc}}
797
797
798 h1. Title
798 h1. Title
799
799
800 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
800 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
801
801
802 h2. Subtitle with a [[Wiki]] link
802 h2. Subtitle with a [[Wiki]] link
803
803
804 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
804 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
805
805
806 h2. Subtitle with [[Wiki|another Wiki]] link
806 h2. Subtitle with [[Wiki|another Wiki]] link
807
807
808 h2. Subtitle with %{color:red}red text%
808 h2. Subtitle with %{color:red}red text%
809
809
810 <pre>
810 <pre>
811 some code
811 some code
812 </pre>
812 </pre>
813
813
814 h3. Subtitle with *some* _modifiers_
814 h3. Subtitle with *some* _modifiers_
815
815
816 h3. Subtitle with @inline code@
816 h3. Subtitle with @inline code@
817
817
818 h1. Another title
818 h1. Another title
819
819
820 h3. An "Internet link":http://www.redmine.org/ inside subtitle
820 h3. An "Internet link":http://www.redmine.org/ inside subtitle
821
821
822 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
822 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
823
823
824 RAW
824 RAW
825
825
826 expected = '<ul class="toc">' +
826 expected = '<ul class="toc">' +
827 '<li><a href="#Title">Title</a>' +
827 '<li><a href="#Title">Title</a>' +
828 '<ul>' +
828 '<ul>' +
829 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
829 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
830 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
830 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
831 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
831 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
832 '<ul>' +
832 '<ul>' +
833 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
833 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
834 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
834 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
835 '</ul>' +
835 '</ul>' +
836 '</li>' +
836 '</li>' +
837 '</ul>' +
837 '</ul>' +
838 '</li>' +
838 '</li>' +
839 '<li><a href="#Another-title">Another title</a>' +
839 '<li><a href="#Another-title">Another title</a>' +
840 '<ul>' +
840 '<ul>' +
841 '<li>' +
841 '<li>' +
842 '<ul>' +
842 '<ul>' +
843 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
843 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
844 '</ul>' +
844 '</ul>' +
845 '</li>' +
845 '</li>' +
846 '<li><a href="#Project-Name">Project Name</a></li>' +
846 '<li><a href="#Project-Name">Project Name</a></li>' +
847 '</ul>' +
847 '</ul>' +
848 '</li>' +
848 '</li>' +
849 '</ul>'
849 '</ul>'
850
850
851 @project = Project.find(1)
851 @project = Project.find(1)
852 assert textilizable(raw).gsub("\n", "").include?(expected)
852 assert textilizable(raw).gsub("\n", "").include?(expected)
853 end
853 end
854
854
855 def test_table_of_content_should_generate_unique_anchors
855 def test_table_of_content_should_generate_unique_anchors
856 raw = <<-RAW
856 raw = <<-RAW
857 {{toc}}
857 {{toc}}
858
858
859 h1. Title
859 h1. Title
860
860
861 h2. Subtitle
861 h2. Subtitle
862
862
863 h2. Subtitle
863 h2. Subtitle
864 RAW
864 RAW
865
865
866 expected = '<ul class="toc">' +
866 expected = '<ul class="toc">' +
867 '<li><a href="#Title">Title</a>' +
867 '<li><a href="#Title">Title</a>' +
868 '<ul>' +
868 '<ul>' +
869 '<li><a href="#Subtitle">Subtitle</a></li>' +
869 '<li><a href="#Subtitle">Subtitle</a></li>' +
870 '<li><a href="#Subtitle-2">Subtitle</a></li>'
870 '<li><a href="#Subtitle-2">Subtitle</a></li>'
871 '</ul>'
871 '</ul>'
872 '</li>' +
872 '</li>' +
873 '</ul>'
873 '</ul>'
874
874
875 @project = Project.find(1)
875 @project = Project.find(1)
876 result = textilizable(raw).gsub("\n", "")
876 result = textilizable(raw).gsub("\n", "")
877 assert_include expected, result
877 assert_include expected, result
878 assert_include '<a name="Subtitle">', result
878 assert_include '<a name="Subtitle">', result
879 assert_include '<a name="Subtitle-2">', result
879 assert_include '<a name="Subtitle-2">', result
880 end
880 end
881
881
882 def test_table_of_content_should_contain_included_page_headings
882 def test_table_of_content_should_contain_included_page_headings
883 raw = <<-RAW
883 raw = <<-RAW
884 {{toc}}
884 {{toc}}
885
885
886 h1. Included
886 h1. Included
887
887
888 {{include(Child_1)}}
888 {{include(Child_1)}}
889 RAW
889 RAW
890
890
891 expected = '<ul class="toc">' +
891 expected = '<ul class="toc">' +
892 '<li><a href="#Included">Included</a></li>' +
892 '<li><a href="#Included">Included</a></li>' +
893 '<li><a href="#Child-page-1">Child page 1</a></li>' +
893 '<li><a href="#Child-page-1">Child page 1</a></li>' +
894 '</ul>'
894 '</ul>'
895
895
896 @project = Project.find(1)
896 @project = Project.find(1)
897 assert textilizable(raw).gsub("\n", "").include?(expected)
897 assert textilizable(raw).gsub("\n", "").include?(expected)
898 end
898 end
899
899
900 def test_section_edit_links
900 def test_section_edit_links
901 raw = <<-RAW
901 raw = <<-RAW
902 h1. Title
902 h1. Title
903
903
904 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
904 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
905
905
906 h2. Subtitle with a [[Wiki]] link
906 h2. Subtitle with a [[Wiki]] link
907
907
908 h2. Subtitle with *some* _modifiers_
908 h2. Subtitle with *some* _modifiers_
909
909
910 h2. Subtitle with @inline code@
910 h2. Subtitle with @inline code@
911
911
912 <pre>
912 <pre>
913 some code
913 some code
914
914
915 h2. heading inside pre
915 h2. heading inside pre
916
916
917 <h2>html heading inside pre</h2>
917 <h2>html heading inside pre</h2>
918 </pre>
918 </pre>
919
919
920 h2. Subtitle after pre tag
920 h2. Subtitle after pre tag
921 RAW
921 RAW
922
922
923 @project = Project.find(1)
923 @project = Project.find(1)
924 set_language_if_valid 'en'
924 set_language_if_valid 'en'
925 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
925 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
926
926
927 # heading that contains inline code
927 # heading that contains inline code
928 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
928 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
929 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
929 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
930 '<a name="Subtitle-with-inline-code"></a>' +
930 '<a name="Subtitle-with-inline-code"></a>' +
931 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
931 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
932 result
932 result
933
933
934 # last heading
934 # last heading
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=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
936 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
937 '<a name="Subtitle-after-pre-tag"></a>' +
937 '<a name="Subtitle-after-pre-tag"></a>' +
938 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
938 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
939 result
939 result
940 end
940 end
941
941
942 def test_default_formatter
942 def test_default_formatter
943 with_settings :text_formatting => 'unknown' do
943 with_settings :text_formatting => 'unknown' do
944 text = 'a *link*: http://www.example.net/'
944 text = 'a *link*: http://www.example.net/'
945 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
945 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
946 end
946 end
947 end
947 end
948
948
949 def test_due_date_distance_in_words
949 def test_due_date_distance_in_words
950 to_test = { Date.today => 'Due in 0 days',
950 to_test = { Date.today => 'Due in 0 days',
951 Date.today + 1 => 'Due in 1 day',
951 Date.today + 1 => 'Due in 1 day',
952 Date.today + 100 => 'Due in about 3 months',
952 Date.today + 100 => 'Due in about 3 months',
953 Date.today + 20000 => 'Due in over 54 years',
953 Date.today + 20000 => 'Due in over 54 years',
954 Date.today - 1 => '1 day late',
954 Date.today - 1 => '1 day late',
955 Date.today - 100 => 'about 3 months late',
955 Date.today - 100 => 'about 3 months late',
956 Date.today - 20000 => 'over 54 years late',
956 Date.today - 20000 => 'over 54 years late',
957 }
957 }
958 ::I18n.locale = :en
958 ::I18n.locale = :en
959 to_test.each do |date, expected|
959 to_test.each do |date, expected|
960 assert_equal expected, due_date_distance_in_words(date)
960 assert_equal expected, due_date_distance_in_words(date)
961 end
961 end
962 end
962 end
963
963
964 def test_avatar
964 def test_avatar
965 # turn on avatars
965 # turn on avatars
966 Setting.gravatar_enabled = '1'
966 Setting.gravatar_enabled = '1'
967 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
967 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
968 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
968 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
969 assert_nil avatar('jsmith')
969 assert_nil avatar('jsmith')
970 assert_nil avatar(nil)
970 assert_nil avatar(nil)
971
971
972 # turn off avatars
972 # turn off avatars
973 Setting.gravatar_enabled = '0'
973 Setting.gravatar_enabled = '0'
974 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
974 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
975 end
975 end
976
976
977 def test_link_to_user
977 def test_link_to_user
978 user = User.find(2)
978 user = User.find(2)
979 t = link_to_user(user)
979 t = link_to_user(user)
980 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
980 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
981 end
981 end
982
982
983 def test_link_to_user_should_not_link_to_locked_user
983 def test_link_to_user_should_not_link_to_locked_user
984 user = User.find(5)
984 user = User.find(5)
985 assert user.locked?
985 assert user.locked?
986 t = link_to_user(user)
986 t = link_to_user(user)
987 assert_equal user.name, t
987 assert_equal user.name, t
988 end
988 end
989
989
990 def test_link_to_user_should_not_link_to_anonymous
990 def test_link_to_user_should_not_link_to_anonymous
991 user = User.anonymous
991 user = User.anonymous
992 assert user.anonymous?
992 assert user.anonymous?
993 t = link_to_user(user)
993 t = link_to_user(user)
994 assert_equal ::I18n.t(:label_user_anonymous), t
994 assert_equal ::I18n.t(:label_user_anonymous), t
995 end
995 end
996
996
997 def test_link_to_project
997 def test_link_to_project
998 project = Project.find(1)
998 project = Project.find(1)
999 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
999 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1000 link_to_project(project)
1000 link_to_project(project)
1001 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1001 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1002 link_to_project(project, :action => 'settings')
1002 link_to_project(project, :action => 'settings')
1003 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1003 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1004 link_to_project(project, {:only_path => false, :jump => 'blah'})
1004 link_to_project(project, {:only_path => false, :jump => 'blah'})
1005 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
1005 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
1006 link_to_project(project, {:action => 'settings'}, :class => "project")
1006 link_to_project(project, {:action => 'settings'}, :class => "project")
1007 end
1007 end
1008
1008
1009 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1009 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1010 # numeric identifier are no longer allowed
1010 # numeric identifier are no longer allowed
1011 Project.update_all "identifier=25", "id=1"
1011 Project.update_all "identifier=25", "id=1"
1012
1012
1013 assert_equal '<a href="/projects/1">eCookbook</a>',
1013 assert_equal '<a href="/projects/1">eCookbook</a>',
1014 link_to_project(Project.find(1))
1014 link_to_project(Project.find(1))
1015 end
1015 end
1016
1016
1017 def test_principals_options_for_select_with_users
1017 def test_principals_options_for_select_with_users
1018 User.current = nil
1018 User.current = nil
1019 users = [User.find(2), User.find(4)]
1019 users = [User.find(2), User.find(4)]
1020 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1020 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1021 principals_options_for_select(users)
1021 principals_options_for_select(users)
1022 end
1022 end
1023
1023
1024 def test_principals_options_for_select_with_selected
1024 def test_principals_options_for_select_with_selected
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" selected="selected">Robert Hill</option>),
1027 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1028 principals_options_for_select(users, User.find(4))
1028 principals_options_for_select(users, User.find(4))
1029 end
1029 end
1030
1030
1031 def test_principals_options_for_select_with_users_and_groups
1031 def test_principals_options_for_select_with_users_and_groups
1032 User.current = nil
1032 User.current = nil
1033 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1033 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1034 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1034 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1035 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1035 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1036 principals_options_for_select(users)
1036 principals_options_for_select(users)
1037 end
1037 end
1038
1038
1039 def test_principals_options_for_select_with_empty_collection
1039 def test_principals_options_for_select_with_empty_collection
1040 assert_equal '', principals_options_for_select([])
1040 assert_equal '', principals_options_for_select([])
1041 end
1041 end
1042
1042
1043 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1043 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1044 users = [User.find(2), User.find(4)]
1044 users = [User.find(2), User.find(4)]
1045 User.current = User.find(4)
1045 User.current = User.find(4)
1046 assert_include '<option value="4"><< me >></option>', principals_options_for_select(users)
1046 assert_include '<option value="4"><< me >></option>', principals_options_for_select(users)
1047 end
1047 end
1048
1048
1049 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1049 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1050 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1050 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1051 end
1051 end
1052
1052
1053 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1053 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1054 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1054 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1055 end
1055 end
1056
1056
1057 def test_image_tag_should_pick_the_default_image
1058 assert_match 'src="/images/image.png"', image_tag("image.png")
1059 end
1060
1061 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1062 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1063 end
1064
1057 def test_javascript_include_tag_should_pick_the_default_javascript
1065 def test_javascript_include_tag_should_pick_the_default_javascript
1058 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1066 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1059 end
1067 end
1060
1068
1061 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1069 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1062 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1070 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1063 end
1071 end
1064 end
1072 end
General Comments 0
You need to be logged in to leave comments. Login now