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