##// END OF EJS Templates
Moved the project name after the item in the html title (#9593)....
Jean-Philippe Lang -
r7722:bf18ee107d08
parent child
Show More
@@ -1,1051 +1,1050
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Display a link to remote if user is authorized
46 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
50 end
51
51
52 # Displays a link to user's account page if active
52 # Displays a link to user's account page if active
53 def link_to_user(user, options={})
53 def link_to_user(user, options={})
54 if user.is_a?(User)
54 if user.is_a?(User)
55 name = h(user.name(options[:format]))
55 name = h(user.name(options[:format]))
56 if user.active?
56 if user.active?
57 link_to name, :controller => 'users', :action => 'show', :id => user
57 link_to name, :controller => 'users', :action => 'show', :id => user
58 else
58 else
59 name
59 name
60 end
60 end
61 else
61 else
62 h(user.to_s)
62 h(user.to_s)
63 end
63 end
64 end
64 end
65
65
66 # Displays a link to +issue+ with its subject.
66 # Displays a link to +issue+ with its subject.
67 # Examples:
67 # Examples:
68 #
68 #
69 # link_to_issue(issue) # => Defect #6: This is the subject
69 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6
71 # link_to_issue(issue, :subject => false) # => Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 #
73 #
74 def link_to_issue(issue, options={})
74 def link_to_issue(issue, options={})
75 title = nil
75 title = nil
76 subject = nil
76 subject = nil
77 if options[:subject] == false
77 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60)
78 title = truncate(issue.subject, :length => 60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if options[:truncate]
81 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate])
82 subject = truncate(subject, :length => options[:truncate])
83 end
83 end
84 end
84 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 :class => issue.css_classes,
86 :class => issue.css_classes,
87 :title => title
87 :title => title
88 s << ": #{h subject}" if subject
88 s << ": #{h subject}" if subject
89 s = "#{h issue.project} - " + s if options[:project]
89 s = "#{h issue.project} - " + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show'
99 action = options.delete(:download) ? 'download' : 'show'
100
100
101 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
101 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
102 end
102 end
103
103
104 # Generates a link to a SCM revision
104 # Generates a link to a SCM revision
105 # Options:
105 # Options:
106 # * :text - Link text (default to the formatted revision)
106 # * :text - Link text (default to the formatted revision)
107 def link_to_revision(revision, project, options={})
107 def link_to_revision(revision, project, options={})
108 text = options.delete(:text) || format_revision(revision)
108 text = options.delete(:text) || format_revision(revision)
109 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
109 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
110
110
111 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
111 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
112 :title => l(:label_revision_id, format_revision(revision)))
112 :title => l(:label_revision_id, format_revision(revision)))
113 end
113 end
114
114
115 # Generates a link to a message
115 # Generates a link to a message
116 def link_to_message(message, options={}, html_options = nil)
116 def link_to_message(message, options={}, html_options = nil)
117 link_to(
117 link_to(
118 h(truncate(message.subject, :length => 60)),
118 h(truncate(message.subject, :length => 60)),
119 { :controller => 'messages', :action => 'show',
119 { :controller => 'messages', :action => 'show',
120 :board_id => message.board_id,
120 :board_id => message.board_id,
121 :id => message.root,
121 :id => message.root,
122 :r => (message.parent_id && message.id),
122 :r => (message.parent_id && message.id),
123 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
123 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
124 }.merge(options),
124 }.merge(options),
125 html_options
125 html_options
126 )
126 )
127 end
127 end
128
128
129 # Generates a link to a project if active
129 # Generates a link to a project if active
130 # Examples:
130 # Examples:
131 #
131 #
132 # link_to_project(project) # => link to the specified project overview
132 # link_to_project(project) # => link to the specified project overview
133 # link_to_project(project, :action=>'settings') # => link to project settings
133 # link_to_project(project, :action=>'settings') # => link to project settings
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
136 #
136 #
137 def link_to_project(project, options={}, html_options = nil)
137 def link_to_project(project, options={}, html_options = nil)
138 if project.active?
138 if project.active?
139 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
139 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
140 link_to(h(project), url, html_options)
140 link_to(h(project), url, html_options)
141 else
141 else
142 h(project)
142 h(project)
143 end
143 end
144 end
144 end
145
145
146 def toggle_link(name, id, options={})
146 def toggle_link(name, id, options={})
147 onclick = "Element.toggle('#{id}'); "
147 onclick = "Element.toggle('#{id}'); "
148 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
148 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
149 onclick << "return false;"
149 onclick << "return false;"
150 link_to(name, "#", :onclick => onclick)
150 link_to(name, "#", :onclick => onclick)
151 end
151 end
152
152
153 def image_to_function(name, function, html_options = {})
153 def image_to_function(name, function, html_options = {})
154 html_options.symbolize_keys!
154 html_options.symbolize_keys!
155 tag(:input, html_options.merge({
155 tag(:input, html_options.merge({
156 :type => "image", :src => image_path(name),
156 :type => "image", :src => image_path(name),
157 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
157 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
158 }))
158 }))
159 end
159 end
160
160
161 def prompt_to_remote(name, text, param, url, html_options = {})
161 def prompt_to_remote(name, text, param, url, html_options = {})
162 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
162 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
163 link_to name, {}, html_options
163 link_to name, {}, html_options
164 end
164 end
165
165
166 def format_activity_title(text)
166 def format_activity_title(text)
167 h(truncate_single_line(text, :length => 100))
167 h(truncate_single_line(text, :length => 100))
168 end
168 end
169
169
170 def format_activity_day(date)
170 def format_activity_day(date)
171 date == Date.today ? l(:label_today).titleize : format_date(date)
171 date == Date.today ? l(:label_today).titleize : format_date(date)
172 end
172 end
173
173
174 def format_activity_description(text)
174 def format_activity_description(text)
175 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
175 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
176 end
176 end
177
177
178 def format_version_name(version)
178 def format_version_name(version)
179 if version.project == @project
179 if version.project == @project
180 h(version)
180 h(version)
181 else
181 else
182 h("#{version.project} - #{version}")
182 h("#{version.project} - #{version}")
183 end
183 end
184 end
184 end
185
185
186 def due_date_distance_in_words(date)
186 def due_date_distance_in_words(date)
187 if date
187 if date
188 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
188 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
189 end
189 end
190 end
190 end
191
191
192 def render_page_hierarchy(pages, node=nil, options={})
192 def render_page_hierarchy(pages, node=nil, options={})
193 content = ''
193 content = ''
194 if pages[node]
194 if pages[node]
195 content << "<ul class=\"pages-hierarchy\">\n"
195 content << "<ul class=\"pages-hierarchy\">\n"
196 pages[node].each do |page|
196 pages[node].each do |page|
197 content << "<li>"
197 content << "<li>"
198 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
198 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
199 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
199 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
200 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
200 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
201 content << "</li>\n"
201 content << "</li>\n"
202 end
202 end
203 content << "</ul>\n"
203 content << "</ul>\n"
204 end
204 end
205 content.html_safe
205 content.html_safe
206 end
206 end
207
207
208 # Renders flash messages
208 # Renders flash messages
209 def render_flash_messages
209 def render_flash_messages
210 s = ''
210 s = ''
211 flash.each do |k,v|
211 flash.each do |k,v|
212 s << content_tag('div', v, :class => "flash #{k}")
212 s << content_tag('div', v, :class => "flash #{k}")
213 end
213 end
214 s.html_safe
214 s.html_safe
215 end
215 end
216
216
217 # Renders tabs and their content
217 # Renders tabs and their content
218 def render_tabs(tabs)
218 def render_tabs(tabs)
219 if tabs.any?
219 if tabs.any?
220 render :partial => 'common/tabs', :locals => {:tabs => tabs}
220 render :partial => 'common/tabs', :locals => {:tabs => tabs}
221 else
221 else
222 content_tag 'p', l(:label_no_data), :class => "nodata"
222 content_tag 'p', l(:label_no_data), :class => "nodata"
223 end
223 end
224 end
224 end
225
225
226 # Renders the project quick-jump box
226 # Renders the project quick-jump box
227 def render_project_jump_box
227 def render_project_jump_box
228 return unless User.current.logged?
228 return unless User.current.logged?
229 projects = User.current.memberships.collect(&:project).compact.uniq
229 projects = User.current.memberships.collect(&:project).compact.uniq
230 if projects.any?
230 if projects.any?
231 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
231 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
232 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
232 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
233 '<option value="" disabled="disabled">---</option>'
233 '<option value="" disabled="disabled">---</option>'
234 s << project_tree_options_for_select(projects, :selected => @project) do |p|
234 s << project_tree_options_for_select(projects, :selected => @project) do |p|
235 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
235 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
236 end
236 end
237 s << '</select>'
237 s << '</select>'
238 s.html_safe
238 s.html_safe
239 end
239 end
240 end
240 end
241
241
242 def project_tree_options_for_select(projects, options = {})
242 def project_tree_options_for_select(projects, options = {})
243 s = ''
243 s = ''
244 project_tree(projects) do |project, level|
244 project_tree(projects) do |project, level|
245 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
245 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
246 tag_options = {:value => project.id}
246 tag_options = {:value => project.id}
247 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
247 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
248 tag_options[:selected] = 'selected'
248 tag_options[:selected] = 'selected'
249 else
249 else
250 tag_options[:selected] = nil
250 tag_options[:selected] = nil
251 end
251 end
252 tag_options.merge!(yield(project)) if block_given?
252 tag_options.merge!(yield(project)) if block_given?
253 s << content_tag('option', name_prefix + h(project), tag_options)
253 s << content_tag('option', name_prefix + h(project), tag_options)
254 end
254 end
255 s.html_safe
255 s.html_safe
256 end
256 end
257
257
258 # Yields the given block for each project with its level in the tree
258 # Yields the given block for each project with its level in the tree
259 #
259 #
260 # Wrapper for Project#project_tree
260 # Wrapper for Project#project_tree
261 def project_tree(projects, &block)
261 def project_tree(projects, &block)
262 Project.project_tree(projects, &block)
262 Project.project_tree(projects, &block)
263 end
263 end
264
264
265 def project_nested_ul(projects, &block)
265 def project_nested_ul(projects, &block)
266 s = ''
266 s = ''
267 if projects.any?
267 if projects.any?
268 ancestors = []
268 ancestors = []
269 projects.sort_by(&:lft).each do |project|
269 projects.sort_by(&:lft).each do |project|
270 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
270 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
271 s << "<ul>\n"
271 s << "<ul>\n"
272 else
272 else
273 ancestors.pop
273 ancestors.pop
274 s << "</li>"
274 s << "</li>"
275 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
275 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
276 ancestors.pop
276 ancestors.pop
277 s << "</ul></li>\n"
277 s << "</ul></li>\n"
278 end
278 end
279 end
279 end
280 s << "<li>"
280 s << "<li>"
281 s << yield(project).to_s
281 s << yield(project).to_s
282 ancestors << project
282 ancestors << project
283 end
283 end
284 s << ("</li></ul>\n" * ancestors.size)
284 s << ("</li></ul>\n" * ancestors.size)
285 end
285 end
286 s.html_safe
286 s.html_safe
287 end
287 end
288
288
289 def principals_check_box_tags(name, principals)
289 def principals_check_box_tags(name, principals)
290 s = ''
290 s = ''
291 principals.sort.each do |principal|
291 principals.sort.each do |principal|
292 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
292 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
293 end
293 end
294 s.html_safe
294 s.html_safe
295 end
295 end
296
296
297 # Returns a string for users/groups option tags
297 # Returns a string for users/groups option tags
298 def principals_options_for_select(collection, selected=nil)
298 def principals_options_for_select(collection, selected=nil)
299 s = ''
299 s = ''
300 groups = ''
300 groups = ''
301 collection.sort.each do |element|
301 collection.sort.each do |element|
302 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
302 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
303 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
303 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
304 end
304 end
305 unless groups.empty?
305 unless groups.empty?
306 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
306 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
307 end
307 end
308 s
308 s
309 end
309 end
310
310
311 # Truncates and returns the string as a single line
311 # Truncates and returns the string as a single line
312 def truncate_single_line(string, *args)
312 def truncate_single_line(string, *args)
313 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
313 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
314 end
314 end
315
315
316 # Truncates at line break after 250 characters or options[:length]
316 # Truncates at line break after 250 characters or options[:length]
317 def truncate_lines(string, options={})
317 def truncate_lines(string, options={})
318 length = options[:length] || 250
318 length = options[:length] || 250
319 if string.to_s =~ /\A(.{#{length}}.*?)$/m
319 if string.to_s =~ /\A(.{#{length}}.*?)$/m
320 "#{$1}..."
320 "#{$1}..."
321 else
321 else
322 string
322 string
323 end
323 end
324 end
324 end
325
325
326 def html_hours(text)
326 def html_hours(text)
327 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
327 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
328 end
328 end
329
329
330 def authoring(created, author, options={})
330 def authoring(created, author, options={})
331 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
331 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
332 end
332 end
333
333
334 def time_tag(time)
334 def time_tag(time)
335 text = distance_of_time_in_words(Time.now, time)
335 text = distance_of_time_in_words(Time.now, time)
336 if @project
336 if @project
337 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
337 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
338 else
338 else
339 content_tag('acronym', text, :title => format_time(time))
339 content_tag('acronym', text, :title => format_time(time))
340 end
340 end
341 end
341 end
342
342
343 def syntax_highlight(name, content)
343 def syntax_highlight(name, content)
344 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
344 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
345 end
345 end
346
346
347 def to_path_param(path)
347 def to_path_param(path)
348 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
348 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
349 end
349 end
350
350
351 def pagination_links_full(paginator, count=nil, options={})
351 def pagination_links_full(paginator, count=nil, options={})
352 page_param = options.delete(:page_param) || :page
352 page_param = options.delete(:page_param) || :page
353 per_page_links = options.delete(:per_page_links)
353 per_page_links = options.delete(:per_page_links)
354 url_param = params.dup
354 url_param = params.dup
355
355
356 html = ''
356 html = ''
357 if paginator.current.previous
357 if paginator.current.previous
358 # \xc2\xab(utf-8) = &#171;
358 # \xc2\xab(utf-8) = &#171;
359 html << link_to_content_update(
359 html << link_to_content_update(
360 "\xc2\xab " + l(:label_previous),
360 "\xc2\xab " + l(:label_previous),
361 url_param.merge(page_param => paginator.current.previous)) + ' '
361 url_param.merge(page_param => paginator.current.previous)) + ' '
362 end
362 end
363
363
364 html << (pagination_links_each(paginator, options) do |n|
364 html << (pagination_links_each(paginator, options) do |n|
365 link_to_content_update(n.to_s, url_param.merge(page_param => n))
365 link_to_content_update(n.to_s, url_param.merge(page_param => n))
366 end || '')
366 end || '')
367
367
368 if paginator.current.next
368 if paginator.current.next
369 # \xc2\xbb(utf-8) = &#187;
369 # \xc2\xbb(utf-8) = &#187;
370 html << ' ' + link_to_content_update(
370 html << ' ' + link_to_content_update(
371 (l(:label_next) + " \xc2\xbb"),
371 (l(:label_next) + " \xc2\xbb"),
372 url_param.merge(page_param => paginator.current.next))
372 url_param.merge(page_param => paginator.current.next))
373 end
373 end
374
374
375 unless count.nil?
375 unless count.nil?
376 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
376 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
377 if per_page_links != false && links = per_page_links(paginator.items_per_page)
377 if per_page_links != false && links = per_page_links(paginator.items_per_page)
378 html << " | #{links}"
378 html << " | #{links}"
379 end
379 end
380 end
380 end
381
381
382 html.html_safe
382 html.html_safe
383 end
383 end
384
384
385 def per_page_links(selected=nil)
385 def per_page_links(selected=nil)
386 links = Setting.per_page_options_array.collect do |n|
386 links = Setting.per_page_options_array.collect do |n|
387 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
387 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
388 end
388 end
389 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
389 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
390 end
390 end
391
391
392 def reorder_links(name, url)
392 def reorder_links(name, url)
393 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
393 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
394 url.merge({"#{name}[move_to]" => 'highest'}),
394 url.merge({"#{name}[move_to]" => 'highest'}),
395 :method => :post, :title => l(:label_sort_highest)) +
395 :method => :post, :title => l(:label_sort_highest)) +
396 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
396 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
397 url.merge({"#{name}[move_to]" => 'higher'}),
397 url.merge({"#{name}[move_to]" => 'higher'}),
398 :method => :post, :title => l(:label_sort_higher)) +
398 :method => :post, :title => l(:label_sort_higher)) +
399 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
399 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
400 url.merge({"#{name}[move_to]" => 'lower'}),
400 url.merge({"#{name}[move_to]" => 'lower'}),
401 :method => :post, :title => l(:label_sort_lower)) +
401 :method => :post, :title => l(:label_sort_lower)) +
402 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
402 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
403 url.merge({"#{name}[move_to]" => 'lowest'}),
403 url.merge({"#{name}[move_to]" => 'lowest'}),
404 :method => :post, :title => l(:label_sort_lowest))
404 :method => :post, :title => l(:label_sort_lowest))
405 end
405 end
406
406
407 def breadcrumb(*args)
407 def breadcrumb(*args)
408 elements = args.flatten
408 elements = args.flatten
409 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
409 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
410 end
410 end
411
411
412 def other_formats_links(&block)
412 def other_formats_links(&block)
413 concat('<p class="other-formats">' + l(:label_export_to))
413 concat('<p class="other-formats">' + l(:label_export_to))
414 yield Redmine::Views::OtherFormatsBuilder.new(self)
414 yield Redmine::Views::OtherFormatsBuilder.new(self)
415 concat('</p>')
415 concat('</p>')
416 end
416 end
417
417
418 def page_header_title
418 def page_header_title
419 if @project.nil? || @project.new_record?
419 if @project.nil? || @project.new_record?
420 h(Setting.app_title)
420 h(Setting.app_title)
421 else
421 else
422 b = []
422 b = []
423 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
423 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
424 if ancestors.any?
424 if ancestors.any?
425 root = ancestors.shift
425 root = ancestors.shift
426 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
426 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
427 if ancestors.size > 2
427 if ancestors.size > 2
428 b << "\xe2\x80\xa6"
428 b << "\xe2\x80\xa6"
429 ancestors = ancestors[-2, 2]
429 ancestors = ancestors[-2, 2]
430 end
430 end
431 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
431 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
432 end
432 end
433 b << h(@project)
433 b << h(@project)
434 b.join(" \xc2\xbb ").html_safe
434 b.join(" \xc2\xbb ").html_safe
435 end
435 end
436 end
436 end
437
437
438 def html_title(*args)
438 def html_title(*args)
439 if args.empty?
439 if args.empty?
440 title = []
440 title = @html_title || []
441 title << @project.name if @project
441 title << @project.name if @project
442 title += @html_title if @html_title
442 title << Setting.app_title unless Setting.app_title == title.last
443 title << Setting.app_title
444 title.select {|t| !t.blank? }.join(' - ')
443 title.select {|t| !t.blank? }.join(' - ')
445 else
444 else
446 @html_title ||= []
445 @html_title ||= []
447 @html_title += args
446 @html_title += args
448 end
447 end
449 end
448 end
450
449
451 # Returns the theme, controller name, and action as css classes for the
450 # Returns the theme, controller name, and action as css classes for the
452 # HTML body.
451 # HTML body.
453 def body_css_classes
452 def body_css_classes
454 css = []
453 css = []
455 if theme = Redmine::Themes.theme(Setting.ui_theme)
454 if theme = Redmine::Themes.theme(Setting.ui_theme)
456 css << 'theme-' + theme.name
455 css << 'theme-' + theme.name
457 end
456 end
458
457
459 css << 'controller-' + params[:controller]
458 css << 'controller-' + params[:controller]
460 css << 'action-' + params[:action]
459 css << 'action-' + params[:action]
461 css.join(' ')
460 css.join(' ')
462 end
461 end
463
462
464 def accesskey(s)
463 def accesskey(s)
465 Redmine::AccessKeys.key_for s
464 Redmine::AccessKeys.key_for s
466 end
465 end
467
466
468 # Formats text according to system settings.
467 # Formats text according to system settings.
469 # 2 ways to call this method:
468 # 2 ways to call this method:
470 # * with a String: textilizable(text, options)
469 # * with a String: textilizable(text, options)
471 # * with an object and one of its attribute: textilizable(issue, :description, options)
470 # * with an object and one of its attribute: textilizable(issue, :description, options)
472 def textilizable(*args)
471 def textilizable(*args)
473 options = args.last.is_a?(Hash) ? args.pop : {}
472 options = args.last.is_a?(Hash) ? args.pop : {}
474 case args.size
473 case args.size
475 when 1
474 when 1
476 obj = options[:object]
475 obj = options[:object]
477 text = args.shift
476 text = args.shift
478 when 2
477 when 2
479 obj = args.shift
478 obj = args.shift
480 attr = args.shift
479 attr = args.shift
481 text = obj.send(attr).to_s
480 text = obj.send(attr).to_s
482 else
481 else
483 raise ArgumentError, 'invalid arguments to textilizable'
482 raise ArgumentError, 'invalid arguments to textilizable'
484 end
483 end
485 return '' if text.blank?
484 return '' if text.blank?
486 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
485 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
487 only_path = options.delete(:only_path) == false ? false : true
486 only_path = options.delete(:only_path) == false ? false : true
488
487
489 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
488 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
490
489
491 @parsed_headings = []
490 @parsed_headings = []
492 @current_section = 0 if options[:edit_section_links]
491 @current_section = 0 if options[:edit_section_links]
493 text = parse_non_pre_blocks(text) do |text|
492 text = parse_non_pre_blocks(text) do |text|
494 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
493 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
495 send method_name, text, project, obj, attr, only_path, options
494 send method_name, text, project, obj, attr, only_path, options
496 end
495 end
497 end
496 end
498
497
499 if @parsed_headings.any?
498 if @parsed_headings.any?
500 replace_toc(text, @parsed_headings)
499 replace_toc(text, @parsed_headings)
501 end
500 end
502
501
503 text
502 text
504 end
503 end
505
504
506 def parse_non_pre_blocks(text)
505 def parse_non_pre_blocks(text)
507 s = StringScanner.new(text)
506 s = StringScanner.new(text)
508 tags = []
507 tags = []
509 parsed = ''
508 parsed = ''
510 while !s.eos?
509 while !s.eos?
511 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
510 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
512 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
511 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
513 if tags.empty?
512 if tags.empty?
514 yield text
513 yield text
515 end
514 end
516 parsed << text
515 parsed << text
517 if tag
516 if tag
518 if closing
517 if closing
519 if tags.last == tag.downcase
518 if tags.last == tag.downcase
520 tags.pop
519 tags.pop
521 end
520 end
522 else
521 else
523 tags << tag.downcase
522 tags << tag.downcase
524 end
523 end
525 parsed << full_tag
524 parsed << full_tag
526 end
525 end
527 end
526 end
528 # Close any non closing tags
527 # Close any non closing tags
529 while tag = tags.pop
528 while tag = tags.pop
530 parsed << "</#{tag}>"
529 parsed << "</#{tag}>"
531 end
530 end
532 parsed.html_safe
531 parsed.html_safe
533 end
532 end
534
533
535 def parse_inline_attachments(text, project, obj, attr, only_path, options)
534 def parse_inline_attachments(text, project, obj, attr, only_path, options)
536 # when using an image link, try to use an attachment, if possible
535 # when using an image link, try to use an attachment, if possible
537 if options[:attachments] || (obj && obj.respond_to?(:attachments))
536 if options[:attachments] || (obj && obj.respond_to?(:attachments))
538 attachments = nil
537 attachments = nil
539 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
538 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
540 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
539 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
541 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
540 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
542 # search for the picture in attachments
541 # search for the picture in attachments
543 if found = attachments.detect { |att| att.filename.downcase == filename }
542 if found = attachments.detect { |att| att.filename.downcase == filename }
544 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
543 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
545 desc = found.description.to_s.gsub('"', '')
544 desc = found.description.to_s.gsub('"', '')
546 if !desc.blank? && alttext.blank?
545 if !desc.blank? && alttext.blank?
547 alt = " title=\"#{desc}\" alt=\"#{desc}\""
546 alt = " title=\"#{desc}\" alt=\"#{desc}\""
548 end
547 end
549 "src=\"#{image_url}\"#{alt}".html_safe
548 "src=\"#{image_url}\"#{alt}".html_safe
550 else
549 else
551 m.html_safe
550 m.html_safe
552 end
551 end
553 end
552 end
554 end
553 end
555 end
554 end
556
555
557 # Wiki links
556 # Wiki links
558 #
557 #
559 # Examples:
558 # Examples:
560 # [[mypage]]
559 # [[mypage]]
561 # [[mypage|mytext]]
560 # [[mypage|mytext]]
562 # wiki links can refer other project wikis, using project name or identifier:
561 # wiki links can refer other project wikis, using project name or identifier:
563 # [[project:]] -> wiki starting page
562 # [[project:]] -> wiki starting page
564 # [[project:|mytext]]
563 # [[project:|mytext]]
565 # [[project:mypage]]
564 # [[project:mypage]]
566 # [[project:mypage|mytext]]
565 # [[project:mypage|mytext]]
567 def parse_wiki_links(text, project, obj, attr, only_path, options)
566 def parse_wiki_links(text, project, obj, attr, only_path, options)
568 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
567 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
569 link_project = project
568 link_project = project
570 esc, all, page, title = $1, $2, $3, $5
569 esc, all, page, title = $1, $2, $3, $5
571 if esc.nil?
570 if esc.nil?
572 if page =~ /^([^\:]+)\:(.*)$/
571 if page =~ /^([^\:]+)\:(.*)$/
573 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
572 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
574 page = $2
573 page = $2
575 title ||= $1 if page.blank?
574 title ||= $1 if page.blank?
576 end
575 end
577
576
578 if link_project && link_project.wiki
577 if link_project && link_project.wiki
579 # extract anchor
578 # extract anchor
580 anchor = nil
579 anchor = nil
581 if page =~ /^(.+?)\#(.+)$/
580 if page =~ /^(.+?)\#(.+)$/
582 page, anchor = $1, $2
581 page, anchor = $1, $2
583 end
582 end
584 anchor = sanitize_anchor_name(anchor) if anchor.present?
583 anchor = sanitize_anchor_name(anchor) if anchor.present?
585 # check if page exists
584 # check if page exists
586 wiki_page = link_project.wiki.find_page(page)
585 wiki_page = link_project.wiki.find_page(page)
587 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
586 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
588 "##{anchor}"
587 "##{anchor}"
589 else
588 else
590 case options[:wiki_links]
589 case options[:wiki_links]
591 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
590 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
592 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
591 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
593 else
592 else
594 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
593 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
595 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
594 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
596 end
595 end
597 end
596 end
598 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
597 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
599 else
598 else
600 # project or wiki doesn't exist
599 # project or wiki doesn't exist
601 all.html_safe
600 all.html_safe
602 end
601 end
603 else
602 else
604 all.html_safe
603 all.html_safe
605 end
604 end
606 end
605 end
607 end
606 end
608
607
609 # Redmine links
608 # Redmine links
610 #
609 #
611 # Examples:
610 # Examples:
612 # Issues:
611 # Issues:
613 # #52 -> Link to issue #52
612 # #52 -> Link to issue #52
614 # Changesets:
613 # Changesets:
615 # r52 -> Link to revision 52
614 # r52 -> Link to revision 52
616 # commit:a85130f -> Link to scmid starting with a85130f
615 # commit:a85130f -> Link to scmid starting with a85130f
617 # Documents:
616 # Documents:
618 # document#17 -> Link to document with id 17
617 # document#17 -> Link to document with id 17
619 # document:Greetings -> Link to the document with title "Greetings"
618 # document:Greetings -> Link to the document with title "Greetings"
620 # document:"Some document" -> Link to the document with title "Some document"
619 # document:"Some document" -> Link to the document with title "Some document"
621 # Versions:
620 # Versions:
622 # version#3 -> Link to version with id 3
621 # version#3 -> Link to version with id 3
623 # version:1.0.0 -> Link to version named "1.0.0"
622 # version:1.0.0 -> Link to version named "1.0.0"
624 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
623 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
625 # Attachments:
624 # Attachments:
626 # attachment:file.zip -> Link to the attachment of the current object named file.zip
625 # attachment:file.zip -> Link to the attachment of the current object named file.zip
627 # Source files:
626 # Source files:
628 # source:some/file -> Link to the file located at /some/file in the project's repository
627 # source:some/file -> Link to the file located at /some/file in the project's repository
629 # source:some/file@52 -> Link to the file's revision 52
628 # source:some/file@52 -> Link to the file's revision 52
630 # source:some/file#L120 -> Link to line 120 of the file
629 # source:some/file#L120 -> Link to line 120 of the file
631 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
630 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
632 # export:some/file -> Force the download of the file
631 # export:some/file -> Force the download of the file
633 # Forum messages:
632 # Forum messages:
634 # message#1218 -> Link to message with id 1218
633 # message#1218 -> Link to message with id 1218
635 #
634 #
636 # Links can refer other objects from other projects, using project identifier:
635 # Links can refer other objects from other projects, using project identifier:
637 # identifier:r52
636 # identifier:r52
638 # identifier:document:"Some document"
637 # identifier:document:"Some document"
639 # identifier:version:1.0.0
638 # identifier:version:1.0.0
640 # identifier:source:some/file
639 # identifier:source:some/file
641 def parse_redmine_links(text, project, obj, attr, only_path, options)
640 def parse_redmine_links(text, project, obj, attr, only_path, options)
642 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
641 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
643 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
642 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
644 link = nil
643 link = nil
645 if project_identifier
644 if project_identifier
646 project = Project.visible.find_by_identifier(project_identifier)
645 project = Project.visible.find_by_identifier(project_identifier)
647 end
646 end
648 if esc.nil?
647 if esc.nil?
649 if prefix.nil? && sep == 'r'
648 if prefix.nil? && sep == 'r'
650 # project.changesets.visible raises an SQL error because of a double join on repositories
649 # project.changesets.visible raises an SQL error because of a double join on repositories
651 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
650 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
652 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
651 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
653 :class => 'changeset',
652 :class => 'changeset',
654 :title => truncate_single_line(changeset.comments, :length => 100))
653 :title => truncate_single_line(changeset.comments, :length => 100))
655 end
654 end
656 elsif sep == '#'
655 elsif sep == '#'
657 oid = identifier.to_i
656 oid = identifier.to_i
658 case prefix
657 case prefix
659 when nil
658 when nil
660 if issue = Issue.visible.find_by_id(oid, :include => :status)
659 if issue = Issue.visible.find_by_id(oid, :include => :status)
661 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
660 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
662 :class => issue.css_classes,
661 :class => issue.css_classes,
663 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
662 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
664 end
663 end
665 when 'document'
664 when 'document'
666 if document = Document.visible.find_by_id(oid)
665 if document = Document.visible.find_by_id(oid)
667 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
666 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
668 :class => 'document'
667 :class => 'document'
669 end
668 end
670 when 'version'
669 when 'version'
671 if version = Version.visible.find_by_id(oid)
670 if version = Version.visible.find_by_id(oid)
672 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
671 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
673 :class => 'version'
672 :class => 'version'
674 end
673 end
675 when 'message'
674 when 'message'
676 if message = Message.visible.find_by_id(oid, :include => :parent)
675 if message = Message.visible.find_by_id(oid, :include => :parent)
677 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
676 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
678 end
677 end
679 when 'forum'
678 when 'forum'
680 if board = Board.visible.find_by_id(oid)
679 if board = Board.visible.find_by_id(oid)
681 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
680 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
682 :class => 'board'
681 :class => 'board'
683 end
682 end
684 when 'news'
683 when 'news'
685 if news = News.visible.find_by_id(oid)
684 if news = News.visible.find_by_id(oid)
686 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
685 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
687 :class => 'news'
686 :class => 'news'
688 end
687 end
689 when 'project'
688 when 'project'
690 if p = Project.visible.find_by_id(oid)
689 if p = Project.visible.find_by_id(oid)
691 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
690 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
692 end
691 end
693 end
692 end
694 elsif sep == ':'
693 elsif sep == ':'
695 # removes the double quotes if any
694 # removes the double quotes if any
696 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
695 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
697 case prefix
696 case prefix
698 when 'document'
697 when 'document'
699 if project && document = project.documents.visible.find_by_title(name)
698 if project && document = project.documents.visible.find_by_title(name)
700 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
699 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
701 :class => 'document'
700 :class => 'document'
702 end
701 end
703 when 'version'
702 when 'version'
704 if project && version = project.versions.visible.find_by_name(name)
703 if project && version = project.versions.visible.find_by_name(name)
705 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
704 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
706 :class => 'version'
705 :class => 'version'
707 end
706 end
708 when 'forum'
707 when 'forum'
709 if project && board = project.boards.visible.find_by_name(name)
708 if project && board = project.boards.visible.find_by_name(name)
710 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
709 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
711 :class => 'board'
710 :class => 'board'
712 end
711 end
713 when 'news'
712 when 'news'
714 if project && news = project.news.visible.find_by_title(name)
713 if project && news = project.news.visible.find_by_title(name)
715 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
714 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
716 :class => 'news'
715 :class => 'news'
717 end
716 end
718 when 'commit'
717 when 'commit'
719 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
718 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
720 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
719 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
721 :class => 'changeset',
720 :class => 'changeset',
722 :title => truncate_single_line(h(changeset.comments), :length => 100)
721 :title => truncate_single_line(h(changeset.comments), :length => 100)
723 end
722 end
724 when 'source', 'export'
723 when 'source', 'export'
725 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
724 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
726 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
725 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
727 path, rev, anchor = $1, $3, $5
726 path, rev, anchor = $1, $3, $5
728 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
727 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
729 :path => to_path_param(path),
728 :path => to_path_param(path),
730 :rev => rev,
729 :rev => rev,
731 :anchor => anchor,
730 :anchor => anchor,
732 :format => (prefix == 'export' ? 'raw' : nil)},
731 :format => (prefix == 'export' ? 'raw' : nil)},
733 :class => (prefix == 'export' ? 'source download' : 'source')
732 :class => (prefix == 'export' ? 'source download' : 'source')
734 end
733 end
735 when 'attachment'
734 when 'attachment'
736 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
735 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
737 if attachments && attachment = attachments.detect {|a| a.filename == name }
736 if attachments && attachment = attachments.detect {|a| a.filename == name }
738 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
737 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
739 :class => 'attachment'
738 :class => 'attachment'
740 end
739 end
741 when 'project'
740 when 'project'
742 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
741 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
743 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
742 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
744 end
743 end
745 end
744 end
746 end
745 end
747 end
746 end
748 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
747 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
749 end
748 end
750 end
749 end
751
750
752 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
751 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
753
752
754 def parse_sections(text, project, obj, attr, only_path, options)
753 def parse_sections(text, project, obj, attr, only_path, options)
755 return unless options[:edit_section_links]
754 return unless options[:edit_section_links]
756 text.gsub!(HEADING_RE) do
755 text.gsub!(HEADING_RE) do
757 @current_section += 1
756 @current_section += 1
758 if @current_section > 1
757 if @current_section > 1
759 content_tag('div',
758 content_tag('div',
760 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
759 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
761 :class => 'contextual',
760 :class => 'contextual',
762 :title => l(:button_edit_section)) + $1
761 :title => l(:button_edit_section)) + $1
763 else
762 else
764 $1
763 $1
765 end
764 end
766 end
765 end
767 end
766 end
768
767
769 # Headings and TOC
768 # Headings and TOC
770 # Adds ids and links to headings unless options[:headings] is set to false
769 # Adds ids and links to headings unless options[:headings] is set to false
771 def parse_headings(text, project, obj, attr, only_path, options)
770 def parse_headings(text, project, obj, attr, only_path, options)
772 return if options[:headings] == false
771 return if options[:headings] == false
773
772
774 text.gsub!(HEADING_RE) do
773 text.gsub!(HEADING_RE) do
775 level, attrs, content = $2.to_i, $3, $4
774 level, attrs, content = $2.to_i, $3, $4
776 item = strip_tags(content).strip
775 item = strip_tags(content).strip
777 anchor = sanitize_anchor_name(item)
776 anchor = sanitize_anchor_name(item)
778 # used for single-file wiki export
777 # used for single-file wiki export
779 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
778 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
780 @parsed_headings << [level, anchor, item]
779 @parsed_headings << [level, anchor, item]
781 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
780 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
782 end
781 end
783 end
782 end
784
783
785 MACROS_RE = /
784 MACROS_RE = /
786 (!)? # escaping
785 (!)? # escaping
787 (
786 (
788 \{\{ # opening tag
787 \{\{ # opening tag
789 ([\w]+) # macro name
788 ([\w]+) # macro name
790 (\(([^\}]*)\))? # optional arguments
789 (\(([^\}]*)\))? # optional arguments
791 \}\} # closing tag
790 \}\} # closing tag
792 )
791 )
793 /x unless const_defined?(:MACROS_RE)
792 /x unless const_defined?(:MACROS_RE)
794
793
795 # Macros substitution
794 # Macros substitution
796 def parse_macros(text, project, obj, attr, only_path, options)
795 def parse_macros(text, project, obj, attr, only_path, options)
797 text.gsub!(MACROS_RE) do
796 text.gsub!(MACROS_RE) do
798 esc, all, macro = $1, $2, $3.downcase
797 esc, all, macro = $1, $2, $3.downcase
799 args = ($5 || '').split(',').each(&:strip)
798 args = ($5 || '').split(',').each(&:strip)
800 if esc.nil?
799 if esc.nil?
801 begin
800 begin
802 exec_macro(macro, obj, args)
801 exec_macro(macro, obj, args)
803 rescue => e
802 rescue => e
804 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
803 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
805 end || all
804 end || all
806 else
805 else
807 all
806 all
808 end
807 end
809 end
808 end
810 end
809 end
811
810
812 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
811 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
813
812
814 # Renders the TOC with given headings
813 # Renders the TOC with given headings
815 def replace_toc(text, headings)
814 def replace_toc(text, headings)
816 text.gsub!(TOC_RE) do
815 text.gsub!(TOC_RE) do
817 if headings.empty?
816 if headings.empty?
818 ''
817 ''
819 else
818 else
820 div_class = 'toc'
819 div_class = 'toc'
821 div_class << ' right' if $1 == '>'
820 div_class << ' right' if $1 == '>'
822 div_class << ' left' if $1 == '<'
821 div_class << ' left' if $1 == '<'
823 out = "<ul class=\"#{div_class}\"><li>"
822 out = "<ul class=\"#{div_class}\"><li>"
824 root = headings.map(&:first).min
823 root = headings.map(&:first).min
825 current = root
824 current = root
826 started = false
825 started = false
827 headings.each do |level, anchor, item|
826 headings.each do |level, anchor, item|
828 if level > current
827 if level > current
829 out << '<ul><li>' * (level - current)
828 out << '<ul><li>' * (level - current)
830 elsif level < current
829 elsif level < current
831 out << "</li></ul>\n" * (current - level) + "</li><li>"
830 out << "</li></ul>\n" * (current - level) + "</li><li>"
832 elsif started
831 elsif started
833 out << '</li><li>'
832 out << '</li><li>'
834 end
833 end
835 out << "<a href=\"##{anchor}\">#{item}</a>"
834 out << "<a href=\"##{anchor}\">#{item}</a>"
836 current = level
835 current = level
837 started = true
836 started = true
838 end
837 end
839 out << '</li></ul>' * (current - root)
838 out << '</li></ul>' * (current - root)
840 out << '</li></ul>'
839 out << '</li></ul>'
841 end
840 end
842 end
841 end
843 end
842 end
844
843
845 # Same as Rails' simple_format helper without using paragraphs
844 # Same as Rails' simple_format helper without using paragraphs
846 def simple_format_without_paragraph(text)
845 def simple_format_without_paragraph(text)
847 text.to_s.
846 text.to_s.
848 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
847 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
849 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
848 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
850 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
849 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
851 html_safe
850 html_safe
852 end
851 end
853
852
854 def lang_options_for_select(blank=true)
853 def lang_options_for_select(blank=true)
855 (blank ? [["(auto)", ""]] : []) +
854 (blank ? [["(auto)", ""]] : []) +
856 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
855 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
857 end
856 end
858
857
859 def label_tag_for(name, option_tags = nil, options = {})
858 def label_tag_for(name, option_tags = nil, options = {})
860 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
859 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
861 content_tag("label", label_text)
860 content_tag("label", label_text)
862 end
861 end
863
862
864 def labelled_tabular_form_for(name, object, options, &proc)
863 def labelled_tabular_form_for(name, object, options, &proc)
865 options[:html] ||= {}
864 options[:html] ||= {}
866 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
865 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
867 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
866 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
868 end
867 end
869
868
870 def back_url_hidden_field_tag
869 def back_url_hidden_field_tag
871 back_url = params[:back_url] || request.env['HTTP_REFERER']
870 back_url = params[:back_url] || request.env['HTTP_REFERER']
872 back_url = CGI.unescape(back_url.to_s)
871 back_url = CGI.unescape(back_url.to_s)
873 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
872 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
874 end
873 end
875
874
876 def check_all_links(form_name)
875 def check_all_links(form_name)
877 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
876 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
878 " | ".html_safe +
877 " | ".html_safe +
879 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
878 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
880 end
879 end
881
880
882 def progress_bar(pcts, options={})
881 def progress_bar(pcts, options={})
883 pcts = [pcts, pcts] unless pcts.is_a?(Array)
882 pcts = [pcts, pcts] unless pcts.is_a?(Array)
884 pcts = pcts.collect(&:round)
883 pcts = pcts.collect(&:round)
885 pcts[1] = pcts[1] - pcts[0]
884 pcts[1] = pcts[1] - pcts[0]
886 pcts << (100 - pcts[1] - pcts[0])
885 pcts << (100 - pcts[1] - pcts[0])
887 width = options[:width] || '100px;'
886 width = options[:width] || '100px;'
888 legend = options[:legend] || ''
887 legend = options[:legend] || ''
889 content_tag('table',
888 content_tag('table',
890 content_tag('tr',
889 content_tag('tr',
891 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
890 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
892 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
891 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
893 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
892 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
894 ), :class => 'progress', :style => "width: #{width};").html_safe +
893 ), :class => 'progress', :style => "width: #{width};").html_safe +
895 content_tag('p', legend, :class => 'pourcent').html_safe
894 content_tag('p', legend, :class => 'pourcent').html_safe
896 end
895 end
897
896
898 def checked_image(checked=true)
897 def checked_image(checked=true)
899 if checked
898 if checked
900 image_tag 'toggle_check.png'
899 image_tag 'toggle_check.png'
901 end
900 end
902 end
901 end
903
902
904 def context_menu(url)
903 def context_menu(url)
905 unless @context_menu_included
904 unless @context_menu_included
906 content_for :header_tags do
905 content_for :header_tags do
907 javascript_include_tag('context_menu') +
906 javascript_include_tag('context_menu') +
908 stylesheet_link_tag('context_menu')
907 stylesheet_link_tag('context_menu')
909 end
908 end
910 if l(:direction) == 'rtl'
909 if l(:direction) == 'rtl'
911 content_for :header_tags do
910 content_for :header_tags do
912 stylesheet_link_tag('context_menu_rtl')
911 stylesheet_link_tag('context_menu_rtl')
913 end
912 end
914 end
913 end
915 @context_menu_included = true
914 @context_menu_included = true
916 end
915 end
917 javascript_tag "new ContextMenu('#{ url_for(url) }')"
916 javascript_tag "new ContextMenu('#{ url_for(url) }')"
918 end
917 end
919
918
920 def context_menu_link(name, url, options={})
919 def context_menu_link(name, url, options={})
921 options[:class] ||= ''
920 options[:class] ||= ''
922 if options.delete(:selected)
921 if options.delete(:selected)
923 options[:class] << ' icon-checked disabled'
922 options[:class] << ' icon-checked disabled'
924 options[:disabled] = true
923 options[:disabled] = true
925 end
924 end
926 if options.delete(:disabled)
925 if options.delete(:disabled)
927 options.delete(:method)
926 options.delete(:method)
928 options.delete(:confirm)
927 options.delete(:confirm)
929 options.delete(:onclick)
928 options.delete(:onclick)
930 options[:class] << ' disabled'
929 options[:class] << ' disabled'
931 url = '#'
930 url = '#'
932 end
931 end
933 link_to h(name), url, options
932 link_to h(name), url, options
934 end
933 end
935
934
936 def calendar_for(field_id)
935 def calendar_for(field_id)
937 include_calendar_headers_tags
936 include_calendar_headers_tags
938 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
937 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
939 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
938 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
940 end
939 end
941
940
942 def include_calendar_headers_tags
941 def include_calendar_headers_tags
943 unless @calendar_headers_tags_included
942 unless @calendar_headers_tags_included
944 @calendar_headers_tags_included = true
943 @calendar_headers_tags_included = true
945 content_for :header_tags do
944 content_for :header_tags do
946 start_of_week = case Setting.start_of_week.to_i
945 start_of_week = case Setting.start_of_week.to_i
947 when 1
946 when 1
948 'Calendar._FD = 1;' # Monday
947 'Calendar._FD = 1;' # Monday
949 when 7
948 when 7
950 'Calendar._FD = 0;' # Sunday
949 'Calendar._FD = 0;' # Sunday
951 when 6
950 when 6
952 'Calendar._FD = 6;' # Saturday
951 'Calendar._FD = 6;' # Saturday
953 else
952 else
954 '' # use language
953 '' # use language
955 end
954 end
956
955
957 javascript_include_tag('calendar/calendar') +
956 javascript_include_tag('calendar/calendar') +
958 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
957 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
959 javascript_tag(start_of_week) +
958 javascript_tag(start_of_week) +
960 javascript_include_tag('calendar/calendar-setup') +
959 javascript_include_tag('calendar/calendar-setup') +
961 stylesheet_link_tag('calendar')
960 stylesheet_link_tag('calendar')
962 end
961 end
963 end
962 end
964 end
963 end
965
964
966 def content_for(name, content = nil, &block)
965 def content_for(name, content = nil, &block)
967 @has_content ||= {}
966 @has_content ||= {}
968 @has_content[name] = true
967 @has_content[name] = true
969 super(name, content, &block)
968 super(name, content, &block)
970 end
969 end
971
970
972 def has_content?(name)
971 def has_content?(name)
973 (@has_content && @has_content[name]) || false
972 (@has_content && @has_content[name]) || false
974 end
973 end
975
974
976 def email_delivery_enabled?
975 def email_delivery_enabled?
977 !!ActionMailer::Base.perform_deliveries
976 !!ActionMailer::Base.perform_deliveries
978 end
977 end
979
978
980 # Returns the avatar image tag for the given +user+ if avatars are enabled
979 # Returns the avatar image tag for the given +user+ if avatars are enabled
981 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
980 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
982 def avatar(user, options = { })
981 def avatar(user, options = { })
983 if Setting.gravatar_enabled?
982 if Setting.gravatar_enabled?
984 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
983 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
985 email = nil
984 email = nil
986 if user.respond_to?(:mail)
985 if user.respond_to?(:mail)
987 email = user.mail
986 email = user.mail
988 elsif user.to_s =~ %r{<(.+?)>}
987 elsif user.to_s =~ %r{<(.+?)>}
989 email = $1
988 email = $1
990 end
989 end
991 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
990 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
992 else
991 else
993 ''
992 ''
994 end
993 end
995 end
994 end
996
995
997 def sanitize_anchor_name(anchor)
996 def sanitize_anchor_name(anchor)
998 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
997 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
999 end
998 end
1000
999
1001 # Returns the javascript tags that are included in the html layout head
1000 # Returns the javascript tags that are included in the html layout head
1002 def javascript_heads
1001 def javascript_heads
1003 tags = javascript_include_tag(:defaults)
1002 tags = javascript_include_tag(:defaults)
1004 unless User.current.pref.warn_on_leaving_unsaved == '0'
1003 unless User.current.pref.warn_on_leaving_unsaved == '0'
1005 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1004 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1006 end
1005 end
1007 tags
1006 tags
1008 end
1007 end
1009
1008
1010 def favicon
1009 def favicon
1011 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1010 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1012 end
1011 end
1013
1012
1014 def robot_exclusion_tag
1013 def robot_exclusion_tag
1015 '<meta name="robots" content="noindex,follow,noarchive" />'
1014 '<meta name="robots" content="noindex,follow,noarchive" />'
1016 end
1015 end
1017
1016
1018 # Returns true if arg is expected in the API response
1017 # Returns true if arg is expected in the API response
1019 def include_in_api_response?(arg)
1018 def include_in_api_response?(arg)
1020 unless @included_in_api_response
1019 unless @included_in_api_response
1021 param = params[:include]
1020 param = params[:include]
1022 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1021 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1023 @included_in_api_response.collect!(&:strip)
1022 @included_in_api_response.collect!(&:strip)
1024 end
1023 end
1025 @included_in_api_response.include?(arg.to_s)
1024 @included_in_api_response.include?(arg.to_s)
1026 end
1025 end
1027
1026
1028 # Returns options or nil if nometa param or X-Redmine-Nometa header
1027 # Returns options or nil if nometa param or X-Redmine-Nometa header
1029 # was set in the request
1028 # was set in the request
1030 def api_meta(options)
1029 def api_meta(options)
1031 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1030 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1032 # compatibility mode for activeresource clients that raise
1031 # compatibility mode for activeresource clients that raise
1033 # an error when unserializing an array with attributes
1032 # an error when unserializing an array with attributes
1034 nil
1033 nil
1035 else
1034 else
1036 options
1035 options
1037 end
1036 end
1038 end
1037 end
1039
1038
1040 private
1039 private
1041
1040
1042 def wiki_helper
1041 def wiki_helper
1043 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1042 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1044 extend helper
1043 extend helper
1045 return self
1044 return self
1046 end
1045 end
1047
1046
1048 def link_to_content_update(text, url_params = {}, html_options = {})
1047 def link_to_content_update(text, url_params = {}, html_options = {})
1049 link_to(text, url_params, html_options)
1048 link_to(text, url_params, html_options)
1050 end
1049 end
1051 end
1050 end
@@ -1,1816 +1,1818
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 class IssuesControllerTest < ActionController::TestCase
21 class IssuesControllerTest < ActionController::TestCase
22 fixtures :projects,
22 fixtures :projects,
23 :users,
23 :users,
24 :roles,
24 :roles,
25 :members,
25 :members,
26 :member_roles,
26 :member_roles,
27 :issues,
27 :issues,
28 :issue_statuses,
28 :issue_statuses,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries
44 :queries
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index
53 def test_index
54 Setting.default_language = 'en'
54 Setting.default_language = 'en'
55
55
56 get :index
56 get :index
57 assert_response :success
57 assert_response :success
58 assert_template 'index'
58 assert_template 'index'
59 assert_not_nil assigns(:issues)
59 assert_not_nil assigns(:issues)
60 assert_nil assigns(:project)
60 assert_nil assigns(:project)
61 assert_tag :tag => 'a', :content => /Can't print recipes/
61 assert_tag :tag => 'a', :content => /Can't print recipes/
62 assert_tag :tag => 'a', :content => /Subproject issue/
62 assert_tag :tag => 'a', :content => /Subproject issue/
63 # private projects hidden
63 # private projects hidden
64 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
64 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
65 assert_no_tag :tag => 'a', :content => /Issue on project 2/
65 assert_no_tag :tag => 'a', :content => /Issue on project 2/
66 # project column
66 # project column
67 assert_tag :tag => 'th', :content => /Project/
67 assert_tag :tag => 'th', :content => /Project/
68 end
68 end
69
69
70 def test_index_should_not_list_issues_when_module_disabled
70 def test_index_should_not_list_issues_when_module_disabled
71 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
71 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
72 get :index
72 get :index
73 assert_response :success
73 assert_response :success
74 assert_template 'index'
74 assert_template 'index'
75 assert_not_nil assigns(:issues)
75 assert_not_nil assigns(:issues)
76 assert_nil assigns(:project)
76 assert_nil assigns(:project)
77 assert_no_tag :tag => 'a', :content => /Can't print recipes/
77 assert_no_tag :tag => 'a', :content => /Can't print recipes/
78 assert_tag :tag => 'a', :content => /Subproject issue/
78 assert_tag :tag => 'a', :content => /Subproject issue/
79 end
79 end
80
80
81 def test_index_should_list_visible_issues_only
81 def test_index_should_list_visible_issues_only
82 get :index, :per_page => 100
82 get :index, :per_page => 100
83 assert_response :success
83 assert_response :success
84 assert_not_nil assigns(:issues)
84 assert_not_nil assigns(:issues)
85 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
85 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
86 end
86 end
87
87
88 def test_index_with_project
88 def test_index_with_project
89 Setting.display_subprojects_issues = 0
89 Setting.display_subprojects_issues = 0
90 get :index, :project_id => 1
90 get :index, :project_id => 1
91 assert_response :success
91 assert_response :success
92 assert_template 'index'
92 assert_template 'index'
93 assert_not_nil assigns(:issues)
93 assert_not_nil assigns(:issues)
94 assert_tag :tag => 'a', :content => /Can't print recipes/
94 assert_tag :tag => 'a', :content => /Can't print recipes/
95 assert_no_tag :tag => 'a', :content => /Subproject issue/
95 assert_no_tag :tag => 'a', :content => /Subproject issue/
96 end
96 end
97
97
98 def test_index_with_project_and_subprojects
98 def test_index_with_project_and_subprojects
99 Setting.display_subprojects_issues = 1
99 Setting.display_subprojects_issues = 1
100 get :index, :project_id => 1
100 get :index, :project_id => 1
101 assert_response :success
101 assert_response :success
102 assert_template 'index'
102 assert_template 'index'
103 assert_not_nil assigns(:issues)
103 assert_not_nil assigns(:issues)
104 assert_tag :tag => 'a', :content => /Can't print recipes/
104 assert_tag :tag => 'a', :content => /Can't print recipes/
105 assert_tag :tag => 'a', :content => /Subproject issue/
105 assert_tag :tag => 'a', :content => /Subproject issue/
106 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
106 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
107 end
107 end
108
108
109 def test_index_with_project_and_subprojects_should_show_private_subprojects
109 def test_index_with_project_and_subprojects_should_show_private_subprojects
110 @request.session[:user_id] = 2
110 @request.session[:user_id] = 2
111 Setting.display_subprojects_issues = 1
111 Setting.display_subprojects_issues = 1
112 get :index, :project_id => 1
112 get :index, :project_id => 1
113 assert_response :success
113 assert_response :success
114 assert_template 'index'
114 assert_template 'index'
115 assert_not_nil assigns(:issues)
115 assert_not_nil assigns(:issues)
116 assert_tag :tag => 'a', :content => /Can't print recipes/
116 assert_tag :tag => 'a', :content => /Can't print recipes/
117 assert_tag :tag => 'a', :content => /Subproject issue/
117 assert_tag :tag => 'a', :content => /Subproject issue/
118 assert_tag :tag => 'a', :content => /Issue of a private subproject/
118 assert_tag :tag => 'a', :content => /Issue of a private subproject/
119 end
119 end
120
120
121 def test_index_with_project_and_default_filter
121 def test_index_with_project_and_default_filter
122 get :index, :project_id => 1, :set_filter => 1
122 get :index, :project_id => 1, :set_filter => 1
123 assert_response :success
123 assert_response :success
124 assert_template 'index'
124 assert_template 'index'
125 assert_not_nil assigns(:issues)
125 assert_not_nil assigns(:issues)
126
126
127 query = assigns(:query)
127 query = assigns(:query)
128 assert_not_nil query
128 assert_not_nil query
129 # default filter
129 # default filter
130 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
130 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
131 end
131 end
132
132
133 def test_index_with_project_and_filter
133 def test_index_with_project_and_filter
134 get :index, :project_id => 1, :set_filter => 1,
134 get :index, :project_id => 1, :set_filter => 1,
135 :f => ['tracker_id'],
135 :f => ['tracker_id'],
136 :op => {'tracker_id' => '='},
136 :op => {'tracker_id' => '='},
137 :v => {'tracker_id' => ['1']}
137 :v => {'tracker_id' => ['1']}
138 assert_response :success
138 assert_response :success
139 assert_template 'index'
139 assert_template 'index'
140 assert_not_nil assigns(:issues)
140 assert_not_nil assigns(:issues)
141
141
142 query = assigns(:query)
142 query = assigns(:query)
143 assert_not_nil query
143 assert_not_nil query
144 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
144 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
145 end
145 end
146
146
147 def test_index_with_short_filters
147 def test_index_with_short_filters
148
148
149 to_test = {
149 to_test = {
150 'status_id' => {
150 'status_id' => {
151 'o' => { :op => 'o', :values => [''] },
151 'o' => { :op => 'o', :values => [''] },
152 'c' => { :op => 'c', :values => [''] },
152 'c' => { :op => 'c', :values => [''] },
153 '7' => { :op => '=', :values => ['7'] },
153 '7' => { :op => '=', :values => ['7'] },
154 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
154 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
155 '=7' => { :op => '=', :values => ['7'] },
155 '=7' => { :op => '=', :values => ['7'] },
156 '!3' => { :op => '!', :values => ['3'] },
156 '!3' => { :op => '!', :values => ['3'] },
157 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
157 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
158 'subject' => {
158 'subject' => {
159 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
159 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
160 'o' => { :op => '=', :values => ['o'] },
160 'o' => { :op => '=', :values => ['o'] },
161 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
161 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
162 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
162 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
163 'tracker_id' => {
163 'tracker_id' => {
164 '3' => { :op => '=', :values => ['3'] },
164 '3' => { :op => '=', :values => ['3'] },
165 '=3' => { :op => '=', :values => ['3'] }},
165 '=3' => { :op => '=', :values => ['3'] }},
166 'start_date' => {
166 'start_date' => {
167 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
167 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
168 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
168 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
169 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
169 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
170 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
170 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
171 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
171 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
172 '<t+2' => { :op => '<t+', :values => ['2'] },
172 '<t+2' => { :op => '<t+', :values => ['2'] },
173 '>t+2' => { :op => '>t+', :values => ['2'] },
173 '>t+2' => { :op => '>t+', :values => ['2'] },
174 't+2' => { :op => 't+', :values => ['2'] },
174 't+2' => { :op => 't+', :values => ['2'] },
175 't' => { :op => 't', :values => [''] },
175 't' => { :op => 't', :values => [''] },
176 'w' => { :op => 'w', :values => [''] },
176 'w' => { :op => 'w', :values => [''] },
177 '>t-2' => { :op => '>t-', :values => ['2'] },
177 '>t-2' => { :op => '>t-', :values => ['2'] },
178 '<t-2' => { :op => '<t-', :values => ['2'] },
178 '<t-2' => { :op => '<t-', :values => ['2'] },
179 't-2' => { :op => 't-', :values => ['2'] }},
179 't-2' => { :op => 't-', :values => ['2'] }},
180 'created_on' => {
180 'created_on' => {
181 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
181 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
182 '<t+2' => { :op => '=', :values => ['<t+2'] },
182 '<t+2' => { :op => '=', :values => ['<t+2'] },
183 '>t+2' => { :op => '=', :values => ['>t+2'] },
183 '>t+2' => { :op => '=', :values => ['>t+2'] },
184 't+2' => { :op => 't', :values => ['+2'] }},
184 't+2' => { :op => 't', :values => ['+2'] }},
185 'cf_1' => {
185 'cf_1' => {
186 'c' => { :op => '=', :values => ['c'] },
186 'c' => { :op => '=', :values => ['c'] },
187 '!c' => { :op => '!', :values => ['c'] },
187 '!c' => { :op => '!', :values => ['c'] },
188 '!*' => { :op => '!*', :values => [''] },
188 '!*' => { :op => '!*', :values => [''] },
189 '*' => { :op => '*', :values => [''] }},
189 '*' => { :op => '*', :values => [''] }},
190 'estimated_hours' => {
190 'estimated_hours' => {
191 '=13.4' => { :op => '=', :values => ['13.4'] },
191 '=13.4' => { :op => '=', :values => ['13.4'] },
192 '>=45' => { :op => '>=', :values => ['45'] },
192 '>=45' => { :op => '>=', :values => ['45'] },
193 '<=125' => { :op => '<=', :values => ['125'] },
193 '<=125' => { :op => '<=', :values => ['125'] },
194 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
194 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
195 '!*' => { :op => '!*', :values => [''] },
195 '!*' => { :op => '!*', :values => [''] },
196 '*' => { :op => '*', :values => [''] }}
196 '*' => { :op => '*', :values => [''] }}
197 }
197 }
198
198
199 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
199 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
200
200
201 to_test.each do |field, expression_and_expected|
201 to_test.each do |field, expression_and_expected|
202 expression_and_expected.each do |filter_expression, expected|
202 expression_and_expected.each do |filter_expression, expected|
203
203
204 get :index, :set_filter => 1, field => filter_expression
204 get :index, :set_filter => 1, field => filter_expression
205
205
206 assert_response :success
206 assert_response :success
207 assert_template 'index'
207 assert_template 'index'
208 assert_not_nil assigns(:issues)
208 assert_not_nil assigns(:issues)
209
209
210 query = assigns(:query)
210 query = assigns(:query)
211 assert_not_nil query
211 assert_not_nil query
212 assert query.has_filter?(field)
212 assert query.has_filter?(field)
213 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
213 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
214 end
214 end
215 end
215 end
216
216
217 end
217 end
218
218
219 def test_index_with_project_and_empty_filters
219 def test_index_with_project_and_empty_filters
220 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
220 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
221 assert_response :success
221 assert_response :success
222 assert_template 'index'
222 assert_template 'index'
223 assert_not_nil assigns(:issues)
223 assert_not_nil assigns(:issues)
224
224
225 query = assigns(:query)
225 query = assigns(:query)
226 assert_not_nil query
226 assert_not_nil query
227 # no filter
227 # no filter
228 assert_equal({}, query.filters)
228 assert_equal({}, query.filters)
229 end
229 end
230
230
231 def test_index_with_query
231 def test_index_with_query
232 get :index, :project_id => 1, :query_id => 5
232 get :index, :project_id => 1, :query_id => 5
233 assert_response :success
233 assert_response :success
234 assert_template 'index'
234 assert_template 'index'
235 assert_not_nil assigns(:issues)
235 assert_not_nil assigns(:issues)
236 assert_nil assigns(:issue_count_by_group)
236 assert_nil assigns(:issue_count_by_group)
237 end
237 end
238
238
239 def test_index_with_query_grouped_by_tracker
239 def test_index_with_query_grouped_by_tracker
240 get :index, :project_id => 1, :query_id => 6
240 get :index, :project_id => 1, :query_id => 6
241 assert_response :success
241 assert_response :success
242 assert_template 'index'
242 assert_template 'index'
243 assert_not_nil assigns(:issues)
243 assert_not_nil assigns(:issues)
244 assert_not_nil assigns(:issue_count_by_group)
244 assert_not_nil assigns(:issue_count_by_group)
245 end
245 end
246
246
247 def test_index_with_query_grouped_by_list_custom_field
247 def test_index_with_query_grouped_by_list_custom_field
248 get :index, :project_id => 1, :query_id => 9
248 get :index, :project_id => 1, :query_id => 9
249 assert_response :success
249 assert_response :success
250 assert_template 'index'
250 assert_template 'index'
251 assert_not_nil assigns(:issues)
251 assert_not_nil assigns(:issues)
252 assert_not_nil assigns(:issue_count_by_group)
252 assert_not_nil assigns(:issue_count_by_group)
253 end
253 end
254
254
255 def test_private_query_should_not_be_available_to_other_users
255 def test_private_query_should_not_be_available_to_other_users
256 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
256 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
257 @request.session[:user_id] = 3
257 @request.session[:user_id] = 3
258
258
259 get :index, :query_id => q.id
259 get :index, :query_id => q.id
260 assert_response 403
260 assert_response 403
261 end
261 end
262
262
263 def test_private_query_should_be_available_to_its_user
263 def test_private_query_should_be_available_to_its_user
264 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
264 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
265 @request.session[:user_id] = 2
265 @request.session[:user_id] = 2
266
266
267 get :index, :query_id => q.id
267 get :index, :query_id => q.id
268 assert_response :success
268 assert_response :success
269 end
269 end
270
270
271 def test_public_query_should_be_available_to_other_users
271 def test_public_query_should_be_available_to_other_users
272 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
272 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
273 @request.session[:user_id] = 3
273 @request.session[:user_id] = 3
274
274
275 get :index, :query_id => q.id
275 get :index, :query_id => q.id
276 assert_response :success
276 assert_response :success
277 end
277 end
278
278
279 def test_index_sort_by_field_not_included_in_columns
279 def test_index_sort_by_field_not_included_in_columns
280 Setting.issue_list_default_columns = %w(subject author)
280 Setting.issue_list_default_columns = %w(subject author)
281 get :index, :sort => 'tracker'
281 get :index, :sort => 'tracker'
282 end
282 end
283
283
284 def test_index_csv_with_project
284 def test_index_csv_with_project
285 Setting.default_language = 'en'
285 Setting.default_language = 'en'
286
286
287 get :index, :format => 'csv'
287 get :index, :format => 'csv'
288 assert_response :success
288 assert_response :success
289 assert_not_nil assigns(:issues)
289 assert_not_nil assigns(:issues)
290 assert_equal 'text/csv', @response.content_type
290 assert_equal 'text/csv', @response.content_type
291 assert @response.body.starts_with?("#,")
291 assert @response.body.starts_with?("#,")
292
292
293 get :index, :project_id => 1, :format => 'csv'
293 get :index, :project_id => 1, :format => 'csv'
294 assert_response :success
294 assert_response :success
295 assert_not_nil assigns(:issues)
295 assert_not_nil assigns(:issues)
296 assert_equal 'text/csv', @response.content_type
296 assert_equal 'text/csv', @response.content_type
297 end
297 end
298
298
299 def test_index_csv_big_5
299 def test_index_csv_big_5
300 with_settings :default_language => "zh-TW" do
300 with_settings :default_language => "zh-TW" do
301 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
301 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
302 str_big5 = "\xa4@\xa4\xeb"
302 str_big5 = "\xa4@\xa4\xeb"
303 if str_utf8.respond_to?(:force_encoding)
303 if str_utf8.respond_to?(:force_encoding)
304 str_utf8.force_encoding('UTF-8')
304 str_utf8.force_encoding('UTF-8')
305 str_big5.force_encoding('Big5')
305 str_big5.force_encoding('Big5')
306 end
306 end
307 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
307 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
308 :status_id => 1, :priority => IssuePriority.all.first,
308 :status_id => 1, :priority => IssuePriority.all.first,
309 :subject => str_utf8)
309 :subject => str_utf8)
310 assert issue.save
310 assert issue.save
311
311
312 get :index, :project_id => 1,
312 get :index, :project_id => 1,
313 :f => ['subject'],
313 :f => ['subject'],
314 :op => '=', :values => [str_utf8],
314 :op => '=', :values => [str_utf8],
315 :format => 'csv'
315 :format => 'csv'
316 assert_equal 'text/csv', @response.content_type
316 assert_equal 'text/csv', @response.content_type
317 lines = @response.body.chomp.split("\n")
317 lines = @response.body.chomp.split("\n")
318 s1 = "\xaa\xac\xbaA"
318 s1 = "\xaa\xac\xbaA"
319 if str_utf8.respond_to?(:force_encoding)
319 if str_utf8.respond_to?(:force_encoding)
320 s1.force_encoding('Big5')
320 s1.force_encoding('Big5')
321 end
321 end
322 assert lines[0].include?(s1)
322 assert lines[0].include?(s1)
323 assert lines[1].include?(str_big5)
323 assert lines[1].include?(str_big5)
324 end
324 end
325 end
325 end
326
326
327 def test_index_csv_cannot_convert_should_be_replaced_big_5
327 def test_index_csv_cannot_convert_should_be_replaced_big_5
328 with_settings :default_language => "zh-TW" do
328 with_settings :default_language => "zh-TW" do
329 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
329 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
330 if str_utf8.respond_to?(:force_encoding)
330 if str_utf8.respond_to?(:force_encoding)
331 str_utf8.force_encoding('UTF-8')
331 str_utf8.force_encoding('UTF-8')
332 end
332 end
333 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
333 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
334 :status_id => 1, :priority => IssuePriority.all.first,
334 :status_id => 1, :priority => IssuePriority.all.first,
335 :subject => str_utf8)
335 :subject => str_utf8)
336 assert issue.save
336 assert issue.save
337
337
338 get :index, :project_id => 1,
338 get :index, :project_id => 1,
339 :f => ['subject'],
339 :f => ['subject'],
340 :op => '=', :values => [str_utf8],
340 :op => '=', :values => [str_utf8],
341 :format => 'csv'
341 :format => 'csv'
342 assert_equal 'text/csv', @response.content_type
342 assert_equal 'text/csv', @response.content_type
343 lines = @response.body.chomp.split("\n")
343 lines = @response.body.chomp.split("\n")
344 s1 = "\xaa\xac\xbaA"
344 s1 = "\xaa\xac\xbaA"
345 if str_utf8.respond_to?(:force_encoding)
345 if str_utf8.respond_to?(:force_encoding)
346 s1.force_encoding('Big5')
346 s1.force_encoding('Big5')
347 end
347 end
348 assert lines[0].include?(s1)
348 assert lines[0].include?(s1)
349 s2 = lines[1].split(",")[5]
349 s2 = lines[1].split(",")[5]
350 if s1.respond_to?(:force_encoding)
350 if s1.respond_to?(:force_encoding)
351 s3 = "\xa5H?"
351 s3 = "\xa5H?"
352 s3.force_encoding('Big5')
352 s3.force_encoding('Big5')
353 assert_equal s3, s2
353 assert_equal s3, s2
354 elsif RUBY_PLATFORM == 'java'
354 elsif RUBY_PLATFORM == 'java'
355 assert_equal "??", s2
355 assert_equal "??", s2
356 else
356 else
357 assert_equal "\xa5H???", s2
357 assert_equal "\xa5H???", s2
358 end
358 end
359 end
359 end
360 end
360 end
361
361
362 def test_index_pdf
362 def test_index_pdf
363 get :index, :format => 'pdf'
363 get :index, :format => 'pdf'
364 assert_response :success
364 assert_response :success
365 assert_not_nil assigns(:issues)
365 assert_not_nil assigns(:issues)
366 assert_equal 'application/pdf', @response.content_type
366 assert_equal 'application/pdf', @response.content_type
367
367
368 get :index, :project_id => 1, :format => 'pdf'
368 get :index, :project_id => 1, :format => 'pdf'
369 assert_response :success
369 assert_response :success
370 assert_not_nil assigns(:issues)
370 assert_not_nil assigns(:issues)
371 assert_equal 'application/pdf', @response.content_type
371 assert_equal 'application/pdf', @response.content_type
372
372
373 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
373 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
374 assert_response :success
374 assert_response :success
375 assert_not_nil assigns(:issues)
375 assert_not_nil assigns(:issues)
376 assert_equal 'application/pdf', @response.content_type
376 assert_equal 'application/pdf', @response.content_type
377 end
377 end
378
378
379 def test_index_pdf_with_query_grouped_by_list_custom_field
379 def test_index_pdf_with_query_grouped_by_list_custom_field
380 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
380 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
381 assert_response :success
381 assert_response :success
382 assert_not_nil assigns(:issues)
382 assert_not_nil assigns(:issues)
383 assert_not_nil assigns(:issue_count_by_group)
383 assert_not_nil assigns(:issue_count_by_group)
384 assert_equal 'application/pdf', @response.content_type
384 assert_equal 'application/pdf', @response.content_type
385 end
385 end
386
386
387 def test_index_sort
387 def test_index_sort
388 get :index, :sort => 'tracker,id:desc'
388 get :index, :sort => 'tracker,id:desc'
389 assert_response :success
389 assert_response :success
390
390
391 sort_params = @request.session['issues_index_sort']
391 sort_params = @request.session['issues_index_sort']
392 assert sort_params.is_a?(String)
392 assert sort_params.is_a?(String)
393 assert_equal 'tracker,id:desc', sort_params
393 assert_equal 'tracker,id:desc', sort_params
394
394
395 issues = assigns(:issues)
395 issues = assigns(:issues)
396 assert_not_nil issues
396 assert_not_nil issues
397 assert !issues.empty?
397 assert !issues.empty?
398 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
398 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
399 end
399 end
400
400
401 def test_index_with_columns
401 def test_index_with_columns
402 columns = ['tracker', 'subject', 'assigned_to']
402 columns = ['tracker', 'subject', 'assigned_to']
403 get :index, :set_filter => 1, :c => columns
403 get :index, :set_filter => 1, :c => columns
404 assert_response :success
404 assert_response :success
405
405
406 # query should use specified columns
406 # query should use specified columns
407 query = assigns(:query)
407 query = assigns(:query)
408 assert_kind_of Query, query
408 assert_kind_of Query, query
409 assert_equal columns, query.column_names.map(&:to_s)
409 assert_equal columns, query.column_names.map(&:to_s)
410
410
411 # columns should be stored in session
411 # columns should be stored in session
412 assert_kind_of Hash, session[:query]
412 assert_kind_of Hash, session[:query]
413 assert_kind_of Array, session[:query][:column_names]
413 assert_kind_of Array, session[:query][:column_names]
414 assert_equal columns, session[:query][:column_names].map(&:to_s)
414 assert_equal columns, session[:query][:column_names].map(&:to_s)
415
415
416 # ensure only these columns are kept in the selected columns list
416 # ensure only these columns are kept in the selected columns list
417 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
417 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
418 :children => { :count => 3 }
418 :children => { :count => 3 }
419 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
419 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
420 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
420 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
421 end
421 end
422
422
423 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
423 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
424 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
424 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
425 get :index, :set_filter => 1
425 get :index, :set_filter => 1
426
426
427 # query should use specified columns
427 # query should use specified columns
428 query = assigns(:query)
428 query = assigns(:query)
429 assert_kind_of Query, query
429 assert_kind_of Query, query
430 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
430 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
431 end
431 end
432
432
433 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
433 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
434 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
434 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
435 columns = ['tracker', 'subject', 'assigned_to']
435 columns = ['tracker', 'subject', 'assigned_to']
436 get :index, :set_filter => 1, :c => columns
436 get :index, :set_filter => 1, :c => columns
437
437
438 # query should use specified columns
438 # query should use specified columns
439 query = assigns(:query)
439 query = assigns(:query)
440 assert_kind_of Query, query
440 assert_kind_of Query, query
441 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
441 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
442 end
442 end
443
443
444 def test_index_with_custom_field_column
444 def test_index_with_custom_field_column
445 columns = %w(tracker subject cf_2)
445 columns = %w(tracker subject cf_2)
446 get :index, :set_filter => 1, :c => columns
446 get :index, :set_filter => 1, :c => columns
447 assert_response :success
447 assert_response :success
448
448
449 # query should use specified columns
449 # query should use specified columns
450 query = assigns(:query)
450 query = assigns(:query)
451 assert_kind_of Query, query
451 assert_kind_of Query, query
452 assert_equal columns, query.column_names.map(&:to_s)
452 assert_equal columns, query.column_names.map(&:to_s)
453
453
454 assert_tag :td,
454 assert_tag :td,
455 :attributes => {:class => 'cf_2 string'},
455 :attributes => {:class => 'cf_2 string'},
456 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
456 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
457 end
457 end
458
458
459 def test_index_send_html_if_query_is_invalid
459 def test_index_send_html_if_query_is_invalid
460 get :index, :f => ['start_date'], :op => {:start_date => '='}
460 get :index, :f => ['start_date'], :op => {:start_date => '='}
461 assert_equal 'text/html', @response.content_type
461 assert_equal 'text/html', @response.content_type
462 assert_template 'index'
462 assert_template 'index'
463 end
463 end
464
464
465 def test_index_send_nothing_if_query_is_invalid
465 def test_index_send_nothing_if_query_is_invalid
466 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
466 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
467 assert_equal 'text/csv', @response.content_type
467 assert_equal 'text/csv', @response.content_type
468 assert @response.body.blank?
468 assert @response.body.blank?
469 end
469 end
470
470
471 def test_show_by_anonymous
471 def test_show_by_anonymous
472 get :show, :id => 1
472 get :show, :id => 1
473 assert_response :success
473 assert_response :success
474 assert_template 'show'
474 assert_template 'show'
475 assert_not_nil assigns(:issue)
475 assert_not_nil assigns(:issue)
476 assert_equal Issue.find(1), assigns(:issue)
476 assert_equal Issue.find(1), assigns(:issue)
477
477
478 # anonymous role is allowed to add a note
478 # anonymous role is allowed to add a note
479 assert_tag :tag => 'form',
479 assert_tag :tag => 'form',
480 :descendant => { :tag => 'fieldset',
480 :descendant => { :tag => 'fieldset',
481 :child => { :tag => 'legend',
481 :child => { :tag => 'legend',
482 :content => /Notes/ } }
482 :content => /Notes/ } }
483 assert_tag :tag => 'title',
484 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
483 end
485 end
484
486
485 def test_show_by_manager
487 def test_show_by_manager
486 @request.session[:user_id] = 2
488 @request.session[:user_id] = 2
487 get :show, :id => 1
489 get :show, :id => 1
488 assert_response :success
490 assert_response :success
489
491
490 assert_tag :tag => 'a',
492 assert_tag :tag => 'a',
491 :content => /Quote/
493 :content => /Quote/
492
494
493 assert_tag :tag => 'form',
495 assert_tag :tag => 'form',
494 :descendant => { :tag => 'fieldset',
496 :descendant => { :tag => 'fieldset',
495 :child => { :tag => 'legend',
497 :child => { :tag => 'legend',
496 :content => /Change properties/ } },
498 :content => /Change properties/ } },
497 :descendant => { :tag => 'fieldset',
499 :descendant => { :tag => 'fieldset',
498 :child => { :tag => 'legend',
500 :child => { :tag => 'legend',
499 :content => /Log time/ } },
501 :content => /Log time/ } },
500 :descendant => { :tag => 'fieldset',
502 :descendant => { :tag => 'fieldset',
501 :child => { :tag => 'legend',
503 :child => { :tag => 'legend',
502 :content => /Notes/ } }
504 :content => /Notes/ } }
503 end
505 end
504
506
505 def test_update_form_should_not_display_inactive_enumerations
507 def test_update_form_should_not_display_inactive_enumerations
506 @request.session[:user_id] = 2
508 @request.session[:user_id] = 2
507 get :show, :id => 1
509 get :show, :id => 1
508 assert_response :success
510 assert_response :success
509
511
510 assert ! IssuePriority.find(15).active?
512 assert ! IssuePriority.find(15).active?
511 assert_no_tag :option, :attributes => {:value => '15'},
513 assert_no_tag :option, :attributes => {:value => '15'},
512 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
514 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
513 end
515 end
514
516
515 def test_update_form_should_allow_attachment_upload
517 def test_update_form_should_allow_attachment_upload
516 @request.session[:user_id] = 2
518 @request.session[:user_id] = 2
517 get :show, :id => 1
519 get :show, :id => 1
518
520
519 assert_tag :tag => 'form',
521 assert_tag :tag => 'form',
520 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
522 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
521 :descendant => {
523 :descendant => {
522 :tag => 'input',
524 :tag => 'input',
523 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
525 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
524 }
526 }
525 end
527 end
526
528
527 def test_show_should_deny_anonymous_access_without_permission
529 def test_show_should_deny_anonymous_access_without_permission
528 Role.anonymous.remove_permission!(:view_issues)
530 Role.anonymous.remove_permission!(:view_issues)
529 get :show, :id => 1
531 get :show, :id => 1
530 assert_response :redirect
532 assert_response :redirect
531 end
533 end
532
534
533 def test_show_should_deny_anonymous_access_to_private_issue
535 def test_show_should_deny_anonymous_access_to_private_issue
534 Issue.update_all(["is_private = ?", true], "id = 1")
536 Issue.update_all(["is_private = ?", true], "id = 1")
535 get :show, :id => 1
537 get :show, :id => 1
536 assert_response :redirect
538 assert_response :redirect
537 end
539 end
538
540
539 def test_show_should_deny_non_member_access_without_permission
541 def test_show_should_deny_non_member_access_without_permission
540 Role.non_member.remove_permission!(:view_issues)
542 Role.non_member.remove_permission!(:view_issues)
541 @request.session[:user_id] = 9
543 @request.session[:user_id] = 9
542 get :show, :id => 1
544 get :show, :id => 1
543 assert_response 403
545 assert_response 403
544 end
546 end
545
547
546 def test_show_should_deny_non_member_access_to_private_issue
548 def test_show_should_deny_non_member_access_to_private_issue
547 Issue.update_all(["is_private = ?", true], "id = 1")
549 Issue.update_all(["is_private = ?", true], "id = 1")
548 @request.session[:user_id] = 9
550 @request.session[:user_id] = 9
549 get :show, :id => 1
551 get :show, :id => 1
550 assert_response 403
552 assert_response 403
551 end
553 end
552
554
553 def test_show_should_deny_member_access_without_permission
555 def test_show_should_deny_member_access_without_permission
554 Role.find(1).remove_permission!(:view_issues)
556 Role.find(1).remove_permission!(:view_issues)
555 @request.session[:user_id] = 2
557 @request.session[:user_id] = 2
556 get :show, :id => 1
558 get :show, :id => 1
557 assert_response 403
559 assert_response 403
558 end
560 end
559
561
560 def test_show_should_deny_member_access_to_private_issue_without_permission
562 def test_show_should_deny_member_access_to_private_issue_without_permission
561 Issue.update_all(["is_private = ?", true], "id = 1")
563 Issue.update_all(["is_private = ?", true], "id = 1")
562 @request.session[:user_id] = 3
564 @request.session[:user_id] = 3
563 get :show, :id => 1
565 get :show, :id => 1
564 assert_response 403
566 assert_response 403
565 end
567 end
566
568
567 def test_show_should_allow_author_access_to_private_issue
569 def test_show_should_allow_author_access_to_private_issue
568 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
570 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
569 @request.session[:user_id] = 3
571 @request.session[:user_id] = 3
570 get :show, :id => 1
572 get :show, :id => 1
571 assert_response :success
573 assert_response :success
572 end
574 end
573
575
574 def test_show_should_allow_assignee_access_to_private_issue
576 def test_show_should_allow_assignee_access_to_private_issue
575 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
577 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
576 @request.session[:user_id] = 3
578 @request.session[:user_id] = 3
577 get :show, :id => 1
579 get :show, :id => 1
578 assert_response :success
580 assert_response :success
579 end
581 end
580
582
581 def test_show_should_allow_member_access_to_private_issue_with_permission
583 def test_show_should_allow_member_access_to_private_issue_with_permission
582 Issue.update_all(["is_private = ?", true], "id = 1")
584 Issue.update_all(["is_private = ?", true], "id = 1")
583 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
585 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
584 @request.session[:user_id] = 3
586 @request.session[:user_id] = 3
585 get :show, :id => 1
587 get :show, :id => 1
586 assert_response :success
588 assert_response :success
587 end
589 end
588
590
589 def test_show_should_not_disclose_relations_to_invisible_issues
591 def test_show_should_not_disclose_relations_to_invisible_issues
590 Setting.cross_project_issue_relations = '1'
592 Setting.cross_project_issue_relations = '1'
591 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
593 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
592 # Relation to a private project issue
594 # Relation to a private project issue
593 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
595 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
594
596
595 get :show, :id => 1
597 get :show, :id => 1
596 assert_response :success
598 assert_response :success
597
599
598 assert_tag :div, :attributes => { :id => 'relations' },
600 assert_tag :div, :attributes => { :id => 'relations' },
599 :descendant => { :tag => 'a', :content => /#2$/ }
601 :descendant => { :tag => 'a', :content => /#2$/ }
600 assert_no_tag :div, :attributes => { :id => 'relations' },
602 assert_no_tag :div, :attributes => { :id => 'relations' },
601 :descendant => { :tag => 'a', :content => /#4$/ }
603 :descendant => { :tag => 'a', :content => /#4$/ }
602 end
604 end
603
605
604 def test_show_atom
606 def test_show_atom
605 get :show, :id => 2, :format => 'atom'
607 get :show, :id => 2, :format => 'atom'
606 assert_response :success
608 assert_response :success
607 assert_template 'journals/index'
609 assert_template 'journals/index'
608 # Inline image
610 # Inline image
609 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
611 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
610 end
612 end
611
613
612 def test_show_export_to_pdf
614 def test_show_export_to_pdf
613 get :show, :id => 3, :format => 'pdf'
615 get :show, :id => 3, :format => 'pdf'
614 assert_response :success
616 assert_response :success
615 assert_equal 'application/pdf', @response.content_type
617 assert_equal 'application/pdf', @response.content_type
616 assert @response.body.starts_with?('%PDF')
618 assert @response.body.starts_with?('%PDF')
617 assert_not_nil assigns(:issue)
619 assert_not_nil assigns(:issue)
618 end
620 end
619
621
620 def test_get_new
622 def test_get_new
621 @request.session[:user_id] = 2
623 @request.session[:user_id] = 2
622 get :new, :project_id => 1, :tracker_id => 1
624 get :new, :project_id => 1, :tracker_id => 1
623 assert_response :success
625 assert_response :success
624 assert_template 'new'
626 assert_template 'new'
625
627
626 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
628 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
627 :value => 'Default string' }
629 :value => 'Default string' }
628
630
629 # Be sure we don't display inactive IssuePriorities
631 # Be sure we don't display inactive IssuePriorities
630 assert ! IssuePriority.find(15).active?
632 assert ! IssuePriority.find(15).active?
631 assert_no_tag :option, :attributes => {:value => '15'},
633 assert_no_tag :option, :attributes => {:value => '15'},
632 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
634 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
633 end
635 end
634
636
635 def test_get_new_without_default_start_date_is_creation_date
637 def test_get_new_without_default_start_date_is_creation_date
636 Setting.default_issue_start_date_to_creation_date = 0
638 Setting.default_issue_start_date_to_creation_date = 0
637
639
638 @request.session[:user_id] = 2
640 @request.session[:user_id] = 2
639 get :new, :project_id => 1, :tracker_id => 1
641 get :new, :project_id => 1, :tracker_id => 1
640 assert_response :success
642 assert_response :success
641 assert_template 'new'
643 assert_template 'new'
642
644
643 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
645 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
644 :value => nil }
646 :value => nil }
645 end
647 end
646
648
647 def test_get_new_with_default_start_date_is_creation_date
649 def test_get_new_with_default_start_date_is_creation_date
648 Setting.default_issue_start_date_to_creation_date = 1
650 Setting.default_issue_start_date_to_creation_date = 1
649
651
650 @request.session[:user_id] = 2
652 @request.session[:user_id] = 2
651 get :new, :project_id => 1, :tracker_id => 1
653 get :new, :project_id => 1, :tracker_id => 1
652 assert_response :success
654 assert_response :success
653 assert_template 'new'
655 assert_template 'new'
654
656
655 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
657 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
656 :value => Date.today.to_s }
658 :value => Date.today.to_s }
657 end
659 end
658
660
659 def test_get_new_form_should_allow_attachment_upload
661 def test_get_new_form_should_allow_attachment_upload
660 @request.session[:user_id] = 2
662 @request.session[:user_id] = 2
661 get :new, :project_id => 1, :tracker_id => 1
663 get :new, :project_id => 1, :tracker_id => 1
662
664
663 assert_tag :tag => 'form',
665 assert_tag :tag => 'form',
664 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
666 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
665 :descendant => {
667 :descendant => {
666 :tag => 'input',
668 :tag => 'input',
667 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
669 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
668 }
670 }
669 end
671 end
670
672
671 def test_get_new_without_tracker_id
673 def test_get_new_without_tracker_id
672 @request.session[:user_id] = 2
674 @request.session[:user_id] = 2
673 get :new, :project_id => 1
675 get :new, :project_id => 1
674 assert_response :success
676 assert_response :success
675 assert_template 'new'
677 assert_template 'new'
676
678
677 issue = assigns(:issue)
679 issue = assigns(:issue)
678 assert_not_nil issue
680 assert_not_nil issue
679 assert_equal Project.find(1).trackers.first, issue.tracker
681 assert_equal Project.find(1).trackers.first, issue.tracker
680 end
682 end
681
683
682 def test_get_new_with_no_default_status_should_display_an_error
684 def test_get_new_with_no_default_status_should_display_an_error
683 @request.session[:user_id] = 2
685 @request.session[:user_id] = 2
684 IssueStatus.delete_all
686 IssueStatus.delete_all
685
687
686 get :new, :project_id => 1
688 get :new, :project_id => 1
687 assert_response 500
689 assert_response 500
688 assert_error_tag :content => /No default issue/
690 assert_error_tag :content => /No default issue/
689 end
691 end
690
692
691 def test_get_new_with_no_tracker_should_display_an_error
693 def test_get_new_with_no_tracker_should_display_an_error
692 @request.session[:user_id] = 2
694 @request.session[:user_id] = 2
693 Tracker.delete_all
695 Tracker.delete_all
694
696
695 get :new, :project_id => 1
697 get :new, :project_id => 1
696 assert_response 500
698 assert_response 500
697 assert_error_tag :content => /No tracker/
699 assert_error_tag :content => /No tracker/
698 end
700 end
699
701
700 def test_update_new_form
702 def test_update_new_form
701 @request.session[:user_id] = 2
703 @request.session[:user_id] = 2
702 xhr :post, :new, :project_id => 1,
704 xhr :post, :new, :project_id => 1,
703 :issue => {:tracker_id => 2,
705 :issue => {:tracker_id => 2,
704 :subject => 'This is the test_new issue',
706 :subject => 'This is the test_new issue',
705 :description => 'This is the description',
707 :description => 'This is the description',
706 :priority_id => 5}
708 :priority_id => 5}
707 assert_response :success
709 assert_response :success
708 assert_template 'attributes'
710 assert_template 'attributes'
709
711
710 issue = assigns(:issue)
712 issue = assigns(:issue)
711 assert_kind_of Issue, issue
713 assert_kind_of Issue, issue
712 assert_equal 1, issue.project_id
714 assert_equal 1, issue.project_id
713 assert_equal 2, issue.tracker_id
715 assert_equal 2, issue.tracker_id
714 assert_equal 'This is the test_new issue', issue.subject
716 assert_equal 'This is the test_new issue', issue.subject
715 end
717 end
716
718
717 def test_post_create
719 def test_post_create
718 @request.session[:user_id] = 2
720 @request.session[:user_id] = 2
719 assert_difference 'Issue.count' do
721 assert_difference 'Issue.count' do
720 post :create, :project_id => 1,
722 post :create, :project_id => 1,
721 :issue => {:tracker_id => 3,
723 :issue => {:tracker_id => 3,
722 :status_id => 2,
724 :status_id => 2,
723 :subject => 'This is the test_new issue',
725 :subject => 'This is the test_new issue',
724 :description => 'This is the description',
726 :description => 'This is the description',
725 :priority_id => 5,
727 :priority_id => 5,
726 :start_date => '2010-11-07',
728 :start_date => '2010-11-07',
727 :estimated_hours => '',
729 :estimated_hours => '',
728 :custom_field_values => {'2' => 'Value for field 2'}}
730 :custom_field_values => {'2' => 'Value for field 2'}}
729 end
731 end
730 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
732 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
731
733
732 issue = Issue.find_by_subject('This is the test_new issue')
734 issue = Issue.find_by_subject('This is the test_new issue')
733 assert_not_nil issue
735 assert_not_nil issue
734 assert_equal 2, issue.author_id
736 assert_equal 2, issue.author_id
735 assert_equal 3, issue.tracker_id
737 assert_equal 3, issue.tracker_id
736 assert_equal 2, issue.status_id
738 assert_equal 2, issue.status_id
737 assert_equal Date.parse('2010-11-07'), issue.start_date
739 assert_equal Date.parse('2010-11-07'), issue.start_date
738 assert_nil issue.estimated_hours
740 assert_nil issue.estimated_hours
739 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
741 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
740 assert_not_nil v
742 assert_not_nil v
741 assert_equal 'Value for field 2', v.value
743 assert_equal 'Value for field 2', v.value
742 end
744 end
743
745
744 def test_post_new_with_group_assignment
746 def test_post_new_with_group_assignment
745 group = Group.find(11)
747 group = Group.find(11)
746 project = Project.find(1)
748 project = Project.find(1)
747 project.members << Member.new(:principal => group, :roles => [Role.first])
749 project.members << Member.new(:principal => group, :roles => [Role.first])
748
750
749 with_settings :issue_group_assignment => '1' do
751 with_settings :issue_group_assignment => '1' do
750 @request.session[:user_id] = 2
752 @request.session[:user_id] = 2
751 assert_difference 'Issue.count' do
753 assert_difference 'Issue.count' do
752 post :create, :project_id => project.id,
754 post :create, :project_id => project.id,
753 :issue => {:tracker_id => 3,
755 :issue => {:tracker_id => 3,
754 :status_id => 1,
756 :status_id => 1,
755 :subject => 'This is the test_new_with_group_assignment issue',
757 :subject => 'This is the test_new_with_group_assignment issue',
756 :assigned_to_id => group.id}
758 :assigned_to_id => group.id}
757 end
759 end
758 end
760 end
759 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
761 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
760
762
761 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
763 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
762 assert_not_nil issue
764 assert_not_nil issue
763 assert_equal group, issue.assigned_to
765 assert_equal group, issue.assigned_to
764 end
766 end
765
767
766 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
768 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
767 Setting.default_issue_start_date_to_creation_date = 0
769 Setting.default_issue_start_date_to_creation_date = 0
768
770
769 @request.session[:user_id] = 2
771 @request.session[:user_id] = 2
770 assert_difference 'Issue.count' do
772 assert_difference 'Issue.count' do
771 post :create, :project_id => 1,
773 post :create, :project_id => 1,
772 :issue => {:tracker_id => 3,
774 :issue => {:tracker_id => 3,
773 :status_id => 2,
775 :status_id => 2,
774 :subject => 'This is the test_new issue',
776 :subject => 'This is the test_new issue',
775 :description => 'This is the description',
777 :description => 'This is the description',
776 :priority_id => 5,
778 :priority_id => 5,
777 :estimated_hours => '',
779 :estimated_hours => '',
778 :custom_field_values => {'2' => 'Value for field 2'}}
780 :custom_field_values => {'2' => 'Value for field 2'}}
779 end
781 end
780 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
782 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
781
783
782 issue = Issue.find_by_subject('This is the test_new issue')
784 issue = Issue.find_by_subject('This is the test_new issue')
783 assert_not_nil issue
785 assert_not_nil issue
784 assert_nil issue.start_date
786 assert_nil issue.start_date
785 end
787 end
786
788
787 def test_post_create_without_start_date_and_default_start_date_is_creation_date
789 def test_post_create_without_start_date_and_default_start_date_is_creation_date
788 Setting.default_issue_start_date_to_creation_date = 1
790 Setting.default_issue_start_date_to_creation_date = 1
789
791
790 @request.session[:user_id] = 2
792 @request.session[:user_id] = 2
791 assert_difference 'Issue.count' do
793 assert_difference 'Issue.count' do
792 post :create, :project_id => 1,
794 post :create, :project_id => 1,
793 :issue => {:tracker_id => 3,
795 :issue => {:tracker_id => 3,
794 :status_id => 2,
796 :status_id => 2,
795 :subject => 'This is the test_new issue',
797 :subject => 'This is the test_new issue',
796 :description => 'This is the description',
798 :description => 'This is the description',
797 :priority_id => 5,
799 :priority_id => 5,
798 :estimated_hours => '',
800 :estimated_hours => '',
799 :custom_field_values => {'2' => 'Value for field 2'}}
801 :custom_field_values => {'2' => 'Value for field 2'}}
800 end
802 end
801 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
803 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
802
804
803 issue = Issue.find_by_subject('This is the test_new issue')
805 issue = Issue.find_by_subject('This is the test_new issue')
804 assert_not_nil issue
806 assert_not_nil issue
805 assert_equal Date.today, issue.start_date
807 assert_equal Date.today, issue.start_date
806 end
808 end
807
809
808 def test_post_create_and_continue
810 def test_post_create_and_continue
809 @request.session[:user_id] = 2
811 @request.session[:user_id] = 2
810 assert_difference 'Issue.count' do
812 assert_difference 'Issue.count' do
811 post :create, :project_id => 1,
813 post :create, :project_id => 1,
812 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
814 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
813 :continue => ''
815 :continue => ''
814 end
816 end
815
817
816 issue = Issue.first(:order => 'id DESC')
818 issue = Issue.first(:order => 'id DESC')
817 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
819 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
818 assert_not_nil flash[:notice], "flash was not set"
820 assert_not_nil flash[:notice], "flash was not set"
819 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
821 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
820 end
822 end
821
823
822 def test_post_create_without_custom_fields_param
824 def test_post_create_without_custom_fields_param
823 @request.session[:user_id] = 2
825 @request.session[:user_id] = 2
824 assert_difference 'Issue.count' do
826 assert_difference 'Issue.count' do
825 post :create, :project_id => 1,
827 post :create, :project_id => 1,
826 :issue => {:tracker_id => 1,
828 :issue => {:tracker_id => 1,
827 :subject => 'This is the test_new issue',
829 :subject => 'This is the test_new issue',
828 :description => 'This is the description',
830 :description => 'This is the description',
829 :priority_id => 5}
831 :priority_id => 5}
830 end
832 end
831 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
833 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
832 end
834 end
833
835
834 def test_post_create_with_required_custom_field_and_without_custom_fields_param
836 def test_post_create_with_required_custom_field_and_without_custom_fields_param
835 field = IssueCustomField.find_by_name('Database')
837 field = IssueCustomField.find_by_name('Database')
836 field.update_attribute(:is_required, true)
838 field.update_attribute(:is_required, true)
837
839
838 @request.session[:user_id] = 2
840 @request.session[:user_id] = 2
839 post :create, :project_id => 1,
841 post :create, :project_id => 1,
840 :issue => {:tracker_id => 1,
842 :issue => {:tracker_id => 1,
841 :subject => 'This is the test_new issue',
843 :subject => 'This is the test_new issue',
842 :description => 'This is the description',
844 :description => 'This is the description',
843 :priority_id => 5}
845 :priority_id => 5}
844 assert_response :success
846 assert_response :success
845 assert_template 'new'
847 assert_template 'new'
846 issue = assigns(:issue)
848 issue = assigns(:issue)
847 assert_not_nil issue
849 assert_not_nil issue
848 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
850 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
849 end
851 end
850
852
851 def test_post_create_with_watchers
853 def test_post_create_with_watchers
852 @request.session[:user_id] = 2
854 @request.session[:user_id] = 2
853 ActionMailer::Base.deliveries.clear
855 ActionMailer::Base.deliveries.clear
854
856
855 assert_difference 'Watcher.count', 2 do
857 assert_difference 'Watcher.count', 2 do
856 post :create, :project_id => 1,
858 post :create, :project_id => 1,
857 :issue => {:tracker_id => 1,
859 :issue => {:tracker_id => 1,
858 :subject => 'This is a new issue with watchers',
860 :subject => 'This is a new issue with watchers',
859 :description => 'This is the description',
861 :description => 'This is the description',
860 :priority_id => 5,
862 :priority_id => 5,
861 :watcher_user_ids => ['2', '3']}
863 :watcher_user_ids => ['2', '3']}
862 end
864 end
863 issue = Issue.find_by_subject('This is a new issue with watchers')
865 issue = Issue.find_by_subject('This is a new issue with watchers')
864 assert_not_nil issue
866 assert_not_nil issue
865 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
867 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
866
868
867 # Watchers added
869 # Watchers added
868 assert_equal [2, 3], issue.watcher_user_ids.sort
870 assert_equal [2, 3], issue.watcher_user_ids.sort
869 assert issue.watched_by?(User.find(3))
871 assert issue.watched_by?(User.find(3))
870 # Watchers notified
872 # Watchers notified
871 mail = ActionMailer::Base.deliveries.last
873 mail = ActionMailer::Base.deliveries.last
872 assert_kind_of TMail::Mail, mail
874 assert_kind_of TMail::Mail, mail
873 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
875 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
874 end
876 end
875
877
876 def test_post_create_subissue
878 def test_post_create_subissue
877 @request.session[:user_id] = 2
879 @request.session[:user_id] = 2
878
880
879 assert_difference 'Issue.count' do
881 assert_difference 'Issue.count' do
880 post :create, :project_id => 1,
882 post :create, :project_id => 1,
881 :issue => {:tracker_id => 1,
883 :issue => {:tracker_id => 1,
882 :subject => 'This is a child issue',
884 :subject => 'This is a child issue',
883 :parent_issue_id => 2}
885 :parent_issue_id => 2}
884 end
886 end
885 issue = Issue.find_by_subject('This is a child issue')
887 issue = Issue.find_by_subject('This is a child issue')
886 assert_not_nil issue
888 assert_not_nil issue
887 assert_equal Issue.find(2), issue.parent
889 assert_equal Issue.find(2), issue.parent
888 end
890 end
889
891
890 def test_post_create_subissue_with_non_numeric_parent_id
892 def test_post_create_subissue_with_non_numeric_parent_id
891 @request.session[:user_id] = 2
893 @request.session[:user_id] = 2
892
894
893 assert_difference 'Issue.count' do
895 assert_difference 'Issue.count' do
894 post :create, :project_id => 1,
896 post :create, :project_id => 1,
895 :issue => {:tracker_id => 1,
897 :issue => {:tracker_id => 1,
896 :subject => 'This is a child issue',
898 :subject => 'This is a child issue',
897 :parent_issue_id => 'ABC'}
899 :parent_issue_id => 'ABC'}
898 end
900 end
899 issue = Issue.find_by_subject('This is a child issue')
901 issue = Issue.find_by_subject('This is a child issue')
900 assert_not_nil issue
902 assert_not_nil issue
901 assert_nil issue.parent
903 assert_nil issue.parent
902 end
904 end
903
905
904 def test_post_create_private
906 def test_post_create_private
905 @request.session[:user_id] = 2
907 @request.session[:user_id] = 2
906
908
907 assert_difference 'Issue.count' do
909 assert_difference 'Issue.count' do
908 post :create, :project_id => 1,
910 post :create, :project_id => 1,
909 :issue => {:tracker_id => 1,
911 :issue => {:tracker_id => 1,
910 :subject => 'This is a private issue',
912 :subject => 'This is a private issue',
911 :is_private => '1'}
913 :is_private => '1'}
912 end
914 end
913 issue = Issue.first(:order => 'id DESC')
915 issue = Issue.first(:order => 'id DESC')
914 assert issue.is_private?
916 assert issue.is_private?
915 end
917 end
916
918
917 def test_post_create_private_with_set_own_issues_private_permission
919 def test_post_create_private_with_set_own_issues_private_permission
918 role = Role.find(1)
920 role = Role.find(1)
919 role.remove_permission! :set_issues_private
921 role.remove_permission! :set_issues_private
920 role.add_permission! :set_own_issues_private
922 role.add_permission! :set_own_issues_private
921
923
922 @request.session[:user_id] = 2
924 @request.session[:user_id] = 2
923
925
924 assert_difference 'Issue.count' do
926 assert_difference 'Issue.count' do
925 post :create, :project_id => 1,
927 post :create, :project_id => 1,
926 :issue => {:tracker_id => 1,
928 :issue => {:tracker_id => 1,
927 :subject => 'This is a private issue',
929 :subject => 'This is a private issue',
928 :is_private => '1'}
930 :is_private => '1'}
929 end
931 end
930 issue = Issue.first(:order => 'id DESC')
932 issue = Issue.first(:order => 'id DESC')
931 assert issue.is_private?
933 assert issue.is_private?
932 end
934 end
933
935
934 def test_post_create_should_send_a_notification
936 def test_post_create_should_send_a_notification
935 ActionMailer::Base.deliveries.clear
937 ActionMailer::Base.deliveries.clear
936 @request.session[:user_id] = 2
938 @request.session[:user_id] = 2
937 assert_difference 'Issue.count' do
939 assert_difference 'Issue.count' do
938 post :create, :project_id => 1,
940 post :create, :project_id => 1,
939 :issue => {:tracker_id => 3,
941 :issue => {:tracker_id => 3,
940 :subject => 'This is the test_new issue',
942 :subject => 'This is the test_new issue',
941 :description => 'This is the description',
943 :description => 'This is the description',
942 :priority_id => 5,
944 :priority_id => 5,
943 :estimated_hours => '',
945 :estimated_hours => '',
944 :custom_field_values => {'2' => 'Value for field 2'}}
946 :custom_field_values => {'2' => 'Value for field 2'}}
945 end
947 end
946 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
948 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
947
949
948 assert_equal 1, ActionMailer::Base.deliveries.size
950 assert_equal 1, ActionMailer::Base.deliveries.size
949 end
951 end
950
952
951 def test_post_create_should_preserve_fields_values_on_validation_failure
953 def test_post_create_should_preserve_fields_values_on_validation_failure
952 @request.session[:user_id] = 2
954 @request.session[:user_id] = 2
953 post :create, :project_id => 1,
955 post :create, :project_id => 1,
954 :issue => {:tracker_id => 1,
956 :issue => {:tracker_id => 1,
955 # empty subject
957 # empty subject
956 :subject => '',
958 :subject => '',
957 :description => 'This is a description',
959 :description => 'This is a description',
958 :priority_id => 6,
960 :priority_id => 6,
959 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
961 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
960 assert_response :success
962 assert_response :success
961 assert_template 'new'
963 assert_template 'new'
962
964
963 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
965 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
964 :content => 'This is a description'
966 :content => 'This is a description'
965 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
967 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
966 :child => { :tag => 'option', :attributes => { :selected => 'selected',
968 :child => { :tag => 'option', :attributes => { :selected => 'selected',
967 :value => '6' },
969 :value => '6' },
968 :content => 'High' }
970 :content => 'High' }
969 # Custom fields
971 # Custom fields
970 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
972 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
971 :child => { :tag => 'option', :attributes => { :selected => 'selected',
973 :child => { :tag => 'option', :attributes => { :selected => 'selected',
972 :value => 'Oracle' },
974 :value => 'Oracle' },
973 :content => 'Oracle' }
975 :content => 'Oracle' }
974 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
976 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
975 :value => 'Value for field 2'}
977 :value => 'Value for field 2'}
976 end
978 end
977
979
978 def test_post_create_should_ignore_non_safe_attributes
980 def test_post_create_should_ignore_non_safe_attributes
979 @request.session[:user_id] = 2
981 @request.session[:user_id] = 2
980 assert_nothing_raised do
982 assert_nothing_raised do
981 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
983 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
982 end
984 end
983 end
985 end
984
986
985 def test_post_create_with_attachment
987 def test_post_create_with_attachment
986 set_tmp_attachments_directory
988 set_tmp_attachments_directory
987 @request.session[:user_id] = 2
989 @request.session[:user_id] = 2
988
990
989 assert_difference 'Issue.count' do
991 assert_difference 'Issue.count' do
990 assert_difference 'Attachment.count' do
992 assert_difference 'Attachment.count' do
991 post :create, :project_id => 1,
993 post :create, :project_id => 1,
992 :issue => { :tracker_id => '1', :subject => 'With attachment' },
994 :issue => { :tracker_id => '1', :subject => 'With attachment' },
993 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
995 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
994 end
996 end
995 end
997 end
996
998
997 issue = Issue.first(:order => 'id DESC')
999 issue = Issue.first(:order => 'id DESC')
998 attachment = Attachment.first(:order => 'id DESC')
1000 attachment = Attachment.first(:order => 'id DESC')
999
1001
1000 assert_equal issue, attachment.container
1002 assert_equal issue, attachment.container
1001 assert_equal 2, attachment.author_id
1003 assert_equal 2, attachment.author_id
1002 assert_equal 'testfile.txt', attachment.filename
1004 assert_equal 'testfile.txt', attachment.filename
1003 assert_equal 'text/plain', attachment.content_type
1005 assert_equal 'text/plain', attachment.content_type
1004 assert_equal 'test file', attachment.description
1006 assert_equal 'test file', attachment.description
1005 assert_equal 59, attachment.filesize
1007 assert_equal 59, attachment.filesize
1006 assert File.exists?(attachment.diskfile)
1008 assert File.exists?(attachment.diskfile)
1007 assert_equal 59, File.size(attachment.diskfile)
1009 assert_equal 59, File.size(attachment.diskfile)
1008 end
1010 end
1009
1011
1010 context "without workflow privilege" do
1012 context "without workflow privilege" do
1011 setup do
1013 setup do
1012 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1014 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1013 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1015 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1014 end
1016 end
1015
1017
1016 context "#new" do
1018 context "#new" do
1017 should "propose default status only" do
1019 should "propose default status only" do
1018 get :new, :project_id => 1
1020 get :new, :project_id => 1
1019 assert_response :success
1021 assert_response :success
1020 assert_template 'new'
1022 assert_template 'new'
1021 assert_tag :tag => 'select',
1023 assert_tag :tag => 'select',
1022 :attributes => {:name => 'issue[status_id]'},
1024 :attributes => {:name => 'issue[status_id]'},
1023 :children => {:count => 1},
1025 :children => {:count => 1},
1024 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1026 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1025 end
1027 end
1026
1028
1027 should "accept default status" do
1029 should "accept default status" do
1028 assert_difference 'Issue.count' do
1030 assert_difference 'Issue.count' do
1029 post :create, :project_id => 1,
1031 post :create, :project_id => 1,
1030 :issue => {:tracker_id => 1,
1032 :issue => {:tracker_id => 1,
1031 :subject => 'This is an issue',
1033 :subject => 'This is an issue',
1032 :status_id => 1}
1034 :status_id => 1}
1033 end
1035 end
1034 issue = Issue.last(:order => 'id')
1036 issue = Issue.last(:order => 'id')
1035 assert_equal IssueStatus.default, issue.status
1037 assert_equal IssueStatus.default, issue.status
1036 end
1038 end
1037
1039
1038 should "ignore unauthorized status" do
1040 should "ignore unauthorized status" do
1039 assert_difference 'Issue.count' do
1041 assert_difference 'Issue.count' do
1040 post :create, :project_id => 1,
1042 post :create, :project_id => 1,
1041 :issue => {:tracker_id => 1,
1043 :issue => {:tracker_id => 1,
1042 :subject => 'This is an issue',
1044 :subject => 'This is an issue',
1043 :status_id => 3}
1045 :status_id => 3}
1044 end
1046 end
1045 issue = Issue.last(:order => 'id')
1047 issue = Issue.last(:order => 'id')
1046 assert_equal IssueStatus.default, issue.status
1048 assert_equal IssueStatus.default, issue.status
1047 end
1049 end
1048 end
1050 end
1049
1051
1050 context "#update" do
1052 context "#update" do
1051 should "ignore status change" do
1053 should "ignore status change" do
1052 assert_difference 'Journal.count' do
1054 assert_difference 'Journal.count' do
1053 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1055 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1054 end
1056 end
1055 assert_equal 1, Issue.find(1).status_id
1057 assert_equal 1, Issue.find(1).status_id
1056 end
1058 end
1057
1059
1058 should "ignore attributes changes" do
1060 should "ignore attributes changes" do
1059 assert_difference 'Journal.count' do
1061 assert_difference 'Journal.count' do
1060 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1062 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1061 end
1063 end
1062 issue = Issue.find(1)
1064 issue = Issue.find(1)
1063 assert_equal "Can't print recipes", issue.subject
1065 assert_equal "Can't print recipes", issue.subject
1064 assert_nil issue.assigned_to
1066 assert_nil issue.assigned_to
1065 end
1067 end
1066 end
1068 end
1067 end
1069 end
1068
1070
1069 context "with workflow privilege" do
1071 context "with workflow privilege" do
1070 setup do
1072 setup do
1071 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1073 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1072 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1074 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1073 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1075 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1074 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1076 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1075 end
1077 end
1076
1078
1077 context "#update" do
1079 context "#update" do
1078 should "accept authorized status" do
1080 should "accept authorized status" do
1079 assert_difference 'Journal.count' do
1081 assert_difference 'Journal.count' do
1080 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1082 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1081 end
1083 end
1082 assert_equal 3, Issue.find(1).status_id
1084 assert_equal 3, Issue.find(1).status_id
1083 end
1085 end
1084
1086
1085 should "ignore unauthorized status" do
1087 should "ignore unauthorized status" do
1086 assert_difference 'Journal.count' do
1088 assert_difference 'Journal.count' do
1087 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1089 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1088 end
1090 end
1089 assert_equal 1, Issue.find(1).status_id
1091 assert_equal 1, Issue.find(1).status_id
1090 end
1092 end
1091
1093
1092 should "accept authorized attributes changes" do
1094 should "accept authorized attributes changes" do
1093 assert_difference 'Journal.count' do
1095 assert_difference 'Journal.count' do
1094 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1096 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1095 end
1097 end
1096 issue = Issue.find(1)
1098 issue = Issue.find(1)
1097 assert_equal 2, issue.assigned_to_id
1099 assert_equal 2, issue.assigned_to_id
1098 end
1100 end
1099
1101
1100 should "ignore unauthorized attributes changes" do
1102 should "ignore unauthorized attributes changes" do
1101 assert_difference 'Journal.count' do
1103 assert_difference 'Journal.count' do
1102 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1104 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1103 end
1105 end
1104 issue = Issue.find(1)
1106 issue = Issue.find(1)
1105 assert_equal "Can't print recipes", issue.subject
1107 assert_equal "Can't print recipes", issue.subject
1106 end
1108 end
1107 end
1109 end
1108
1110
1109 context "and :edit_issues permission" do
1111 context "and :edit_issues permission" do
1110 setup do
1112 setup do
1111 Role.anonymous.add_permission! :add_issues, :edit_issues
1113 Role.anonymous.add_permission! :add_issues, :edit_issues
1112 end
1114 end
1113
1115
1114 should "accept authorized status" do
1116 should "accept authorized status" do
1115 assert_difference 'Journal.count' do
1117 assert_difference 'Journal.count' do
1116 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1118 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1117 end
1119 end
1118 assert_equal 3, Issue.find(1).status_id
1120 assert_equal 3, Issue.find(1).status_id
1119 end
1121 end
1120
1122
1121 should "ignore unauthorized status" do
1123 should "ignore unauthorized status" do
1122 assert_difference 'Journal.count' do
1124 assert_difference 'Journal.count' do
1123 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1125 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1124 end
1126 end
1125 assert_equal 1, Issue.find(1).status_id
1127 assert_equal 1, Issue.find(1).status_id
1126 end
1128 end
1127
1129
1128 should "accept authorized attributes changes" do
1130 should "accept authorized attributes changes" do
1129 assert_difference 'Journal.count' do
1131 assert_difference 'Journal.count' do
1130 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1132 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1131 end
1133 end
1132 issue = Issue.find(1)
1134 issue = Issue.find(1)
1133 assert_equal "changed", issue.subject
1135 assert_equal "changed", issue.subject
1134 assert_equal 2, issue.assigned_to_id
1136 assert_equal 2, issue.assigned_to_id
1135 end
1137 end
1136 end
1138 end
1137 end
1139 end
1138
1140
1139 def test_copy_issue
1141 def test_copy_issue
1140 @request.session[:user_id] = 2
1142 @request.session[:user_id] = 2
1141 get :new, :project_id => 1, :copy_from => 1
1143 get :new, :project_id => 1, :copy_from => 1
1142 assert_template 'new'
1144 assert_template 'new'
1143 assert_not_nil assigns(:issue)
1145 assert_not_nil assigns(:issue)
1144 orig = Issue.find(1)
1146 orig = Issue.find(1)
1145 assert_equal orig.subject, assigns(:issue).subject
1147 assert_equal orig.subject, assigns(:issue).subject
1146 end
1148 end
1147
1149
1148 def test_get_edit
1150 def test_get_edit
1149 @request.session[:user_id] = 2
1151 @request.session[:user_id] = 2
1150 get :edit, :id => 1
1152 get :edit, :id => 1
1151 assert_response :success
1153 assert_response :success
1152 assert_template 'edit'
1154 assert_template 'edit'
1153 assert_not_nil assigns(:issue)
1155 assert_not_nil assigns(:issue)
1154 assert_equal Issue.find(1), assigns(:issue)
1156 assert_equal Issue.find(1), assigns(:issue)
1155
1157
1156 # Be sure we don't display inactive IssuePriorities
1158 # Be sure we don't display inactive IssuePriorities
1157 assert ! IssuePriority.find(15).active?
1159 assert ! IssuePriority.find(15).active?
1158 assert_no_tag :option, :attributes => {:value => '15'},
1160 assert_no_tag :option, :attributes => {:value => '15'},
1159 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1161 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1160 end
1162 end
1161
1163
1162 def test_get_edit_with_params
1164 def test_get_edit_with_params
1163 @request.session[:user_id] = 2
1165 @request.session[:user_id] = 2
1164 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1166 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1165 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1167 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1166 assert_response :success
1168 assert_response :success
1167 assert_template 'edit'
1169 assert_template 'edit'
1168
1170
1169 issue = assigns(:issue)
1171 issue = assigns(:issue)
1170 assert_not_nil issue
1172 assert_not_nil issue
1171
1173
1172 assert_equal 5, issue.status_id
1174 assert_equal 5, issue.status_id
1173 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1175 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1174 :child => { :tag => 'option',
1176 :child => { :tag => 'option',
1175 :content => 'Closed',
1177 :content => 'Closed',
1176 :attributes => { :selected => 'selected' } }
1178 :attributes => { :selected => 'selected' } }
1177
1179
1178 assert_equal 7, issue.priority_id
1180 assert_equal 7, issue.priority_id
1179 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1181 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1180 :child => { :tag => 'option',
1182 :child => { :tag => 'option',
1181 :content => 'Urgent',
1183 :content => 'Urgent',
1182 :attributes => { :selected => 'selected' } }
1184 :attributes => { :selected => 'selected' } }
1183
1185
1184 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1186 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1185 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1187 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1186 :child => { :tag => 'option',
1188 :child => { :tag => 'option',
1187 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1189 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1188 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1190 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1189 end
1191 end
1190
1192
1191 def test_update_edit_form
1193 def test_update_edit_form
1192 @request.session[:user_id] = 2
1194 @request.session[:user_id] = 2
1193 xhr :post, :new, :project_id => 1,
1195 xhr :post, :new, :project_id => 1,
1194 :id => 1,
1196 :id => 1,
1195 :issue => {:tracker_id => 2,
1197 :issue => {:tracker_id => 2,
1196 :subject => 'This is the test_new issue',
1198 :subject => 'This is the test_new issue',
1197 :description => 'This is the description',
1199 :description => 'This is the description',
1198 :priority_id => 5}
1200 :priority_id => 5}
1199 assert_response :success
1201 assert_response :success
1200 assert_template 'attributes'
1202 assert_template 'attributes'
1201
1203
1202 issue = assigns(:issue)
1204 issue = assigns(:issue)
1203 assert_kind_of Issue, issue
1205 assert_kind_of Issue, issue
1204 assert_equal 1, issue.id
1206 assert_equal 1, issue.id
1205 assert_equal 1, issue.project_id
1207 assert_equal 1, issue.project_id
1206 assert_equal 2, issue.tracker_id
1208 assert_equal 2, issue.tracker_id
1207 assert_equal 'This is the test_new issue', issue.subject
1209 assert_equal 'This is the test_new issue', issue.subject
1208 end
1210 end
1209
1211
1210 def test_update_using_invalid_http_verbs
1212 def test_update_using_invalid_http_verbs
1211 @request.session[:user_id] = 2
1213 @request.session[:user_id] = 2
1212 subject = 'Updated by an invalid http verb'
1214 subject = 'Updated by an invalid http verb'
1213
1215
1214 get :update, :id => 1, :issue => {:subject => subject}
1216 get :update, :id => 1, :issue => {:subject => subject}
1215 assert_not_equal subject, Issue.find(1).subject
1217 assert_not_equal subject, Issue.find(1).subject
1216
1218
1217 post :update, :id => 1, :issue => {:subject => subject}
1219 post :update, :id => 1, :issue => {:subject => subject}
1218 assert_not_equal subject, Issue.find(1).subject
1220 assert_not_equal subject, Issue.find(1).subject
1219
1221
1220 delete :update, :id => 1, :issue => {:subject => subject}
1222 delete :update, :id => 1, :issue => {:subject => subject}
1221 assert_not_equal subject, Issue.find(1).subject
1223 assert_not_equal subject, Issue.find(1).subject
1222 end
1224 end
1223
1225
1224 def test_put_update_without_custom_fields_param
1226 def test_put_update_without_custom_fields_param
1225 @request.session[:user_id] = 2
1227 @request.session[:user_id] = 2
1226 ActionMailer::Base.deliveries.clear
1228 ActionMailer::Base.deliveries.clear
1227
1229
1228 issue = Issue.find(1)
1230 issue = Issue.find(1)
1229 assert_equal '125', issue.custom_value_for(2).value
1231 assert_equal '125', issue.custom_value_for(2).value
1230 old_subject = issue.subject
1232 old_subject = issue.subject
1231 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1233 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1232
1234
1233 assert_difference('Journal.count') do
1235 assert_difference('Journal.count') do
1234 assert_difference('JournalDetail.count', 2) do
1236 assert_difference('JournalDetail.count', 2) do
1235 put :update, :id => 1, :issue => {:subject => new_subject,
1237 put :update, :id => 1, :issue => {:subject => new_subject,
1236 :priority_id => '6',
1238 :priority_id => '6',
1237 :category_id => '1' # no change
1239 :category_id => '1' # no change
1238 }
1240 }
1239 end
1241 end
1240 end
1242 end
1241 assert_redirected_to :action => 'show', :id => '1'
1243 assert_redirected_to :action => 'show', :id => '1'
1242 issue.reload
1244 issue.reload
1243 assert_equal new_subject, issue.subject
1245 assert_equal new_subject, issue.subject
1244 # Make sure custom fields were not cleared
1246 # Make sure custom fields were not cleared
1245 assert_equal '125', issue.custom_value_for(2).value
1247 assert_equal '125', issue.custom_value_for(2).value
1246
1248
1247 mail = ActionMailer::Base.deliveries.last
1249 mail = ActionMailer::Base.deliveries.last
1248 assert_kind_of TMail::Mail, mail
1250 assert_kind_of TMail::Mail, mail
1249 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1251 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1250 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1252 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1251 end
1253 end
1252
1254
1253 def test_put_update_with_custom_field_change
1255 def test_put_update_with_custom_field_change
1254 @request.session[:user_id] = 2
1256 @request.session[:user_id] = 2
1255 issue = Issue.find(1)
1257 issue = Issue.find(1)
1256 assert_equal '125', issue.custom_value_for(2).value
1258 assert_equal '125', issue.custom_value_for(2).value
1257
1259
1258 assert_difference('Journal.count') do
1260 assert_difference('Journal.count') do
1259 assert_difference('JournalDetail.count', 3) do
1261 assert_difference('JournalDetail.count', 3) do
1260 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1262 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1261 :priority_id => '6',
1263 :priority_id => '6',
1262 :category_id => '1', # no change
1264 :category_id => '1', # no change
1263 :custom_field_values => { '2' => 'New custom value' }
1265 :custom_field_values => { '2' => 'New custom value' }
1264 }
1266 }
1265 end
1267 end
1266 end
1268 end
1267 assert_redirected_to :action => 'show', :id => '1'
1269 assert_redirected_to :action => 'show', :id => '1'
1268 issue.reload
1270 issue.reload
1269 assert_equal 'New custom value', issue.custom_value_for(2).value
1271 assert_equal 'New custom value', issue.custom_value_for(2).value
1270
1272
1271 mail = ActionMailer::Base.deliveries.last
1273 mail = ActionMailer::Base.deliveries.last
1272 assert_kind_of TMail::Mail, mail
1274 assert_kind_of TMail::Mail, mail
1273 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1275 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1274 end
1276 end
1275
1277
1276 def test_put_update_with_status_and_assignee_change
1278 def test_put_update_with_status_and_assignee_change
1277 issue = Issue.find(1)
1279 issue = Issue.find(1)
1278 assert_equal 1, issue.status_id
1280 assert_equal 1, issue.status_id
1279 @request.session[:user_id] = 2
1281 @request.session[:user_id] = 2
1280 assert_difference('TimeEntry.count', 0) do
1282 assert_difference('TimeEntry.count', 0) do
1281 put :update,
1283 put :update,
1282 :id => 1,
1284 :id => 1,
1283 :issue => { :status_id => 2, :assigned_to_id => 3 },
1285 :issue => { :status_id => 2, :assigned_to_id => 3 },
1284 :notes => 'Assigned to dlopper',
1286 :notes => 'Assigned to dlopper',
1285 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1287 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1286 end
1288 end
1287 assert_redirected_to :action => 'show', :id => '1'
1289 assert_redirected_to :action => 'show', :id => '1'
1288 issue.reload
1290 issue.reload
1289 assert_equal 2, issue.status_id
1291 assert_equal 2, issue.status_id
1290 j = Journal.find(:first, :order => 'id DESC')
1292 j = Journal.find(:first, :order => 'id DESC')
1291 assert_equal 'Assigned to dlopper', j.notes
1293 assert_equal 'Assigned to dlopper', j.notes
1292 assert_equal 2, j.details.size
1294 assert_equal 2, j.details.size
1293
1295
1294 mail = ActionMailer::Base.deliveries.last
1296 mail = ActionMailer::Base.deliveries.last
1295 assert mail.body.include?("Status changed from New to Assigned")
1297 assert mail.body.include?("Status changed from New to Assigned")
1296 # subject should contain the new status
1298 # subject should contain the new status
1297 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1299 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1298 end
1300 end
1299
1301
1300 def test_put_update_with_note_only
1302 def test_put_update_with_note_only
1301 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1303 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1302 # anonymous user
1304 # anonymous user
1303 put :update,
1305 put :update,
1304 :id => 1,
1306 :id => 1,
1305 :notes => notes
1307 :notes => notes
1306 assert_redirected_to :action => 'show', :id => '1'
1308 assert_redirected_to :action => 'show', :id => '1'
1307 j = Journal.find(:first, :order => 'id DESC')
1309 j = Journal.find(:first, :order => 'id DESC')
1308 assert_equal notes, j.notes
1310 assert_equal notes, j.notes
1309 assert_equal 0, j.details.size
1311 assert_equal 0, j.details.size
1310 assert_equal User.anonymous, j.user
1312 assert_equal User.anonymous, j.user
1311
1313
1312 mail = ActionMailer::Base.deliveries.last
1314 mail = ActionMailer::Base.deliveries.last
1313 assert mail.body.include?(notes)
1315 assert mail.body.include?(notes)
1314 end
1316 end
1315
1317
1316 def test_put_update_with_note_and_spent_time
1318 def test_put_update_with_note_and_spent_time
1317 @request.session[:user_id] = 2
1319 @request.session[:user_id] = 2
1318 spent_hours_before = Issue.find(1).spent_hours
1320 spent_hours_before = Issue.find(1).spent_hours
1319 assert_difference('TimeEntry.count') do
1321 assert_difference('TimeEntry.count') do
1320 put :update,
1322 put :update,
1321 :id => 1,
1323 :id => 1,
1322 :notes => '2.5 hours added',
1324 :notes => '2.5 hours added',
1323 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1325 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1324 end
1326 end
1325 assert_redirected_to :action => 'show', :id => '1'
1327 assert_redirected_to :action => 'show', :id => '1'
1326
1328
1327 issue = Issue.find(1)
1329 issue = Issue.find(1)
1328
1330
1329 j = Journal.find(:first, :order => 'id DESC')
1331 j = Journal.find(:first, :order => 'id DESC')
1330 assert_equal '2.5 hours added', j.notes
1332 assert_equal '2.5 hours added', j.notes
1331 assert_equal 0, j.details.size
1333 assert_equal 0, j.details.size
1332
1334
1333 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1335 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1334 assert_not_nil t
1336 assert_not_nil t
1335 assert_equal 2.5, t.hours
1337 assert_equal 2.5, t.hours
1336 assert_equal spent_hours_before + 2.5, issue.spent_hours
1338 assert_equal spent_hours_before + 2.5, issue.spent_hours
1337 end
1339 end
1338
1340
1339 def test_put_update_with_attachment_only
1341 def test_put_update_with_attachment_only
1340 set_tmp_attachments_directory
1342 set_tmp_attachments_directory
1341
1343
1342 # Delete all fixtured journals, a race condition can occur causing the wrong
1344 # Delete all fixtured journals, a race condition can occur causing the wrong
1343 # journal to get fetched in the next find.
1345 # journal to get fetched in the next find.
1344 Journal.delete_all
1346 Journal.delete_all
1345
1347
1346 # anonymous user
1348 # anonymous user
1347 assert_difference 'Attachment.count' do
1349 assert_difference 'Attachment.count' do
1348 put :update, :id => 1,
1350 put :update, :id => 1,
1349 :notes => '',
1351 :notes => '',
1350 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1352 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1351 end
1353 end
1352
1354
1353 assert_redirected_to :action => 'show', :id => '1'
1355 assert_redirected_to :action => 'show', :id => '1'
1354 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1356 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1355 assert j.notes.blank?
1357 assert j.notes.blank?
1356 assert_equal 1, j.details.size
1358 assert_equal 1, j.details.size
1357 assert_equal 'testfile.txt', j.details.first.value
1359 assert_equal 'testfile.txt', j.details.first.value
1358 assert_equal User.anonymous, j.user
1360 assert_equal User.anonymous, j.user
1359
1361
1360 attachment = Attachment.first(:order => 'id DESC')
1362 attachment = Attachment.first(:order => 'id DESC')
1361 assert_equal Issue.find(1), attachment.container
1363 assert_equal Issue.find(1), attachment.container
1362 assert_equal User.anonymous, attachment.author
1364 assert_equal User.anonymous, attachment.author
1363 assert_equal 'testfile.txt', attachment.filename
1365 assert_equal 'testfile.txt', attachment.filename
1364 assert_equal 'text/plain', attachment.content_type
1366 assert_equal 'text/plain', attachment.content_type
1365 assert_equal 'test file', attachment.description
1367 assert_equal 'test file', attachment.description
1366 assert_equal 59, attachment.filesize
1368 assert_equal 59, attachment.filesize
1367 assert File.exists?(attachment.diskfile)
1369 assert File.exists?(attachment.diskfile)
1368 assert_equal 59, File.size(attachment.diskfile)
1370 assert_equal 59, File.size(attachment.diskfile)
1369
1371
1370 mail = ActionMailer::Base.deliveries.last
1372 mail = ActionMailer::Base.deliveries.last
1371 assert mail.body.include?('testfile.txt')
1373 assert mail.body.include?('testfile.txt')
1372 end
1374 end
1373
1375
1374 def test_put_update_with_attachment_that_fails_to_save
1376 def test_put_update_with_attachment_that_fails_to_save
1375 set_tmp_attachments_directory
1377 set_tmp_attachments_directory
1376
1378
1377 # Delete all fixtured journals, a race condition can occur causing the wrong
1379 # Delete all fixtured journals, a race condition can occur causing the wrong
1378 # journal to get fetched in the next find.
1380 # journal to get fetched in the next find.
1379 Journal.delete_all
1381 Journal.delete_all
1380
1382
1381 # Mock out the unsaved attachment
1383 # Mock out the unsaved attachment
1382 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1384 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1383
1385
1384 # anonymous user
1386 # anonymous user
1385 put :update,
1387 put :update,
1386 :id => 1,
1388 :id => 1,
1387 :notes => '',
1389 :notes => '',
1388 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1390 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1389 assert_redirected_to :action => 'show', :id => '1'
1391 assert_redirected_to :action => 'show', :id => '1'
1390 assert_equal '1 file(s) could not be saved.', flash[:warning]
1392 assert_equal '1 file(s) could not be saved.', flash[:warning]
1391
1393
1392 end if Object.const_defined?(:Mocha)
1394 end if Object.const_defined?(:Mocha)
1393
1395
1394 def test_put_update_with_no_change
1396 def test_put_update_with_no_change
1395 issue = Issue.find(1)
1397 issue = Issue.find(1)
1396 issue.journals.clear
1398 issue.journals.clear
1397 ActionMailer::Base.deliveries.clear
1399 ActionMailer::Base.deliveries.clear
1398
1400
1399 put :update,
1401 put :update,
1400 :id => 1,
1402 :id => 1,
1401 :notes => ''
1403 :notes => ''
1402 assert_redirected_to :action => 'show', :id => '1'
1404 assert_redirected_to :action => 'show', :id => '1'
1403
1405
1404 issue.reload
1406 issue.reload
1405 assert issue.journals.empty?
1407 assert issue.journals.empty?
1406 # No email should be sent
1408 # No email should be sent
1407 assert ActionMailer::Base.deliveries.empty?
1409 assert ActionMailer::Base.deliveries.empty?
1408 end
1410 end
1409
1411
1410 def test_put_update_should_send_a_notification
1412 def test_put_update_should_send_a_notification
1411 @request.session[:user_id] = 2
1413 @request.session[:user_id] = 2
1412 ActionMailer::Base.deliveries.clear
1414 ActionMailer::Base.deliveries.clear
1413 issue = Issue.find(1)
1415 issue = Issue.find(1)
1414 old_subject = issue.subject
1416 old_subject = issue.subject
1415 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1417 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1416
1418
1417 put :update, :id => 1, :issue => {:subject => new_subject,
1419 put :update, :id => 1, :issue => {:subject => new_subject,
1418 :priority_id => '6',
1420 :priority_id => '6',
1419 :category_id => '1' # no change
1421 :category_id => '1' # no change
1420 }
1422 }
1421 assert_equal 1, ActionMailer::Base.deliveries.size
1423 assert_equal 1, ActionMailer::Base.deliveries.size
1422 end
1424 end
1423
1425
1424 def test_put_update_with_invalid_spent_time_hours_only
1426 def test_put_update_with_invalid_spent_time_hours_only
1425 @request.session[:user_id] = 2
1427 @request.session[:user_id] = 2
1426 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1428 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1427
1429
1428 assert_no_difference('Journal.count') do
1430 assert_no_difference('Journal.count') do
1429 put :update,
1431 put :update,
1430 :id => 1,
1432 :id => 1,
1431 :notes => notes,
1433 :notes => notes,
1432 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1434 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1433 end
1435 end
1434 assert_response :success
1436 assert_response :success
1435 assert_template 'edit'
1437 assert_template 'edit'
1436
1438
1437 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1439 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1438 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1440 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1439 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1441 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1440 end
1442 end
1441
1443
1442 def test_put_update_with_invalid_spent_time_comments_only
1444 def test_put_update_with_invalid_spent_time_comments_only
1443 @request.session[:user_id] = 2
1445 @request.session[:user_id] = 2
1444 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1446 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1445
1447
1446 assert_no_difference('Journal.count') do
1448 assert_no_difference('Journal.count') do
1447 put :update,
1449 put :update,
1448 :id => 1,
1450 :id => 1,
1449 :notes => notes,
1451 :notes => notes,
1450 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1452 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1451 end
1453 end
1452 assert_response :success
1454 assert_response :success
1453 assert_template 'edit'
1455 assert_template 'edit'
1454
1456
1455 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1457 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1456 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1458 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1457 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1459 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1458 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1460 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1459 end
1461 end
1460
1462
1461 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1463 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1462 issue = Issue.find(2)
1464 issue = Issue.find(2)
1463 @request.session[:user_id] = 2
1465 @request.session[:user_id] = 2
1464
1466
1465 put :update,
1467 put :update,
1466 :id => issue.id,
1468 :id => issue.id,
1467 :issue => {
1469 :issue => {
1468 :fixed_version_id => 4
1470 :fixed_version_id => 4
1469 }
1471 }
1470
1472
1471 assert_response :redirect
1473 assert_response :redirect
1472 issue.reload
1474 issue.reload
1473 assert_equal 4, issue.fixed_version_id
1475 assert_equal 4, issue.fixed_version_id
1474 assert_not_equal issue.project_id, issue.fixed_version.project_id
1476 assert_not_equal issue.project_id, issue.fixed_version.project_id
1475 end
1477 end
1476
1478
1477 def test_put_update_should_redirect_back_using_the_back_url_parameter
1479 def test_put_update_should_redirect_back_using_the_back_url_parameter
1478 issue = Issue.find(2)
1480 issue = Issue.find(2)
1479 @request.session[:user_id] = 2
1481 @request.session[:user_id] = 2
1480
1482
1481 put :update,
1483 put :update,
1482 :id => issue.id,
1484 :id => issue.id,
1483 :issue => {
1485 :issue => {
1484 :fixed_version_id => 4
1486 :fixed_version_id => 4
1485 },
1487 },
1486 :back_url => '/issues'
1488 :back_url => '/issues'
1487
1489
1488 assert_response :redirect
1490 assert_response :redirect
1489 assert_redirected_to '/issues'
1491 assert_redirected_to '/issues'
1490 end
1492 end
1491
1493
1492 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1494 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1493 issue = Issue.find(2)
1495 issue = Issue.find(2)
1494 @request.session[:user_id] = 2
1496 @request.session[:user_id] = 2
1495
1497
1496 put :update,
1498 put :update,
1497 :id => issue.id,
1499 :id => issue.id,
1498 :issue => {
1500 :issue => {
1499 :fixed_version_id => 4
1501 :fixed_version_id => 4
1500 },
1502 },
1501 :back_url => 'http://google.com'
1503 :back_url => 'http://google.com'
1502
1504
1503 assert_response :redirect
1505 assert_response :redirect
1504 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1506 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1505 end
1507 end
1506
1508
1507 def test_get_bulk_edit
1509 def test_get_bulk_edit
1508 @request.session[:user_id] = 2
1510 @request.session[:user_id] = 2
1509 get :bulk_edit, :ids => [1, 2]
1511 get :bulk_edit, :ids => [1, 2]
1510 assert_response :success
1512 assert_response :success
1511 assert_template 'bulk_edit'
1513 assert_template 'bulk_edit'
1512
1514
1513 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1515 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1514
1516
1515 # Project specific custom field, date type
1517 # Project specific custom field, date type
1516 field = CustomField.find(9)
1518 field = CustomField.find(9)
1517 assert !field.is_for_all?
1519 assert !field.is_for_all?
1518 assert_equal 'date', field.field_format
1520 assert_equal 'date', field.field_format
1519 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1521 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1520
1522
1521 # System wide custom field
1523 # System wide custom field
1522 assert CustomField.find(1).is_for_all?
1524 assert CustomField.find(1).is_for_all?
1523 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1525 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1524
1526
1525 # Be sure we don't display inactive IssuePriorities
1527 # Be sure we don't display inactive IssuePriorities
1526 assert ! IssuePriority.find(15).active?
1528 assert ! IssuePriority.find(15).active?
1527 assert_no_tag :option, :attributes => {:value => '15'},
1529 assert_no_tag :option, :attributes => {:value => '15'},
1528 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1530 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1529 end
1531 end
1530
1532
1531 def test_get_bulk_edit_on_different_projects
1533 def test_get_bulk_edit_on_different_projects
1532 @request.session[:user_id] = 2
1534 @request.session[:user_id] = 2
1533 get :bulk_edit, :ids => [1, 2, 6]
1535 get :bulk_edit, :ids => [1, 2, 6]
1534 assert_response :success
1536 assert_response :success
1535 assert_template 'bulk_edit'
1537 assert_template 'bulk_edit'
1536
1538
1537 # Can not set issues from different projects as children of an issue
1539 # Can not set issues from different projects as children of an issue
1538 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1540 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1539
1541
1540 # Project specific custom field, date type
1542 # Project specific custom field, date type
1541 field = CustomField.find(9)
1543 field = CustomField.find(9)
1542 assert !field.is_for_all?
1544 assert !field.is_for_all?
1543 assert !field.project_ids.include?(Issue.find(6).project_id)
1545 assert !field.project_ids.include?(Issue.find(6).project_id)
1544 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1546 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1545 end
1547 end
1546
1548
1547 def test_get_bulk_edit_with_user_custom_field
1549 def test_get_bulk_edit_with_user_custom_field
1548 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1550 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1549
1551
1550 @request.session[:user_id] = 2
1552 @request.session[:user_id] = 2
1551 get :bulk_edit, :ids => [1, 2]
1553 get :bulk_edit, :ids => [1, 2]
1552 assert_response :success
1554 assert_response :success
1553 assert_template 'bulk_edit'
1555 assert_template 'bulk_edit'
1554
1556
1555 assert_tag :select,
1557 assert_tag :select,
1556 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1558 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1557 :children => {
1559 :children => {
1558 :only => {:tag => 'option'},
1560 :only => {:tag => 'option'},
1559 :count => Project.find(1).users.count + 1
1561 :count => Project.find(1).users.count + 1
1560 }
1562 }
1561 end
1563 end
1562
1564
1563 def test_get_bulk_edit_with_version_custom_field
1565 def test_get_bulk_edit_with_version_custom_field
1564 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1566 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1565
1567
1566 @request.session[:user_id] = 2
1568 @request.session[:user_id] = 2
1567 get :bulk_edit, :ids => [1, 2]
1569 get :bulk_edit, :ids => [1, 2]
1568 assert_response :success
1570 assert_response :success
1569 assert_template 'bulk_edit'
1571 assert_template 'bulk_edit'
1570
1572
1571 assert_tag :select,
1573 assert_tag :select,
1572 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1574 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1573 :children => {
1575 :children => {
1574 :only => {:tag => 'option'},
1576 :only => {:tag => 'option'},
1575 :count => Project.find(1).shared_versions.count + 1
1577 :count => Project.find(1).shared_versions.count + 1
1576 }
1578 }
1577 end
1579 end
1578
1580
1579 def test_bulk_update
1581 def test_bulk_update
1580 @request.session[:user_id] = 2
1582 @request.session[:user_id] = 2
1581 # update issues priority
1583 # update issues priority
1582 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1584 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1583 :issue => {:priority_id => 7,
1585 :issue => {:priority_id => 7,
1584 :assigned_to_id => '',
1586 :assigned_to_id => '',
1585 :custom_field_values => {'2' => ''}}
1587 :custom_field_values => {'2' => ''}}
1586
1588
1587 assert_response 302
1589 assert_response 302
1588 # check that the issues were updated
1590 # check that the issues were updated
1589 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1591 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1590
1592
1591 issue = Issue.find(1)
1593 issue = Issue.find(1)
1592 journal = issue.journals.find(:first, :order => 'created_on DESC')
1594 journal = issue.journals.find(:first, :order => 'created_on DESC')
1593 assert_equal '125', issue.custom_value_for(2).value
1595 assert_equal '125', issue.custom_value_for(2).value
1594 assert_equal 'Bulk editing', journal.notes
1596 assert_equal 'Bulk editing', journal.notes
1595 assert_equal 1, journal.details.size
1597 assert_equal 1, journal.details.size
1596 end
1598 end
1597
1599
1598 def test_bulk_update_with_group_assignee
1600 def test_bulk_update_with_group_assignee
1599 group = Group.find(11)
1601 group = Group.find(11)
1600 project = Project.find(1)
1602 project = Project.find(1)
1601 project.members << Member.new(:principal => group, :roles => [Role.first])
1603 project.members << Member.new(:principal => group, :roles => [Role.first])
1602
1604
1603 @request.session[:user_id] = 2
1605 @request.session[:user_id] = 2
1604 # update issues assignee
1606 # update issues assignee
1605 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1607 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1606 :issue => {:priority_id => '',
1608 :issue => {:priority_id => '',
1607 :assigned_to_id => group.id,
1609 :assigned_to_id => group.id,
1608 :custom_field_values => {'2' => ''}}
1610 :custom_field_values => {'2' => ''}}
1609
1611
1610 assert_response 302
1612 assert_response 302
1611 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1613 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1612 end
1614 end
1613
1615
1614 def test_bulk_update_on_different_projects
1616 def test_bulk_update_on_different_projects
1615 @request.session[:user_id] = 2
1617 @request.session[:user_id] = 2
1616 # update issues priority
1618 # update issues priority
1617 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1619 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1618 :issue => {:priority_id => 7,
1620 :issue => {:priority_id => 7,
1619 :assigned_to_id => '',
1621 :assigned_to_id => '',
1620 :custom_field_values => {'2' => ''}}
1622 :custom_field_values => {'2' => ''}}
1621
1623
1622 assert_response 302
1624 assert_response 302
1623 # check that the issues were updated
1625 # check that the issues were updated
1624 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1626 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1625
1627
1626 issue = Issue.find(1)
1628 issue = Issue.find(1)
1627 journal = issue.journals.find(:first, :order => 'created_on DESC')
1629 journal = issue.journals.find(:first, :order => 'created_on DESC')
1628 assert_equal '125', issue.custom_value_for(2).value
1630 assert_equal '125', issue.custom_value_for(2).value
1629 assert_equal 'Bulk editing', journal.notes
1631 assert_equal 'Bulk editing', journal.notes
1630 assert_equal 1, journal.details.size
1632 assert_equal 1, journal.details.size
1631 end
1633 end
1632
1634
1633 def test_bulk_update_on_different_projects_without_rights
1635 def test_bulk_update_on_different_projects_without_rights
1634 @request.session[:user_id] = 3
1636 @request.session[:user_id] = 3
1635 user = User.find(3)
1637 user = User.find(3)
1636 action = { :controller => "issues", :action => "bulk_update" }
1638 action = { :controller => "issues", :action => "bulk_update" }
1637 assert user.allowed_to?(action, Issue.find(1).project)
1639 assert user.allowed_to?(action, Issue.find(1).project)
1638 assert ! user.allowed_to?(action, Issue.find(6).project)
1640 assert ! user.allowed_to?(action, Issue.find(6).project)
1639 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1641 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1640 :issue => {:priority_id => 7,
1642 :issue => {:priority_id => 7,
1641 :assigned_to_id => '',
1643 :assigned_to_id => '',
1642 :custom_field_values => {'2' => ''}}
1644 :custom_field_values => {'2' => ''}}
1643 assert_response 403
1645 assert_response 403
1644 assert_not_equal "Bulk should fail", Journal.last.notes
1646 assert_not_equal "Bulk should fail", Journal.last.notes
1645 end
1647 end
1646
1648
1647 def test_bullk_update_should_send_a_notification
1649 def test_bullk_update_should_send_a_notification
1648 @request.session[:user_id] = 2
1650 @request.session[:user_id] = 2
1649 ActionMailer::Base.deliveries.clear
1651 ActionMailer::Base.deliveries.clear
1650 post(:bulk_update,
1652 post(:bulk_update,
1651 {
1653 {
1652 :ids => [1, 2],
1654 :ids => [1, 2],
1653 :notes => 'Bulk editing',
1655 :notes => 'Bulk editing',
1654 :issue => {
1656 :issue => {
1655 :priority_id => 7,
1657 :priority_id => 7,
1656 :assigned_to_id => '',
1658 :assigned_to_id => '',
1657 :custom_field_values => {'2' => ''}
1659 :custom_field_values => {'2' => ''}
1658 }
1660 }
1659 })
1661 })
1660
1662
1661 assert_response 302
1663 assert_response 302
1662 assert_equal 2, ActionMailer::Base.deliveries.size
1664 assert_equal 2, ActionMailer::Base.deliveries.size
1663 end
1665 end
1664
1666
1665 def test_bulk_update_status
1667 def test_bulk_update_status
1666 @request.session[:user_id] = 2
1668 @request.session[:user_id] = 2
1667 # update issues priority
1669 # update issues priority
1668 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1670 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1669 :issue => {:priority_id => '',
1671 :issue => {:priority_id => '',
1670 :assigned_to_id => '',
1672 :assigned_to_id => '',
1671 :status_id => '5'}
1673 :status_id => '5'}
1672
1674
1673 assert_response 302
1675 assert_response 302
1674 issue = Issue.find(1)
1676 issue = Issue.find(1)
1675 assert issue.closed?
1677 assert issue.closed?
1676 end
1678 end
1677
1679
1678 def test_bulk_update_parent_id
1680 def test_bulk_update_parent_id
1679 @request.session[:user_id] = 2
1681 @request.session[:user_id] = 2
1680 post :bulk_update, :ids => [1, 3],
1682 post :bulk_update, :ids => [1, 3],
1681 :notes => 'Bulk editing parent',
1683 :notes => 'Bulk editing parent',
1682 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1684 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1683
1685
1684 assert_response 302
1686 assert_response 302
1685 parent = Issue.find(2)
1687 parent = Issue.find(2)
1686 assert_equal parent.id, Issue.find(1).parent_id
1688 assert_equal parent.id, Issue.find(1).parent_id
1687 assert_equal parent.id, Issue.find(3).parent_id
1689 assert_equal parent.id, Issue.find(3).parent_id
1688 assert_equal [1, 3], parent.children.collect(&:id).sort
1690 assert_equal [1, 3], parent.children.collect(&:id).sort
1689 end
1691 end
1690
1692
1691 def test_bulk_update_custom_field
1693 def test_bulk_update_custom_field
1692 @request.session[:user_id] = 2
1694 @request.session[:user_id] = 2
1693 # update issues priority
1695 # update issues priority
1694 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1696 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1695 :issue => {:priority_id => '',
1697 :issue => {:priority_id => '',
1696 :assigned_to_id => '',
1698 :assigned_to_id => '',
1697 :custom_field_values => {'2' => '777'}}
1699 :custom_field_values => {'2' => '777'}}
1698
1700
1699 assert_response 302
1701 assert_response 302
1700
1702
1701 issue = Issue.find(1)
1703 issue = Issue.find(1)
1702 journal = issue.journals.find(:first, :order => 'created_on DESC')
1704 journal = issue.journals.find(:first, :order => 'created_on DESC')
1703 assert_equal '777', issue.custom_value_for(2).value
1705 assert_equal '777', issue.custom_value_for(2).value
1704 assert_equal 1, journal.details.size
1706 assert_equal 1, journal.details.size
1705 assert_equal '125', journal.details.first.old_value
1707 assert_equal '125', journal.details.first.old_value
1706 assert_equal '777', journal.details.first.value
1708 assert_equal '777', journal.details.first.value
1707 end
1709 end
1708
1710
1709 def test_bulk_update_unassign
1711 def test_bulk_update_unassign
1710 assert_not_nil Issue.find(2).assigned_to
1712 assert_not_nil Issue.find(2).assigned_to
1711 @request.session[:user_id] = 2
1713 @request.session[:user_id] = 2
1712 # unassign issues
1714 # unassign issues
1713 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1715 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1714 assert_response 302
1716 assert_response 302
1715 # check that the issues were updated
1717 # check that the issues were updated
1716 assert_nil Issue.find(2).assigned_to
1718 assert_nil Issue.find(2).assigned_to
1717 end
1719 end
1718
1720
1719 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1721 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1720 @request.session[:user_id] = 2
1722 @request.session[:user_id] = 2
1721
1723
1722 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1724 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1723
1725
1724 assert_response :redirect
1726 assert_response :redirect
1725 issues = Issue.find([1,2])
1727 issues = Issue.find([1,2])
1726 issues.each do |issue|
1728 issues.each do |issue|
1727 assert_equal 4, issue.fixed_version_id
1729 assert_equal 4, issue.fixed_version_id
1728 assert_not_equal issue.project_id, issue.fixed_version.project_id
1730 assert_not_equal issue.project_id, issue.fixed_version.project_id
1729 end
1731 end
1730 end
1732 end
1731
1733
1732 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1734 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1733 @request.session[:user_id] = 2
1735 @request.session[:user_id] = 2
1734 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1736 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1735
1737
1736 assert_response :redirect
1738 assert_response :redirect
1737 assert_redirected_to '/issues'
1739 assert_redirected_to '/issues'
1738 end
1740 end
1739
1741
1740 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1742 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1741 @request.session[:user_id] = 2
1743 @request.session[:user_id] = 2
1742 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1744 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1743
1745
1744 assert_response :redirect
1746 assert_response :redirect
1745 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1747 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1746 end
1748 end
1747
1749
1748 def test_destroy_issue_with_no_time_entries
1750 def test_destroy_issue_with_no_time_entries
1749 assert_nil TimeEntry.find_by_issue_id(2)
1751 assert_nil TimeEntry.find_by_issue_id(2)
1750 @request.session[:user_id] = 2
1752 @request.session[:user_id] = 2
1751 post :destroy, :id => 2
1753 post :destroy, :id => 2
1752 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1754 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1753 assert_nil Issue.find_by_id(2)
1755 assert_nil Issue.find_by_id(2)
1754 end
1756 end
1755
1757
1756 def test_destroy_issues_with_time_entries
1758 def test_destroy_issues_with_time_entries
1757 @request.session[:user_id] = 2
1759 @request.session[:user_id] = 2
1758 post :destroy, :ids => [1, 3]
1760 post :destroy, :ids => [1, 3]
1759 assert_response :success
1761 assert_response :success
1760 assert_template 'destroy'
1762 assert_template 'destroy'
1761 assert_not_nil assigns(:hours)
1763 assert_not_nil assigns(:hours)
1762 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1764 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1763 end
1765 end
1764
1766
1765 def test_destroy_issues_and_destroy_time_entries
1767 def test_destroy_issues_and_destroy_time_entries
1766 @request.session[:user_id] = 2
1768 @request.session[:user_id] = 2
1767 post :destroy, :ids => [1, 3], :todo => 'destroy'
1769 post :destroy, :ids => [1, 3], :todo => 'destroy'
1768 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1770 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1769 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1771 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1770 assert_nil TimeEntry.find_by_id([1, 2])
1772 assert_nil TimeEntry.find_by_id([1, 2])
1771 end
1773 end
1772
1774
1773 def test_destroy_issues_and_assign_time_entries_to_project
1775 def test_destroy_issues_and_assign_time_entries_to_project
1774 @request.session[:user_id] = 2
1776 @request.session[:user_id] = 2
1775 post :destroy, :ids => [1, 3], :todo => 'nullify'
1777 post :destroy, :ids => [1, 3], :todo => 'nullify'
1776 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1778 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1777 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1779 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1778 assert_nil TimeEntry.find(1).issue_id
1780 assert_nil TimeEntry.find(1).issue_id
1779 assert_nil TimeEntry.find(2).issue_id
1781 assert_nil TimeEntry.find(2).issue_id
1780 end
1782 end
1781
1783
1782 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1784 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1783 @request.session[:user_id] = 2
1785 @request.session[:user_id] = 2
1784 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1786 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1785 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1787 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1786 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1788 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1787 assert_equal 2, TimeEntry.find(1).issue_id
1789 assert_equal 2, TimeEntry.find(1).issue_id
1788 assert_equal 2, TimeEntry.find(2).issue_id
1790 assert_equal 2, TimeEntry.find(2).issue_id
1789 end
1791 end
1790
1792
1791 def test_destroy_issues_from_different_projects
1793 def test_destroy_issues_from_different_projects
1792 @request.session[:user_id] = 2
1794 @request.session[:user_id] = 2
1793 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1795 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1794 assert_redirected_to :controller => 'issues', :action => 'index'
1796 assert_redirected_to :controller => 'issues', :action => 'index'
1795 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1797 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1796 end
1798 end
1797
1799
1798 def test_destroy_parent_and_child_issues
1800 def test_destroy_parent_and_child_issues
1799 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
1801 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
1800 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
1802 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
1801 assert child.is_descendant_of?(parent.reload)
1803 assert child.is_descendant_of?(parent.reload)
1802
1804
1803 @request.session[:user_id] = 2
1805 @request.session[:user_id] = 2
1804 assert_difference 'Issue.count', -2 do
1806 assert_difference 'Issue.count', -2 do
1805 post :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
1807 post :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
1806 end
1808 end
1807 assert_response 302
1809 assert_response 302
1808 end
1810 end
1809
1811
1810 def test_default_search_scope
1812 def test_default_search_scope
1811 get :index
1813 get :index
1812 assert_tag :div, :attributes => {:id => 'quick-search'},
1814 assert_tag :div, :attributes => {:id => 'quick-search'},
1813 :child => {:tag => 'form',
1815 :child => {:tag => 'form',
1814 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1816 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1815 end
1817 end
1816 end
1818 end
General Comments 0
You need to be logged in to leave comments. Login now