##// END OF EJS Templates
Merged r4064 from trunk....
Eric Davis -
r4020:00423307ce77
parent child
Show More
@@ -1,838 +1,857
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'forwardable'
18 require 'forwardable'
19 require 'cgi'
19 require 'cgi'
20
20
21 module ApplicationHelper
21 module ApplicationHelper
22 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
23 include Redmine::I18n
23 include Redmine::I18n
24 include GravatarHelper::PublicMethods
24 include GravatarHelper::PublicMethods
25
25
26 extend Forwardable
26 extend Forwardable
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
27 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28
28
29 # Return true if user is authorized for controller/action, otherwise false
29 # Return true if user is authorized for controller/action, otherwise false
30 def authorize_for(controller, action)
30 def authorize_for(controller, action)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
31 User.current.allowed_to?({:controller => controller, :action => action}, @project)
32 end
32 end
33
33
34 # Display a link if user is authorized
34 # Display a link if user is authorized
35 #
36 # @param [String] name Anchor text (passed to link_to)
37 # @param [Hash, String] options Hash params or url for the link target (passed to link_to).
38 # This will checked by authorize_for to see if the user is authorized
39 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
35 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
36 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 if options.is_a?(String)
43 begin
44 route = ActionController::Routing::Routes.recognize_path(options.gsub(/\?.*/,''), :method => options[:method] || :get)
45 link_controller = route[:controller]
46 link_action = route[:action]
47 rescue ActionController::RoutingError # Parse failed, not a route
48 link_controller, link_action = nil, nil
49 end
50 else
51 link_controller = options[:controller] || params[:controller]
52 link_action = options[:action]
53 end
54
55 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(link_controller, link_action)
37 end
56 end
38
57
39 # Display a link to remote if user is authorized
58 # Display a link to remote if user is authorized
40 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
59 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
41 url = options[:url] || {}
60 url = options[:url] || {}
42 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
61 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
43 end
62 end
44
63
45 # Displays a link to user's account page if active
64 # Displays a link to user's account page if active
46 def link_to_user(user, options={})
65 def link_to_user(user, options={})
47 if user.is_a?(User)
66 if user.is_a?(User)
48 name = h(user.name(options[:format]))
67 name = h(user.name(options[:format]))
49 if user.active?
68 if user.active?
50 link_to name, :controller => 'users', :action => 'show', :id => user
69 link_to name, :controller => 'users', :action => 'show', :id => user
51 else
70 else
52 name
71 name
53 end
72 end
54 else
73 else
55 h(user.to_s)
74 h(user.to_s)
56 end
75 end
57 end
76 end
58
77
59 # Displays a link to +issue+ with its subject.
78 # Displays a link to +issue+ with its subject.
60 # Examples:
79 # Examples:
61 #
80 #
62 # link_to_issue(issue) # => Defect #6: This is the subject
81 # link_to_issue(issue) # => Defect #6: This is the subject
63 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
82 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
64 # link_to_issue(issue, :subject => false) # => Defect #6
83 # link_to_issue(issue, :subject => false) # => Defect #6
65 # link_to_issue(issue, :project => true) # => Foo - Defect #6
84 # link_to_issue(issue, :project => true) # => Foo - Defect #6
66 #
85 #
67 def link_to_issue(issue, options={})
86 def link_to_issue(issue, options={})
68 title = nil
87 title = nil
69 subject = nil
88 subject = nil
70 if options[:subject] == false
89 if options[:subject] == false
71 title = truncate(issue.subject, :length => 60)
90 title = truncate(issue.subject, :length => 60)
72 else
91 else
73 subject = issue.subject
92 subject = issue.subject
74 if options[:truncate]
93 if options[:truncate]
75 subject = truncate(subject, :length => options[:truncate])
94 subject = truncate(subject, :length => options[:truncate])
76 end
95 end
77 end
96 end
78 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
97 s = link_to "#{issue.tracker} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
79 :class => issue.css_classes,
98 :class => issue.css_classes,
80 :title => title
99 :title => title
81 s << ": #{h subject}" if subject
100 s << ": #{h subject}" if subject
82 s = "#{h issue.project} - " + s if options[:project]
101 s = "#{h issue.project} - " + s if options[:project]
83 s
102 s
84 end
103 end
85
104
86 # Generates a link to an attachment.
105 # Generates a link to an attachment.
87 # Options:
106 # Options:
88 # * :text - Link text (default to attachment filename)
107 # * :text - Link text (default to attachment filename)
89 # * :download - Force download (default: false)
108 # * :download - Force download (default: false)
90 def link_to_attachment(attachment, options={})
109 def link_to_attachment(attachment, options={})
91 text = options.delete(:text) || attachment.filename
110 text = options.delete(:text) || attachment.filename
92 action = options.delete(:download) ? 'download' : 'show'
111 action = options.delete(:download) ? 'download' : 'show'
93
112
94 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
113 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
95 end
114 end
96
115
97 # Generates a link to a SCM revision
116 # Generates a link to a SCM revision
98 # Options:
117 # Options:
99 # * :text - Link text (default to the formatted revision)
118 # * :text - Link text (default to the formatted revision)
100 def link_to_revision(revision, project, options={})
119 def link_to_revision(revision, project, options={})
101 text = options.delete(:text) || format_revision(revision)
120 text = options.delete(:text) || format_revision(revision)
102
121
103 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
122 link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
104 end
123 end
105
124
106 # Generates a link to a project if active
125 # Generates a link to a project if active
107 # Examples:
126 # Examples:
108 #
127 #
109 # link_to_project(project) # => link to the specified project overview
128 # link_to_project(project) # => link to the specified project overview
110 # link_to_project(project, :action=>'settings') # => link to project settings
129 # link_to_project(project, :action=>'settings') # => link to project settings
111 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
130 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
112 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
131 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
113 #
132 #
114 def link_to_project(project, options={}, html_options = nil)
133 def link_to_project(project, options={}, html_options = nil)
115 if project.active?
134 if project.active?
116 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
135 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
117 link_to(h(project), url, html_options)
136 link_to(h(project), url, html_options)
118 else
137 else
119 h(project)
138 h(project)
120 end
139 end
121 end
140 end
122
141
123 def toggle_link(name, id, options={})
142 def toggle_link(name, id, options={})
124 onclick = "Element.toggle('#{id}'); "
143 onclick = "Element.toggle('#{id}'); "
125 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
144 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
126 onclick << "return false;"
145 onclick << "return false;"
127 link_to(name, "#", :onclick => onclick)
146 link_to(name, "#", :onclick => onclick)
128 end
147 end
129
148
130 def image_to_function(name, function, html_options = {})
149 def image_to_function(name, function, html_options = {})
131 html_options.symbolize_keys!
150 html_options.symbolize_keys!
132 tag(:input, html_options.merge({
151 tag(:input, html_options.merge({
133 :type => "image", :src => image_path(name),
152 :type => "image", :src => image_path(name),
134 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
153 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
135 }))
154 }))
136 end
155 end
137
156
138 def prompt_to_remote(name, text, param, url, html_options = {})
157 def prompt_to_remote(name, text, param, url, html_options = {})
139 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
158 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
140 link_to name, {}, html_options
159 link_to name, {}, html_options
141 end
160 end
142
161
143 def format_activity_title(text)
162 def format_activity_title(text)
144 h(truncate_single_line(text, :length => 100))
163 h(truncate_single_line(text, :length => 100))
145 end
164 end
146
165
147 def format_activity_day(date)
166 def format_activity_day(date)
148 date == Date.today ? l(:label_today).titleize : format_date(date)
167 date == Date.today ? l(:label_today).titleize : format_date(date)
149 end
168 end
150
169
151 def format_activity_description(text)
170 def format_activity_description(text)
152 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
171 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
153 end
172 end
154
173
155 def format_version_name(version)
174 def format_version_name(version)
156 if version.project == @project
175 if version.project == @project
157 h(version)
176 h(version)
158 else
177 else
159 h("#{version.project} - #{version}")
178 h("#{version.project} - #{version}")
160 end
179 end
161 end
180 end
162
181
163 def due_date_distance_in_words(date)
182 def due_date_distance_in_words(date)
164 if date
183 if date
165 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
184 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
166 end
185 end
167 end
186 end
168
187
169 def render_page_hierarchy(pages, node=nil)
188 def render_page_hierarchy(pages, node=nil)
170 content = ''
189 content = ''
171 if pages[node]
190 if pages[node]
172 content << "<ul class=\"pages-hierarchy\">\n"
191 content << "<ul class=\"pages-hierarchy\">\n"
173 pages[node].each do |page|
192 pages[node].each do |page|
174 content << "<li>"
193 content << "<li>"
175 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
194 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
176 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
195 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
177 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
196 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
178 content << "</li>\n"
197 content << "</li>\n"
179 end
198 end
180 content << "</ul>\n"
199 content << "</ul>\n"
181 end
200 end
182 content
201 content
183 end
202 end
184
203
185 # Renders flash messages
204 # Renders flash messages
186 def render_flash_messages
205 def render_flash_messages
187 s = ''
206 s = ''
188 flash.each do |k,v|
207 flash.each do |k,v|
189 s << content_tag('div', v, :class => "flash #{k}")
208 s << content_tag('div', v, :class => "flash #{k}")
190 end
209 end
191 s
210 s
192 end
211 end
193
212
194 # Renders tabs and their content
213 # Renders tabs and their content
195 def render_tabs(tabs)
214 def render_tabs(tabs)
196 if tabs.any?
215 if tabs.any?
197 render :partial => 'common/tabs', :locals => {:tabs => tabs}
216 render :partial => 'common/tabs', :locals => {:tabs => tabs}
198 else
217 else
199 content_tag 'p', l(:label_no_data), :class => "nodata"
218 content_tag 'p', l(:label_no_data), :class => "nodata"
200 end
219 end
201 end
220 end
202
221
203 # Renders the project quick-jump box
222 # Renders the project quick-jump box
204 def render_project_jump_box
223 def render_project_jump_box
205 # Retrieve them now to avoid a COUNT query
224 # Retrieve them now to avoid a COUNT query
206 projects = User.current.projects.all
225 projects = User.current.projects.all
207 if projects.any?
226 if projects.any?
208 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
227 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
209 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
228 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
210 '<option value="" disabled="disabled">---</option>'
229 '<option value="" disabled="disabled">---</option>'
211 s << project_tree_options_for_select(projects, :selected => @project) do |p|
230 s << project_tree_options_for_select(projects, :selected => @project) do |p|
212 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
231 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
213 end
232 end
214 s << '</select>'
233 s << '</select>'
215 s
234 s
216 end
235 end
217 end
236 end
218
237
219 def project_tree_options_for_select(projects, options = {})
238 def project_tree_options_for_select(projects, options = {})
220 s = ''
239 s = ''
221 project_tree(projects) do |project, level|
240 project_tree(projects) do |project, level|
222 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
241 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
223 tag_options = {:value => project.id}
242 tag_options = {:value => project.id}
224 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
243 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
225 tag_options[:selected] = 'selected'
244 tag_options[:selected] = 'selected'
226 else
245 else
227 tag_options[:selected] = nil
246 tag_options[:selected] = nil
228 end
247 end
229 tag_options.merge!(yield(project)) if block_given?
248 tag_options.merge!(yield(project)) if block_given?
230 s << content_tag('option', name_prefix + h(project), tag_options)
249 s << content_tag('option', name_prefix + h(project), tag_options)
231 end
250 end
232 s
251 s
233 end
252 end
234
253
235 # Yields the given block for each project with its level in the tree
254 # Yields the given block for each project with its level in the tree
236 def project_tree(projects, &block)
255 def project_tree(projects, &block)
237 ancestors = []
256 ancestors = []
238 projects.sort_by(&:lft).each do |project|
257 projects.sort_by(&:lft).each do |project|
239 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
258 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
240 ancestors.pop
259 ancestors.pop
241 end
260 end
242 yield project, ancestors.size
261 yield project, ancestors.size
243 ancestors << project
262 ancestors << project
244 end
263 end
245 end
264 end
246
265
247 def project_nested_ul(projects, &block)
266 def project_nested_ul(projects, &block)
248 s = ''
267 s = ''
249 if projects.any?
268 if projects.any?
250 ancestors = []
269 ancestors = []
251 projects.sort_by(&:lft).each do |project|
270 projects.sort_by(&:lft).each do |project|
252 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
271 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
253 s << "<ul>\n"
272 s << "<ul>\n"
254 else
273 else
255 ancestors.pop
274 ancestors.pop
256 s << "</li>"
275 s << "</li>"
257 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
276 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
258 ancestors.pop
277 ancestors.pop
259 s << "</ul></li>\n"
278 s << "</ul></li>\n"
260 end
279 end
261 end
280 end
262 s << "<li>"
281 s << "<li>"
263 s << yield(project).to_s
282 s << yield(project).to_s
264 ancestors << project
283 ancestors << project
265 end
284 end
266 s << ("</li></ul>\n" * ancestors.size)
285 s << ("</li></ul>\n" * ancestors.size)
267 end
286 end
268 s
287 s
269 end
288 end
270
289
271 def principals_check_box_tags(name, principals)
290 def principals_check_box_tags(name, principals)
272 s = ''
291 s = ''
273 principals.sort.each do |principal|
292 principals.sort.each do |principal|
274 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
293 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
275 end
294 end
276 s
295 s
277 end
296 end
278
297
279 # Truncates and returns the string as a single line
298 # Truncates and returns the string as a single line
280 def truncate_single_line(string, *args)
299 def truncate_single_line(string, *args)
281 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
300 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
282 end
301 end
283
302
284 # Truncates at line break after 250 characters or options[:length]
303 # Truncates at line break after 250 characters or options[:length]
285 def truncate_lines(string, options={})
304 def truncate_lines(string, options={})
286 length = options[:length] || 250
305 length = options[:length] || 250
287 if string.to_s =~ /\A(.{#{length}}.*?)$/m
306 if string.to_s =~ /\A(.{#{length}}.*?)$/m
288 "#{$1}..."
307 "#{$1}..."
289 else
308 else
290 string
309 string
291 end
310 end
292 end
311 end
293
312
294 def html_hours(text)
313 def html_hours(text)
295 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
314 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
296 end
315 end
297
316
298 def authoring(created, author, options={})
317 def authoring(created, author, options={})
299 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
318 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
300 end
319 end
301
320
302 def time_tag(time)
321 def time_tag(time)
303 text = distance_of_time_in_words(Time.now, time)
322 text = distance_of_time_in_words(Time.now, time)
304 if @project
323 if @project
305 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
324 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
306 else
325 else
307 content_tag('acronym', text, :title => format_time(time))
326 content_tag('acronym', text, :title => format_time(time))
308 end
327 end
309 end
328 end
310
329
311 def syntax_highlight(name, content)
330 def syntax_highlight(name, content)
312 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
331 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
313 end
332 end
314
333
315 def to_path_param(path)
334 def to_path_param(path)
316 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
335 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
317 end
336 end
318
337
319 def pagination_links_full(paginator, count=nil, options={})
338 def pagination_links_full(paginator, count=nil, options={})
320 page_param = options.delete(:page_param) || :page
339 page_param = options.delete(:page_param) || :page
321 per_page_links = options.delete(:per_page_links)
340 per_page_links = options.delete(:per_page_links)
322 url_param = params.dup
341 url_param = params.dup
323 # don't reuse query params if filters are present
342 # don't reuse query params if filters are present
324 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
343 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
325
344
326 html = ''
345 html = ''
327 if paginator.current.previous
346 if paginator.current.previous
328 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
347 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
329 end
348 end
330
349
331 html << (pagination_links_each(paginator, options) do |n|
350 html << (pagination_links_each(paginator, options) do |n|
332 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
351 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
333 end || '')
352 end || '')
334
353
335 if paginator.current.next
354 if paginator.current.next
336 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
355 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
337 end
356 end
338
357
339 unless count.nil?
358 unless count.nil?
340 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
359 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
341 if per_page_links != false && links = per_page_links(paginator.items_per_page)
360 if per_page_links != false && links = per_page_links(paginator.items_per_page)
342 html << " | #{links}"
361 html << " | #{links}"
343 end
362 end
344 end
363 end
345
364
346 html
365 html
347 end
366 end
348
367
349 def per_page_links(selected=nil)
368 def per_page_links(selected=nil)
350 url_param = params.dup
369 url_param = params.dup
351 url_param.clear if url_param.has_key?(:set_filter)
370 url_param.clear if url_param.has_key?(:set_filter)
352
371
353 links = Setting.per_page_options_array.collect do |n|
372 links = Setting.per_page_options_array.collect do |n|
354 n == selected ? n : link_to_remote(n, {:update => "content",
373 n == selected ? n : link_to_remote(n, {:update => "content",
355 :url => params.dup.merge(:per_page => n),
374 :url => params.dup.merge(:per_page => n),
356 :method => :get},
375 :method => :get},
357 {:href => url_for(url_param.merge(:per_page => n))})
376 {:href => url_for(url_param.merge(:per_page => n))})
358 end
377 end
359 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
378 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
360 end
379 end
361
380
362 def reorder_links(name, url)
381 def reorder_links(name, url)
363 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
382 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
364 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
383 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
365 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
384 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
366 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
385 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
367 end
386 end
368
387
369 def breadcrumb(*args)
388 def breadcrumb(*args)
370 elements = args.flatten
389 elements = args.flatten
371 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
390 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
372 end
391 end
373
392
374 def other_formats_links(&block)
393 def other_formats_links(&block)
375 concat('<p class="other-formats">' + l(:label_export_to))
394 concat('<p class="other-formats">' + l(:label_export_to))
376 yield Redmine::Views::OtherFormatsBuilder.new(self)
395 yield Redmine::Views::OtherFormatsBuilder.new(self)
377 concat('</p>')
396 concat('</p>')
378 end
397 end
379
398
380 def page_header_title
399 def page_header_title
381 if @project.nil? || @project.new_record?
400 if @project.nil? || @project.new_record?
382 h(Setting.app_title)
401 h(Setting.app_title)
383 else
402 else
384 b = []
403 b = []
385 ancestors = (@project.root? ? [] : @project.ancestors.visible)
404 ancestors = (@project.root? ? [] : @project.ancestors.visible)
386 if ancestors.any?
405 if ancestors.any?
387 root = ancestors.shift
406 root = ancestors.shift
388 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
407 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
389 if ancestors.size > 2
408 if ancestors.size > 2
390 b << '&#8230;'
409 b << '&#8230;'
391 ancestors = ancestors[-2, 2]
410 ancestors = ancestors[-2, 2]
392 end
411 end
393 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
412 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
394 end
413 end
395 b << h(@project)
414 b << h(@project)
396 b.join(' &#187; ')
415 b.join(' &#187; ')
397 end
416 end
398 end
417 end
399
418
400 def html_title(*args)
419 def html_title(*args)
401 if args.empty?
420 if args.empty?
402 title = []
421 title = []
403 title << @project.name if @project
422 title << @project.name if @project
404 title += @html_title if @html_title
423 title += @html_title if @html_title
405 title << Setting.app_title
424 title << Setting.app_title
406 title.select {|t| !t.blank? }.join(' - ')
425 title.select {|t| !t.blank? }.join(' - ')
407 else
426 else
408 @html_title ||= []
427 @html_title ||= []
409 @html_title += args
428 @html_title += args
410 end
429 end
411 end
430 end
412
431
413 # Returns the theme, controller name, and action as css classes for the
432 # Returns the theme, controller name, and action as css classes for the
414 # HTML body.
433 # HTML body.
415 def body_css_classes
434 def body_css_classes
416 css = []
435 css = []
417 if theme = Redmine::Themes.theme(Setting.ui_theme)
436 if theme = Redmine::Themes.theme(Setting.ui_theme)
418 css << 'theme-' + theme.name
437 css << 'theme-' + theme.name
419 end
438 end
420
439
421 css << 'controller-' + params[:controller]
440 css << 'controller-' + params[:controller]
422 css << 'action-' + params[:action]
441 css << 'action-' + params[:action]
423 css.join(' ')
442 css.join(' ')
424 end
443 end
425
444
426 def accesskey(s)
445 def accesskey(s)
427 Redmine::AccessKeys.key_for s
446 Redmine::AccessKeys.key_for s
428 end
447 end
429
448
430 # Formats text according to system settings.
449 # Formats text according to system settings.
431 # 2 ways to call this method:
450 # 2 ways to call this method:
432 # * with a String: textilizable(text, options)
451 # * with a String: textilizable(text, options)
433 # * with an object and one of its attribute: textilizable(issue, :description, options)
452 # * with an object and one of its attribute: textilizable(issue, :description, options)
434 def textilizable(*args)
453 def textilizable(*args)
435 options = args.last.is_a?(Hash) ? args.pop : {}
454 options = args.last.is_a?(Hash) ? args.pop : {}
436 case args.size
455 case args.size
437 when 1
456 when 1
438 obj = options[:object]
457 obj = options[:object]
439 text = args.shift
458 text = args.shift
440 when 2
459 when 2
441 obj = args.shift
460 obj = args.shift
442 attr = args.shift
461 attr = args.shift
443 text = obj.send(attr).to_s
462 text = obj.send(attr).to_s
444 else
463 else
445 raise ArgumentError, 'invalid arguments to textilizable'
464 raise ArgumentError, 'invalid arguments to textilizable'
446 end
465 end
447 return '' if text.blank?
466 return '' if text.blank?
448 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
467 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
449 only_path = options.delete(:only_path) == false ? false : true
468 only_path = options.delete(:only_path) == false ? false : true
450
469
451 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
470 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
452
471
453 parse_non_pre_blocks(text) do |text|
472 parse_non_pre_blocks(text) do |text|
454 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
473 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
455 send method_name, text, project, obj, attr, only_path, options
474 send method_name, text, project, obj, attr, only_path, options
456 end
475 end
457 end
476 end
458 end
477 end
459
478
460 def parse_non_pre_blocks(text)
479 def parse_non_pre_blocks(text)
461 s = StringScanner.new(text)
480 s = StringScanner.new(text)
462 tags = []
481 tags = []
463 parsed = ''
482 parsed = ''
464 while !s.eos?
483 while !s.eos?
465 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
484 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
466 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
485 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
467 if tags.empty?
486 if tags.empty?
468 yield text
487 yield text
469 end
488 end
470 parsed << text
489 parsed << text
471 if tag
490 if tag
472 if closing
491 if closing
473 if tags.last == tag.downcase
492 if tags.last == tag.downcase
474 tags.pop
493 tags.pop
475 end
494 end
476 else
495 else
477 tags << tag.downcase
496 tags << tag.downcase
478 end
497 end
479 parsed << full_tag
498 parsed << full_tag
480 end
499 end
481 end
500 end
482 # Close any non closing tags
501 # Close any non closing tags
483 while tag = tags.pop
502 while tag = tags.pop
484 parsed << "</#{tag}>"
503 parsed << "</#{tag}>"
485 end
504 end
486 parsed
505 parsed
487 end
506 end
488
507
489 def parse_inline_attachments(text, project, obj, attr, only_path, options)
508 def parse_inline_attachments(text, project, obj, attr, only_path, options)
490 # when using an image link, try to use an attachment, if possible
509 # when using an image link, try to use an attachment, if possible
491 if options[:attachments] || (obj && obj.respond_to?(:attachments))
510 if options[:attachments] || (obj && obj.respond_to?(:attachments))
492 attachments = nil
511 attachments = nil
493 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
512 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
494 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
513 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
495 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
514 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
496 # search for the picture in attachments
515 # search for the picture in attachments
497 if found = attachments.detect { |att| att.filename.downcase == filename }
516 if found = attachments.detect { |att| att.filename.downcase == filename }
498 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
517 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
499 desc = found.description.to_s.gsub('"', '')
518 desc = found.description.to_s.gsub('"', '')
500 if !desc.blank? && alttext.blank?
519 if !desc.blank? && alttext.blank?
501 alt = " title=\"#{desc}\" alt=\"#{desc}\""
520 alt = " title=\"#{desc}\" alt=\"#{desc}\""
502 end
521 end
503 "src=\"#{image_url}\"#{alt}"
522 "src=\"#{image_url}\"#{alt}"
504 else
523 else
505 m
524 m
506 end
525 end
507 end
526 end
508 end
527 end
509 end
528 end
510
529
511 # Wiki links
530 # Wiki links
512 #
531 #
513 # Examples:
532 # Examples:
514 # [[mypage]]
533 # [[mypage]]
515 # [[mypage|mytext]]
534 # [[mypage|mytext]]
516 # wiki links can refer other project wikis, using project name or identifier:
535 # wiki links can refer other project wikis, using project name or identifier:
517 # [[project:]] -> wiki starting page
536 # [[project:]] -> wiki starting page
518 # [[project:|mytext]]
537 # [[project:|mytext]]
519 # [[project:mypage]]
538 # [[project:mypage]]
520 # [[project:mypage|mytext]]
539 # [[project:mypage|mytext]]
521 def parse_wiki_links(text, project, obj, attr, only_path, options)
540 def parse_wiki_links(text, project, obj, attr, only_path, options)
522 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
541 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
523 link_project = project
542 link_project = project
524 esc, all, page, title = $1, $2, $3, $5
543 esc, all, page, title = $1, $2, $3, $5
525 if esc.nil?
544 if esc.nil?
526 if page =~ /^([^\:]+)\:(.*)$/
545 if page =~ /^([^\:]+)\:(.*)$/
527 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
546 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
528 page = $2
547 page = $2
529 title ||= $1 if page.blank?
548 title ||= $1 if page.blank?
530 end
549 end
531
550
532 if link_project && link_project.wiki
551 if link_project && link_project.wiki
533 # extract anchor
552 # extract anchor
534 anchor = nil
553 anchor = nil
535 if page =~ /^(.+?)\#(.+)$/
554 if page =~ /^(.+?)\#(.+)$/
536 page, anchor = $1, $2
555 page, anchor = $1, $2
537 end
556 end
538 # check if page exists
557 # check if page exists
539 wiki_page = link_project.wiki.find_page(page)
558 wiki_page = link_project.wiki.find_page(page)
540 url = case options[:wiki_links]
559 url = case options[:wiki_links]
541 when :local; "#{title}.html"
560 when :local; "#{title}.html"
542 when :anchor; "##{title}" # used for single-file wiki export
561 when :anchor; "##{title}" # used for single-file wiki export
543 else
562 else
544 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
563 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
545 end
564 end
546 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
565 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
547 else
566 else
548 # project or wiki doesn't exist
567 # project or wiki doesn't exist
549 all
568 all
550 end
569 end
551 else
570 else
552 all
571 all
553 end
572 end
554 end
573 end
555 end
574 end
556
575
557 # Redmine links
576 # Redmine links
558 #
577 #
559 # Examples:
578 # Examples:
560 # Issues:
579 # Issues:
561 # #52 -> Link to issue #52
580 # #52 -> Link to issue #52
562 # Changesets:
581 # Changesets:
563 # r52 -> Link to revision 52
582 # r52 -> Link to revision 52
564 # commit:a85130f -> Link to scmid starting with a85130f
583 # commit:a85130f -> Link to scmid starting with a85130f
565 # Documents:
584 # Documents:
566 # document#17 -> Link to document with id 17
585 # document#17 -> Link to document with id 17
567 # document:Greetings -> Link to the document with title "Greetings"
586 # document:Greetings -> Link to the document with title "Greetings"
568 # document:"Some document" -> Link to the document with title "Some document"
587 # document:"Some document" -> Link to the document with title "Some document"
569 # Versions:
588 # Versions:
570 # version#3 -> Link to version with id 3
589 # version#3 -> Link to version with id 3
571 # version:1.0.0 -> Link to version named "1.0.0"
590 # version:1.0.0 -> Link to version named "1.0.0"
572 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
591 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
573 # Attachments:
592 # Attachments:
574 # attachment:file.zip -> Link to the attachment of the current object named file.zip
593 # attachment:file.zip -> Link to the attachment of the current object named file.zip
575 # Source files:
594 # Source files:
576 # source:some/file -> Link to the file located at /some/file in the project's repository
595 # source:some/file -> Link to the file located at /some/file in the project's repository
577 # source:some/file@52 -> Link to the file's revision 52
596 # source:some/file@52 -> Link to the file's revision 52
578 # source:some/file#L120 -> Link to line 120 of the file
597 # source:some/file#L120 -> Link to line 120 of the file
579 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
598 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
580 # export:some/file -> Force the download of the file
599 # export:some/file -> Force the download of the file
581 # Forum messages:
600 # Forum messages:
582 # message#1218 -> Link to message with id 1218
601 # message#1218 -> Link to message with id 1218
583 def parse_redmine_links(text, project, obj, attr, only_path, options)
602 def parse_redmine_links(text, project, obj, attr, only_path, options)
584 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
603 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
585 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
604 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
586 link = nil
605 link = nil
587 if esc.nil?
606 if esc.nil?
588 if prefix.nil? && sep == 'r'
607 if prefix.nil? && sep == 'r'
589 if project && (changeset = project.changesets.find_by_revision(identifier))
608 if project && (changeset = project.changesets.find_by_revision(identifier))
590 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
609 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
591 :class => 'changeset',
610 :class => 'changeset',
592 :title => truncate_single_line(changeset.comments, :length => 100))
611 :title => truncate_single_line(changeset.comments, :length => 100))
593 end
612 end
594 elsif sep == '#'
613 elsif sep == '#'
595 oid = identifier.to_i
614 oid = identifier.to_i
596 case prefix
615 case prefix
597 when nil
616 when nil
598 if issue = Issue.visible.find_by_id(oid, :include => :status)
617 if issue = Issue.visible.find_by_id(oid, :include => :status)
599 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
618 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
600 :class => issue.css_classes,
619 :class => issue.css_classes,
601 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
620 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
602 end
621 end
603 when 'document'
622 when 'document'
604 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
623 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
605 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
624 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
606 :class => 'document'
625 :class => 'document'
607 end
626 end
608 when 'version'
627 when 'version'
609 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
628 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
610 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
629 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
611 :class => 'version'
630 :class => 'version'
612 end
631 end
613 when 'message'
632 when 'message'
614 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
633 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
615 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
634 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
616 :controller => 'messages',
635 :controller => 'messages',
617 :action => 'show',
636 :action => 'show',
618 :board_id => message.board,
637 :board_id => message.board,
619 :id => message.root,
638 :id => message.root,
620 :anchor => (message.parent ? "message-#{message.id}" : nil)},
639 :anchor => (message.parent ? "message-#{message.id}" : nil)},
621 :class => 'message'
640 :class => 'message'
622 end
641 end
623 when 'project'
642 when 'project'
624 if p = Project.visible.find_by_id(oid)
643 if p = Project.visible.find_by_id(oid)
625 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
644 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
626 end
645 end
627 end
646 end
628 elsif sep == ':'
647 elsif sep == ':'
629 # removes the double quotes if any
648 # removes the double quotes if any
630 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
649 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
631 case prefix
650 case prefix
632 when 'document'
651 when 'document'
633 if project && document = project.documents.find_by_title(name)
652 if project && document = project.documents.find_by_title(name)
634 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
653 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
635 :class => 'document'
654 :class => 'document'
636 end
655 end
637 when 'version'
656 when 'version'
638 if project && version = project.versions.find_by_name(name)
657 if project && version = project.versions.find_by_name(name)
639 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
658 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
640 :class => 'version'
659 :class => 'version'
641 end
660 end
642 when 'commit'
661 when 'commit'
643 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
662 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
644 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
663 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
645 :class => 'changeset',
664 :class => 'changeset',
646 :title => truncate_single_line(changeset.comments, :length => 100)
665 :title => truncate_single_line(changeset.comments, :length => 100)
647 end
666 end
648 when 'source', 'export'
667 when 'source', 'export'
649 if project && project.repository
668 if project && project.repository
650 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
669 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
651 path, rev, anchor = $1, $3, $5
670 path, rev, anchor = $1, $3, $5
652 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
671 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
653 :path => to_path_param(path),
672 :path => to_path_param(path),
654 :rev => rev,
673 :rev => rev,
655 :anchor => anchor,
674 :anchor => anchor,
656 :format => (prefix == 'export' ? 'raw' : nil)},
675 :format => (prefix == 'export' ? 'raw' : nil)},
657 :class => (prefix == 'export' ? 'source download' : 'source')
676 :class => (prefix == 'export' ? 'source download' : 'source')
658 end
677 end
659 when 'attachment'
678 when 'attachment'
660 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
679 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
661 if attachments && attachment = attachments.detect {|a| a.filename == name }
680 if attachments && attachment = attachments.detect {|a| a.filename == name }
662 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
681 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
663 :class => 'attachment'
682 :class => 'attachment'
664 end
683 end
665 when 'project'
684 when 'project'
666 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
685 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
667 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
686 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
668 end
687 end
669 end
688 end
670 end
689 end
671 end
690 end
672 leading + (link || "#{prefix}#{sep}#{identifier}")
691 leading + (link || "#{prefix}#{sep}#{identifier}")
673 end
692 end
674 end
693 end
675
694
676 # Same as Rails' simple_format helper without using paragraphs
695 # Same as Rails' simple_format helper without using paragraphs
677 def simple_format_without_paragraph(text)
696 def simple_format_without_paragraph(text)
678 text.to_s.
697 text.to_s.
679 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
698 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
680 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
699 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
681 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
700 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
682 end
701 end
683
702
684 def lang_options_for_select(blank=true)
703 def lang_options_for_select(blank=true)
685 (blank ? [["(auto)", ""]] : []) +
704 (blank ? [["(auto)", ""]] : []) +
686 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
705 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
687 end
706 end
688
707
689 def label_tag_for(name, option_tags = nil, options = {})
708 def label_tag_for(name, option_tags = nil, options = {})
690 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
709 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
691 content_tag("label", label_text)
710 content_tag("label", label_text)
692 end
711 end
693
712
694 def labelled_tabular_form_for(name, object, options, &proc)
713 def labelled_tabular_form_for(name, object, options, &proc)
695 options[:html] ||= {}
714 options[:html] ||= {}
696 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
715 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
697 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
716 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
698 end
717 end
699
718
700 def back_url_hidden_field_tag
719 def back_url_hidden_field_tag
701 back_url = params[:back_url] || request.env['HTTP_REFERER']
720 back_url = params[:back_url] || request.env['HTTP_REFERER']
702 back_url = CGI.unescape(back_url.to_s)
721 back_url = CGI.unescape(back_url.to_s)
703 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
722 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
704 end
723 end
705
724
706 def check_all_links(form_name)
725 def check_all_links(form_name)
707 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
726 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
708 " | " +
727 " | " +
709 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
728 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
710 end
729 end
711
730
712 def progress_bar(pcts, options={})
731 def progress_bar(pcts, options={})
713 pcts = [pcts, pcts] unless pcts.is_a?(Array)
732 pcts = [pcts, pcts] unless pcts.is_a?(Array)
714 pcts = pcts.collect(&:round)
733 pcts = pcts.collect(&:round)
715 pcts[1] = pcts[1] - pcts[0]
734 pcts[1] = pcts[1] - pcts[0]
716 pcts << (100 - pcts[1] - pcts[0])
735 pcts << (100 - pcts[1] - pcts[0])
717 width = options[:width] || '100px;'
736 width = options[:width] || '100px;'
718 legend = options[:legend] || ''
737 legend = options[:legend] || ''
719 content_tag('table',
738 content_tag('table',
720 content_tag('tr',
739 content_tag('tr',
721 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
740 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
722 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
741 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
723 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
742 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
724 ), :class => 'progress', :style => "width: #{width};") +
743 ), :class => 'progress', :style => "width: #{width};") +
725 content_tag('p', legend, :class => 'pourcent')
744 content_tag('p', legend, :class => 'pourcent')
726 end
745 end
727
746
728 def checked_image(checked=true)
747 def checked_image(checked=true)
729 if checked
748 if checked
730 image_tag 'toggle_check.png'
749 image_tag 'toggle_check.png'
731 end
750 end
732 end
751 end
733
752
734 def context_menu(url)
753 def context_menu(url)
735 unless @context_menu_included
754 unless @context_menu_included
736 content_for :header_tags do
755 content_for :header_tags do
737 javascript_include_tag('context_menu') +
756 javascript_include_tag('context_menu') +
738 stylesheet_link_tag('context_menu')
757 stylesheet_link_tag('context_menu')
739 end
758 end
740 if l(:direction) == 'rtl'
759 if l(:direction) == 'rtl'
741 content_for :header_tags do
760 content_for :header_tags do
742 stylesheet_link_tag('context_menu_rtl')
761 stylesheet_link_tag('context_menu_rtl')
743 end
762 end
744 end
763 end
745 @context_menu_included = true
764 @context_menu_included = true
746 end
765 end
747 javascript_tag "new ContextMenu('#{ url_for(url) }')"
766 javascript_tag "new ContextMenu('#{ url_for(url) }')"
748 end
767 end
749
768
750 def context_menu_link(name, url, options={})
769 def context_menu_link(name, url, options={})
751 options[:class] ||= ''
770 options[:class] ||= ''
752 if options.delete(:selected)
771 if options.delete(:selected)
753 options[:class] << ' icon-checked disabled'
772 options[:class] << ' icon-checked disabled'
754 options[:disabled] = true
773 options[:disabled] = true
755 end
774 end
756 if options.delete(:disabled)
775 if options.delete(:disabled)
757 options.delete(:method)
776 options.delete(:method)
758 options.delete(:confirm)
777 options.delete(:confirm)
759 options.delete(:onclick)
778 options.delete(:onclick)
760 options[:class] << ' disabled'
779 options[:class] << ' disabled'
761 url = '#'
780 url = '#'
762 end
781 end
763 link_to name, url, options
782 link_to name, url, options
764 end
783 end
765
784
766 def calendar_for(field_id)
785 def calendar_for(field_id)
767 include_calendar_headers_tags
786 include_calendar_headers_tags
768 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
787 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
769 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
788 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
770 end
789 end
771
790
772 def include_calendar_headers_tags
791 def include_calendar_headers_tags
773 unless @calendar_headers_tags_included
792 unless @calendar_headers_tags_included
774 @calendar_headers_tags_included = true
793 @calendar_headers_tags_included = true
775 content_for :header_tags do
794 content_for :header_tags do
776 start_of_week = case Setting.start_of_week.to_i
795 start_of_week = case Setting.start_of_week.to_i
777 when 1
796 when 1
778 'Calendar._FD = 1;' # Monday
797 'Calendar._FD = 1;' # Monday
779 when 7
798 when 7
780 'Calendar._FD = 0;' # Sunday
799 'Calendar._FD = 0;' # Sunday
781 else
800 else
782 '' # use language
801 '' # use language
783 end
802 end
784
803
785 javascript_include_tag('calendar/calendar') +
804 javascript_include_tag('calendar/calendar') +
786 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
805 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
787 javascript_tag(start_of_week) +
806 javascript_tag(start_of_week) +
788 javascript_include_tag('calendar/calendar-setup') +
807 javascript_include_tag('calendar/calendar-setup') +
789 stylesheet_link_tag('calendar')
808 stylesheet_link_tag('calendar')
790 end
809 end
791 end
810 end
792 end
811 end
793
812
794 def content_for(name, content = nil, &block)
813 def content_for(name, content = nil, &block)
795 @has_content ||= {}
814 @has_content ||= {}
796 @has_content[name] = true
815 @has_content[name] = true
797 super(name, content, &block)
816 super(name, content, &block)
798 end
817 end
799
818
800 def has_content?(name)
819 def has_content?(name)
801 (@has_content && @has_content[name]) || false
820 (@has_content && @has_content[name]) || false
802 end
821 end
803
822
804 # Returns the avatar image tag for the given +user+ if avatars are enabled
823 # Returns the avatar image tag for the given +user+ if avatars are enabled
805 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
824 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
806 def avatar(user, options = { })
825 def avatar(user, options = { })
807 if Setting.gravatar_enabled?
826 if Setting.gravatar_enabled?
808 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
827 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
809 email = nil
828 email = nil
810 if user.respond_to?(:mail)
829 if user.respond_to?(:mail)
811 email = user.mail
830 email = user.mail
812 elsif user.to_s =~ %r{<(.+?)>}
831 elsif user.to_s =~ %r{<(.+?)>}
813 email = $1
832 email = $1
814 end
833 end
815 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
834 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
816 end
835 end
817 end
836 end
818
837
819 def favicon
838 def favicon
820 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
839 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
821 end
840 end
822
841
823 private
842 private
824
843
825 def wiki_helper
844 def wiki_helper
826 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
845 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
827 extend helper
846 extend helper
828 return self
847 return self
829 end
848 end
830
849
831 def link_to_remote_content_update(text, url_params)
850 def link_to_remote_content_update(text, url_params)
832 link_to_remote(text,
851 link_to_remote(text,
833 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
852 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
834 {:href => url_for(:params => url_params)}
853 {:href => url_for(:params => url_params)}
835 )
854 )
836 end
855 end
837
856
838 end
857 end
@@ -1,609 +1,638
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < ActionView::TestCase
20 class ApplicationHelperTest < ActionView::TestCase
21
21
22 fixtures :projects, :roles, :enabled_modules, :users,
22 fixtures :projects, :roles, :enabled_modules, :users,
23 :repositories, :changesets,
23 :repositories, :changesets,
24 :trackers, :issue_statuses, :issues, :versions, :documents,
24 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :wikis, :wiki_pages, :wiki_contents,
25 :wikis, :wiki_pages, :wiki_contents,
26 :boards, :messages,
26 :boards, :messages,
27 :attachments,
27 :attachments,
28 :enumerations
28 :enumerations
29
29
30 def setup
30 def setup
31 super
31 super
32 end
32 end
33
34 context "#link_to_if_authorized" do
35 context "authorized user" do
36 should "be tested"
37 end
38
39 context "unauthorized user" do
40 should "be tested"
41 end
42
43 should "allow using the :controller and :action for the target link" do
44 User.current = User.find_by_login('admin')
45
46 @project = Issue.first.project # Used by helper
47 response = link_to_if_authorized("By controller/action",
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 assert_match /href/, response
50 end
51
52 should "allow using the url for the target link" do
53 User.current = User.find_by_login('admin')
54
55 @project = Issue.first.project # Used by helper
56 response = link_to_if_authorized("By url",
57 new_issue_move_path(:id => Issue.first.id))
58 assert_match /href/, response
59 end
60
61 end
33
62
34 def test_auto_links
63 def test_auto_links
35 to_test = {
64 to_test = {
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
65 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
66 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
67 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
68 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
40 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
69 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
41 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
70 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
42 '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>.',
71 '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>.',
43 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
72 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
44 '(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>)',
73 '(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>)',
45 '(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>)',
74 '(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>)',
46 '(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>).',
75 '(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>).',
47 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
76 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
48 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
77 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
49 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
78 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
50 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
79 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
51 '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>',
80 '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>',
52 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
81 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
53 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
82 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
54 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
83 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
55 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
84 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
56 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
85 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
57 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
86 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
58 # two exclamation marks
87 # two exclamation marks
59 '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>',
88 '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>',
60 # escaping
89 # escaping
61 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
90 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
62 }
91 }
63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
92 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
64 end
93 end
65
94
66 def test_auto_mailto
95 def test_auto_mailto
67 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
96 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
68 textilizable('test@foo.bar')
97 textilizable('test@foo.bar')
69 end
98 end
70
99
71 def test_inline_images
100 def test_inline_images
72 to_test = {
101 to_test = {
73 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
102 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
74 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
103 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
75 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
104 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
76 # inline styles should be stripped
105 # inline styles should be stripped
77 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
106 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
78 '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" />',
107 '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" />',
79 '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;" />',
108 '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;" />',
80 }
109 }
81 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
110 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
82 end
111 end
83
112
84 def test_inline_images_inside_tags
113 def test_inline_images_inside_tags
85 raw = <<-RAW
114 raw = <<-RAW
86 h1. !foo.png! Heading
115 h1. !foo.png! Heading
87
116
88 Centered image:
117 Centered image:
89
118
90 p=. !bar.gif!
119 p=. !bar.gif!
91 RAW
120 RAW
92
121
93 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
122 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
94 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
123 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
95 end
124 end
96
125
97 def test_acronyms
126 def test_acronyms
98 to_test = {
127 to_test = {
99 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
128 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
100 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
129 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
101 }
130 }
102 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
131 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
103
132
104 end
133 end
105
134
106 def test_attached_images
135 def test_attached_images
107 to_test = {
136 to_test = {
108 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
137 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
109 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
138 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
110 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
139 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
111 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
140 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
112 # link image
141 # link image
113 '!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>',
142 '!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>',
114 }
143 }
115 attachments = Attachment.find(:all)
144 attachments = Attachment.find(:all)
116 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
145 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
117 end
146 end
118
147
119 def test_textile_external_links
148 def test_textile_external_links
120 to_test = {
149 to_test = {
121 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
150 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
122 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
151 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
123 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
152 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
124 '"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>',
153 '"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>',
125 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
154 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
126 # no multiline link text
155 # no multiline link text
127 "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",
156 "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",
128 # mailto link
157 # mailto link
129 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
158 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
130 # two exclamation marks
159 # two exclamation marks
131 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
160 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
132 # escaping
161 # escaping
133 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
162 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
134 }
163 }
135 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
164 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
136 end
165 end
137
166
138 def test_redmine_links
167 def test_redmine_links
139 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
168 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
140 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
169 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
141
170
142 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
171 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
143 :class => 'changeset', :title => 'My very first commit')
172 :class => 'changeset', :title => 'My very first commit')
144 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
173 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
145 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
174 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
146
175
147 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
176 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
148 :class => 'document')
177 :class => 'document')
149
178
150 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
179 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
151 :class => 'version')
180 :class => 'version')
152
181
153 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
182 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
154
183
155 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
184 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
156
185
157 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
186 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
158 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
187 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
159
188
160 to_test = {
189 to_test = {
161 # tickets
190 # tickets
162 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
191 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
163 # changesets
192 # changesets
164 'r1' => changeset_link,
193 'r1' => changeset_link,
165 'r1.' => "#{changeset_link}.",
194 'r1.' => "#{changeset_link}.",
166 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
195 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
167 'r1,r2' => "#{changeset_link},#{changeset_link2}",
196 'r1,r2' => "#{changeset_link},#{changeset_link2}",
168 # documents
197 # documents
169 'document#1' => document_link,
198 'document#1' => document_link,
170 'document:"Test document"' => document_link,
199 'document:"Test document"' => document_link,
171 # versions
200 # versions
172 'version#2' => version_link,
201 'version#2' => version_link,
173 'version:1.0' => version_link,
202 'version:1.0' => version_link,
174 'version:"1.0"' => version_link,
203 'version:"1.0"' => version_link,
175 # source
204 # source
176 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
205 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
177 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
206 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
178 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
207 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
179 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
208 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
180 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
209 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
181 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
210 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
182 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
211 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
183 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
212 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
184 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
213 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
185 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
214 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
186 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
215 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
187 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
216 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
188 # message
217 # message
189 'message#4' => link_to('Post 2', message_url, :class => 'message'),
218 'message#4' => link_to('Post 2', message_url, :class => 'message'),
190 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
219 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
191 # project
220 # project
192 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
221 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
193 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
222 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
194 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
223 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
195 # escaping
224 # escaping
196 '!#3.' => '#3.',
225 '!#3.' => '#3.',
197 '!r1' => 'r1',
226 '!r1' => 'r1',
198 '!document#1' => 'document#1',
227 '!document#1' => 'document#1',
199 '!document:"Test document"' => 'document:"Test document"',
228 '!document:"Test document"' => 'document:"Test document"',
200 '!version#2' => 'version#2',
229 '!version#2' => 'version#2',
201 '!version:1.0' => 'version:1.0',
230 '!version:1.0' => 'version:1.0',
202 '!version:"1.0"' => 'version:"1.0"',
231 '!version:"1.0"' => 'version:"1.0"',
203 '!source:/some/file' => 'source:/some/file',
232 '!source:/some/file' => 'source:/some/file',
204 # not found
233 # not found
205 '#0123456789' => '#0123456789',
234 '#0123456789' => '#0123456789',
206 # invalid expressions
235 # invalid expressions
207 'source:' => 'source:',
236 'source:' => 'source:',
208 # url hash
237 # url hash
209 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
238 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
210 }
239 }
211 @project = Project.find(1)
240 @project = Project.find(1)
212 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
241 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
213 end
242 end
214
243
215 def test_attachment_links
244 def test_attachment_links
216 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
245 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
217 to_test = {
246 to_test = {
218 'attachment:error281.txt' => attachment_link
247 'attachment:error281.txt' => attachment_link
219 }
248 }
220 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
249 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
221 end
250 end
222
251
223 def test_wiki_links
252 def test_wiki_links
224 to_test = {
253 to_test = {
225 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
254 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
226 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
255 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
227 # link with anchor
256 # link with anchor
228 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
257 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
229 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
258 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
230 # page that doesn't exist
259 # page that doesn't exist
231 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
260 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
232 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
261 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
233 # link to another project wiki
262 # link to another project wiki
234 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
263 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
235 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
264 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
236 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
265 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
237 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
266 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
238 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
267 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
239 # striked through link
268 # striked through link
240 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
269 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
241 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
270 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
242 # escaping
271 # escaping
243 '![[Another page|Page]]' => '[[Another page|Page]]',
272 '![[Another page|Page]]' => '[[Another page|Page]]',
244 # project does not exist
273 # project does not exist
245 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
274 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
246 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
275 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
247 }
276 }
248 @project = Project.find(1)
277 @project = Project.find(1)
249 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
278 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
250 end
279 end
251
280
252 def test_html_tags
281 def test_html_tags
253 to_test = {
282 to_test = {
254 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
283 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
255 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
284 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
256 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
285 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
257 # do not escape pre/code tags
286 # do not escape pre/code tags
258 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
287 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
259 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
288 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
260 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
289 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
261 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
290 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
262 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
291 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
263 # remove attributes except class
292 # remove attributes except class
264 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
293 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
265 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
294 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
266 }
295 }
267 to_test.each { |text, result| assert_equal result, textilizable(text) }
296 to_test.each { |text, result| assert_equal result, textilizable(text) }
268 end
297 end
269
298
270 def test_allowed_html_tags
299 def test_allowed_html_tags
271 to_test = {
300 to_test = {
272 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
301 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
273 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
302 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
274 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
303 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
275 }
304 }
276 to_test.each { |text, result| assert_equal result, textilizable(text) }
305 to_test.each { |text, result| assert_equal result, textilizable(text) }
277 end
306 end
278
307
279 def test_pre_tags
308 def test_pre_tags
280 raw = <<-RAW
309 raw = <<-RAW
281 Before
310 Before
282
311
283 <pre>
312 <pre>
284 <prepared-statement-cache-size>32</prepared-statement-cache-size>
313 <prepared-statement-cache-size>32</prepared-statement-cache-size>
285 </pre>
314 </pre>
286
315
287 After
316 After
288 RAW
317 RAW
289
318
290 expected = <<-EXPECTED
319 expected = <<-EXPECTED
291 <p>Before</p>
320 <p>Before</p>
292 <pre>
321 <pre>
293 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
322 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
294 </pre>
323 </pre>
295 <p>After</p>
324 <p>After</p>
296 EXPECTED
325 EXPECTED
297
326
298 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
327 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
299 end
328 end
300
329
301 def test_pre_content_should_not_parse_wiki_and_redmine_links
330 def test_pre_content_should_not_parse_wiki_and_redmine_links
302 raw = <<-RAW
331 raw = <<-RAW
303 [[CookBook documentation]]
332 [[CookBook documentation]]
304
333
305 #1
334 #1
306
335
307 <pre>
336 <pre>
308 [[CookBook documentation]]
337 [[CookBook documentation]]
309
338
310 #1
339 #1
311 </pre>
340 </pre>
312 RAW
341 RAW
313
342
314 expected = <<-EXPECTED
343 expected = <<-EXPECTED
315 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
344 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
316 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
345 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
317 <pre>
346 <pre>
318 [[CookBook documentation]]
347 [[CookBook documentation]]
319
348
320 #1
349 #1
321 </pre>
350 </pre>
322 EXPECTED
351 EXPECTED
323
352
324 @project = Project.find(1)
353 @project = Project.find(1)
325 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
354 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
326 end
355 end
327
356
328 def test_non_closing_pre_blocks_should_be_closed
357 def test_non_closing_pre_blocks_should_be_closed
329 raw = <<-RAW
358 raw = <<-RAW
330 <pre><code>
359 <pre><code>
331 RAW
360 RAW
332
361
333 expected = <<-EXPECTED
362 expected = <<-EXPECTED
334 <pre><code>
363 <pre><code>
335 </code></pre>
364 </code></pre>
336 EXPECTED
365 EXPECTED
337
366
338 @project = Project.find(1)
367 @project = Project.find(1)
339 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
368 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
340 end
369 end
341
370
342 def test_syntax_highlight
371 def test_syntax_highlight
343 raw = <<-RAW
372 raw = <<-RAW
344 <pre><code class="ruby">
373 <pre><code class="ruby">
345 # Some ruby code here
374 # Some ruby code here
346 </code></pre>
375 </code></pre>
347 RAW
376 RAW
348
377
349 expected = <<-EXPECTED
378 expected = <<-EXPECTED
350 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
379 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="no">1</span> <span class="c"># Some ruby code here</span></span>
351 </code></pre>
380 </code></pre>
352 EXPECTED
381 EXPECTED
353
382
354 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
383 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
355 end
384 end
356
385
357 def test_wiki_links_in_tables
386 def test_wiki_links_in_tables
358 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
387 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
359 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
388 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
360 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
389 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
361 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
390 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
362 }
391 }
363 @project = Project.find(1)
392 @project = Project.find(1)
364 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
393 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
365 end
394 end
366
395
367 def test_text_formatting
396 def test_text_formatting
368 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
397 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
369 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
398 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
370 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
399 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
371 '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>',
400 '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>',
372 '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',
401 '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',
373 }
402 }
374 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
403 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
375 end
404 end
376
405
377 def test_wiki_horizontal_rule
406 def test_wiki_horizontal_rule
378 assert_equal '<hr />', textilizable('---')
407 assert_equal '<hr />', textilizable('---')
379 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
408 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
380 end
409 end
381
410
382 def test_acronym
411 def test_acronym
383 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
412 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
384 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
413 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
385 end
414 end
386
415
387 def test_footnotes
416 def test_footnotes
388 raw = <<-RAW
417 raw = <<-RAW
389 This is some text[1].
418 This is some text[1].
390
419
391 fn1. This is the foot note
420 fn1. This is the foot note
392 RAW
421 RAW
393
422
394 expected = <<-EXPECTED
423 expected = <<-EXPECTED
395 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
424 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
396 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
425 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
397 EXPECTED
426 EXPECTED
398
427
399 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
428 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
400 end
429 end
401
430
402 def test_table_of_content
431 def test_table_of_content
403 raw = <<-RAW
432 raw = <<-RAW
404 {{toc}}
433 {{toc}}
405
434
406 h1. Title
435 h1. Title
407
436
408 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
437 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
409
438
410 h2. Subtitle with a [[Wiki]] link
439 h2. Subtitle with a [[Wiki]] link
411
440
412 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
441 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
413
442
414 h2. Subtitle with [[Wiki|another Wiki]] link
443 h2. Subtitle with [[Wiki|another Wiki]] link
415
444
416 h2. Subtitle with %{color:red}red text%
445 h2. Subtitle with %{color:red}red text%
417
446
418 h1. Another title
447 h1. Another title
419
448
420 h2. An "Internet link":http://www.redmine.org/ inside subtitle
449 h2. An "Internet link":http://www.redmine.org/ inside subtitle
421
450
422 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
451 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
423
452
424 RAW
453 RAW
425
454
426 expected = '<ul class="toc">' +
455 expected = '<ul class="toc">' +
427 '<li class="heading1"><a href="#Title">Title</a></li>' +
456 '<li class="heading1"><a href="#Title">Title</a></li>' +
428 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
457 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
429 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
458 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
430 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
459 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
431 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
460 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
432 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
461 '<li class="heading2"><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
433 '<li class="heading2"><a href="#Project-Name">Project Name</a></li>' +
462 '<li class="heading2"><a href="#Project-Name">Project Name</a></li>' +
434 '</ul>'
463 '</ul>'
435
464
436 assert textilizable(raw).gsub("\n", "").include?(expected)
465 assert textilizable(raw).gsub("\n", "").include?(expected)
437 end
466 end
438
467
439 def test_blockquote
468 def test_blockquote
440 # orig raw text
469 # orig raw text
441 raw = <<-RAW
470 raw = <<-RAW
442 John said:
471 John said:
443 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
472 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
444 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
473 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
445 > * Donec odio lorem,
474 > * Donec odio lorem,
446 > * sagittis ac,
475 > * sagittis ac,
447 > * malesuada in,
476 > * malesuada in,
448 > * adipiscing eu, dolor.
477 > * adipiscing eu, dolor.
449 >
478 >
450 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
479 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
451 > Proin a tellus. Nam vel neque.
480 > Proin a tellus. Nam vel neque.
452
481
453 He's right.
482 He's right.
454 RAW
483 RAW
455
484
456 # expected html
485 # expected html
457 expected = <<-EXPECTED
486 expected = <<-EXPECTED
458 <p>John said:</p>
487 <p>John said:</p>
459 <blockquote>
488 <blockquote>
460 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
489 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
461 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
490 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
462 <ul>
491 <ul>
463 <li>Donec odio lorem,</li>
492 <li>Donec odio lorem,</li>
464 <li>sagittis ac,</li>
493 <li>sagittis ac,</li>
465 <li>malesuada in,</li>
494 <li>malesuada in,</li>
466 <li>adipiscing eu, dolor.</li>
495 <li>adipiscing eu, dolor.</li>
467 </ul>
496 </ul>
468 <blockquote>
497 <blockquote>
469 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
498 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
470 </blockquote>
499 </blockquote>
471 <p>Proin a tellus. Nam vel neque.</p>
500 <p>Proin a tellus. Nam vel neque.</p>
472 </blockquote>
501 </blockquote>
473 <p>He's right.</p>
502 <p>He's right.</p>
474 EXPECTED
503 EXPECTED
475
504
476 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
505 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
477 end
506 end
478
507
479 def test_table
508 def test_table
480 raw = <<-RAW
509 raw = <<-RAW
481 This is a table with empty cells:
510 This is a table with empty cells:
482
511
483 |cell11|cell12||
512 |cell11|cell12||
484 |cell21||cell23|
513 |cell21||cell23|
485 |cell31|cell32|cell33|
514 |cell31|cell32|cell33|
486 RAW
515 RAW
487
516
488 expected = <<-EXPECTED
517 expected = <<-EXPECTED
489 <p>This is a table with empty cells:</p>
518 <p>This is a table with empty cells:</p>
490
519
491 <table>
520 <table>
492 <tr><td>cell11</td><td>cell12</td><td></td></tr>
521 <tr><td>cell11</td><td>cell12</td><td></td></tr>
493 <tr><td>cell21</td><td></td><td>cell23</td></tr>
522 <tr><td>cell21</td><td></td><td>cell23</td></tr>
494 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
523 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
495 </table>
524 </table>
496 EXPECTED
525 EXPECTED
497
526
498 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
527 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
499 end
528 end
500
529
501 def test_table_with_line_breaks
530 def test_table_with_line_breaks
502 raw = <<-RAW
531 raw = <<-RAW
503 This is a table with line breaks:
532 This is a table with line breaks:
504
533
505 |cell11
534 |cell11
506 continued|cell12||
535 continued|cell12||
507 |-cell21-||cell23
536 |-cell21-||cell23
508 cell23 line2
537 cell23 line2
509 cell23 *line3*|
538 cell23 *line3*|
510 |cell31|cell32
539 |cell31|cell32
511 cell32 line2|cell33|
540 cell32 line2|cell33|
512
541
513 RAW
542 RAW
514
543
515 expected = <<-EXPECTED
544 expected = <<-EXPECTED
516 <p>This is a table with line breaks:</p>
545 <p>This is a table with line breaks:</p>
517
546
518 <table>
547 <table>
519 <tr>
548 <tr>
520 <td>cell11<br />continued</td>
549 <td>cell11<br />continued</td>
521 <td>cell12</td>
550 <td>cell12</td>
522 <td></td>
551 <td></td>
523 </tr>
552 </tr>
524 <tr>
553 <tr>
525 <td><del>cell21</del></td>
554 <td><del>cell21</del></td>
526 <td></td>
555 <td></td>
527 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
556 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
528 </tr>
557 </tr>
529 <tr>
558 <tr>
530 <td>cell31</td>
559 <td>cell31</td>
531 <td>cell32<br/>cell32 line2</td>
560 <td>cell32<br/>cell32 line2</td>
532 <td>cell33</td>
561 <td>cell33</td>
533 </tr>
562 </tr>
534 </table>
563 </table>
535 EXPECTED
564 EXPECTED
536
565
537 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
566 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
538 end
567 end
539
568
540 def test_textile_should_not_mangle_brackets
569 def test_textile_should_not_mangle_brackets
541 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
570 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
542 end
571 end
543
572
544 def test_default_formatter
573 def test_default_formatter
545 Setting.text_formatting = 'unknown'
574 Setting.text_formatting = 'unknown'
546 text = 'a *link*: http://www.example.net/'
575 text = 'a *link*: http://www.example.net/'
547 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
576 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
548 Setting.text_formatting = 'textile'
577 Setting.text_formatting = 'textile'
549 end
578 end
550
579
551 def test_due_date_distance_in_words
580 def test_due_date_distance_in_words
552 to_test = { Date.today => 'Due in 0 days',
581 to_test = { Date.today => 'Due in 0 days',
553 Date.today + 1 => 'Due in 1 day',
582 Date.today + 1 => 'Due in 1 day',
554 Date.today + 100 => 'Due in about 3 months',
583 Date.today + 100 => 'Due in about 3 months',
555 Date.today + 20000 => 'Due in over 54 years',
584 Date.today + 20000 => 'Due in over 54 years',
556 Date.today - 1 => '1 day late',
585 Date.today - 1 => '1 day late',
557 Date.today - 100 => 'about 3 months late',
586 Date.today - 100 => 'about 3 months late',
558 Date.today - 20000 => 'over 54 years late',
587 Date.today - 20000 => 'over 54 years late',
559 }
588 }
560 to_test.each do |date, expected|
589 to_test.each do |date, expected|
561 assert_equal expected, due_date_distance_in_words(date)
590 assert_equal expected, due_date_distance_in_words(date)
562 end
591 end
563 end
592 end
564
593
565 def test_avatar
594 def test_avatar
566 # turn on avatars
595 # turn on avatars
567 Setting.gravatar_enabled = '1'
596 Setting.gravatar_enabled = '1'
568 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
597 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
569 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
598 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
570 assert_nil avatar('jsmith')
599 assert_nil avatar('jsmith')
571 assert_nil avatar(nil)
600 assert_nil avatar(nil)
572
601
573 # turn off avatars
602 # turn off avatars
574 Setting.gravatar_enabled = '0'
603 Setting.gravatar_enabled = '0'
575 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
604 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
576 end
605 end
577
606
578 def test_link_to_user
607 def test_link_to_user
579 user = User.find(2)
608 user = User.find(2)
580 t = link_to_user(user)
609 t = link_to_user(user)
581 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
610 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
582 end
611 end
583
612
584 def test_link_to_user_should_not_link_to_locked_user
613 def test_link_to_user_should_not_link_to_locked_user
585 user = User.find(5)
614 user = User.find(5)
586 assert user.locked?
615 assert user.locked?
587 t = link_to_user(user)
616 t = link_to_user(user)
588 assert_equal user.name, t
617 assert_equal user.name, t
589 end
618 end
590
619
591 def test_link_to_user_should_not_link_to_anonymous
620 def test_link_to_user_should_not_link_to_anonymous
592 user = User.anonymous
621 user = User.anonymous
593 assert user.anonymous?
622 assert user.anonymous?
594 t = link_to_user(user)
623 t = link_to_user(user)
595 assert_equal ::I18n.t(:label_user_anonymous), t
624 assert_equal ::I18n.t(:label_user_anonymous), t
596 end
625 end
597
626
598 def test_link_to_project
627 def test_link_to_project
599 project = Project.find(1)
628 project = Project.find(1)
600 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
629 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
601 link_to_project(project)
630 link_to_project(project)
602 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
631 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
603 link_to_project(project, :action => 'settings')
632 link_to_project(project, :action => 'settings')
604 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
633 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
605 link_to_project(project, {:only_path => false, :jump => 'blah'})
634 link_to_project(project, {:only_path => false, :jump => 'blah'})
606 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
635 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
607 link_to_project(project, {:action => 'settings'}, :class => "project")
636 link_to_project(project, {:action => 'settings'}, :class => "project")
608 end
637 end
609 end
638 end
General Comments 0
You need to be logged in to leave comments. Login now