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