##// END OF EJS Templates
Don't prepend project name if the version is not shared....
Jean-Philippe Lang -
r12972:2e04614e218e
parent child
Show More
@@ -1,1360 +1,1360
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27 include Redmine::Pagination::Helper
27 include Redmine::Pagination::Helper
28
28
29 extend Forwardable
29 extend Forwardable
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
31
31
32 # Return true if user is authorized for controller/action, otherwise false
32 # Return true if user is authorized for controller/action, otherwise false
33 def authorize_for(controller, action)
33 def authorize_for(controller, action)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 User.current.allowed_to?({:controller => controller, :action => action}, @project)
35 end
35 end
36
36
37 # Display a link if user is authorized
37 # Display a link if user is authorized
38 #
38 #
39 # @param [String] name Anchor text (passed to link_to)
39 # @param [String] name Anchor text (passed to link_to)
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
41 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] html_options Options passed to link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
45 end
45 end
46
46
47 # Displays a link to user's account page if active
47 # Displays a link to user's account page if active
48 def link_to_user(user, options={})
48 def link_to_user(user, options={})
49 if user.is_a?(User)
49 if user.is_a?(User)
50 name = h(user.name(options[:format]))
50 name = h(user.name(options[:format]))
51 if user.active? || (User.current.admin? && user.logged?)
51 if user.active? || (User.current.admin? && user.logged?)
52 link_to name, user_path(user), :class => user.css_classes
52 link_to name, user_path(user), :class => user.css_classes
53 else
53 else
54 name
54 name
55 end
55 end
56 else
56 else
57 h(user.to_s)
57 h(user.to_s)
58 end
58 end
59 end
59 end
60
60
61 # Displays a link to +issue+ with its subject.
61 # Displays a link to +issue+ with its subject.
62 # Examples:
62 # Examples:
63 #
63 #
64 # link_to_issue(issue) # => Defect #6: This is the subject
64 # link_to_issue(issue) # => Defect #6: This is the subject
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
65 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
66 # link_to_issue(issue, :subject => false) # => Defect #6
66 # link_to_issue(issue, :subject => false) # => Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
67 # link_to_issue(issue, :project => true) # => Foo - Defect #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
68 # link_to_issue(issue, :subject => false, :tracker => false) # => #6
69 #
69 #
70 def link_to_issue(issue, options={})
70 def link_to_issue(issue, options={})
71 title = nil
71 title = nil
72 subject = nil
72 subject = nil
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
73 text = options[:tracker] == false ? "##{issue.id}" : "#{issue.tracker} ##{issue.id}"
74 if options[:subject] == false
74 if options[:subject] == false
75 title = issue.subject.truncate(60)
75 title = issue.subject.truncate(60)
76 else
76 else
77 subject = issue.subject
77 subject = issue.subject
78 if truncate_length = options[:truncate]
78 if truncate_length = options[:truncate]
79 subject = subject.truncate(truncate_length)
79 subject = subject.truncate(truncate_length)
80 end
80 end
81 end
81 end
82 only_path = options[:only_path].nil? ? true : options[:only_path]
82 only_path = options[:only_path].nil? ? true : options[:only_path]
83 s = link_to(text, issue_path(issue, :only_path => only_path),
83 s = link_to(text, issue_path(issue, :only_path => only_path),
84 :class => issue.css_classes, :title => title)
84 :class => issue.css_classes, :title => title)
85 s << h(": #{subject}") if subject
85 s << h(": #{subject}") if subject
86 s = h("#{issue.project} - ") + s if options[:project]
86 s = h("#{issue.project} - ") + s if options[:project]
87 s
87 s
88 end
88 end
89
89
90 # Generates a link to an attachment.
90 # Generates a link to an attachment.
91 # Options:
91 # Options:
92 # * :text - Link text (default to attachment filename)
92 # * :text - Link text (default to attachment filename)
93 # * :download - Force download (default: false)
93 # * :download - Force download (default: false)
94 def link_to_attachment(attachment, options={})
94 def link_to_attachment(attachment, options={})
95 text = options.delete(:text) || attachment.filename
95 text = options.delete(:text) || attachment.filename
96 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
96 route_method = options.delete(:download) ? :download_named_attachment_path : :named_attachment_path
97 html_options = options.slice!(:only_path)
97 html_options = options.slice!(:only_path)
98 url = send(route_method, attachment, attachment.filename, options)
98 url = send(route_method, attachment, attachment.filename, options)
99 link_to text, url, html_options
99 link_to text, url, html_options
100 end
100 end
101
101
102 # Generates a link to a SCM revision
102 # Generates a link to a SCM revision
103 # Options:
103 # Options:
104 # * :text - Link text (default to the formatted revision)
104 # * :text - Link text (default to the formatted revision)
105 def link_to_revision(revision, repository, options={})
105 def link_to_revision(revision, repository, options={})
106 if repository.is_a?(Project)
106 if repository.is_a?(Project)
107 repository = repository.repository
107 repository = repository.repository
108 end
108 end
109 text = options.delete(:text) || format_revision(revision)
109 text = options.delete(:text) || format_revision(revision)
110 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
110 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
111 link_to(
111 link_to(
112 h(text),
112 h(text),
113 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
113 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
114 :title => l(:label_revision_id, format_revision(revision))
114 :title => l(:label_revision_id, format_revision(revision))
115 )
115 )
116 end
116 end
117
117
118 # Generates a link to a message
118 # Generates a link to a message
119 def link_to_message(message, options={}, html_options = nil)
119 def link_to_message(message, options={}, html_options = nil)
120 link_to(
120 link_to(
121 message.subject.truncate(60),
121 message.subject.truncate(60),
122 board_message_path(message.board_id, message.parent_id || message.id, {
122 board_message_path(message.board_id, message.parent_id || message.id, {
123 :r => (message.parent_id && message.id),
123 :r => (message.parent_id && message.id),
124 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
124 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
125 }.merge(options)),
125 }.merge(options)),
126 html_options
126 html_options
127 )
127 )
128 end
128 end
129
129
130 # Generates a link to a project if active
130 # Generates a link to a project if active
131 # Examples:
131 # Examples:
132 #
132 #
133 # link_to_project(project) # => link to the specified project overview
133 # link_to_project(project) # => link to the specified project overview
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.archived?
138 if project.archived?
139 h(project.name)
139 h(project.name)
140 elsif options.key?(:action)
140 elsif options.key?(:action)
141 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
141 ActiveSupport::Deprecation.warn "#link_to_project with :action option is deprecated and will be removed in Redmine 3.0."
142 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
142 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
143 link_to project.name, url, html_options
143 link_to project.name, url, html_options
144 else
144 else
145 link_to project.name, project_path(project, options), html_options
145 link_to project.name, project_path(project, options), html_options
146 end
146 end
147 end
147 end
148
148
149 # Generates a link to a project settings if active
149 # Generates a link to a project settings if active
150 def link_to_project_settings(project, options={}, html_options=nil)
150 def link_to_project_settings(project, options={}, html_options=nil)
151 if project.active?
151 if project.active?
152 link_to project.name, settings_project_path(project, options), html_options
152 link_to project.name, settings_project_path(project, options), html_options
153 elsif project.archived?
153 elsif project.archived?
154 h(project.name)
154 h(project.name)
155 else
155 else
156 link_to project.name, project_path(project, options), html_options
156 link_to project.name, project_path(project, options), html_options
157 end
157 end
158 end
158 end
159
159
160 # Generates a link to a version
160 # Generates a link to a version
161 def link_to_version(version, options = {})
161 def link_to_version(version, options = {})
162 return '' unless version && version.is_a?(Version)
162 return '' unless version && version.is_a?(Version)
163 options = {:title => format_date(version.effective_date)}.merge(options)
163 options = {:title => format_date(version.effective_date)}.merge(options)
164 link_to_if version.visible?, format_version_name(version), version_path(version), options
164 link_to_if version.visible?, format_version_name(version), version_path(version), options
165 end
165 end
166
166
167 # Helper that formats object for html or text rendering
167 # Helper that formats object for html or text rendering
168 def format_object(object, html=true, &block)
168 def format_object(object, html=true, &block)
169 if block_given?
169 if block_given?
170 object = yield object
170 object = yield object
171 end
171 end
172 case object.class.name
172 case object.class.name
173 when 'Array'
173 when 'Array'
174 object.map {|o| format_object(o, html)}.join(', ').html_safe
174 object.map {|o| format_object(o, html)}.join(', ').html_safe
175 when 'Time'
175 when 'Time'
176 format_time(object)
176 format_time(object)
177 when 'Date'
177 when 'Date'
178 format_date(object)
178 format_date(object)
179 when 'Fixnum'
179 when 'Fixnum'
180 object.to_s
180 object.to_s
181 when 'Float'
181 when 'Float'
182 sprintf "%.2f", object
182 sprintf "%.2f", object
183 when 'User'
183 when 'User'
184 html ? link_to_user(object) : object.to_s
184 html ? link_to_user(object) : object.to_s
185 when 'Project'
185 when 'Project'
186 html ? link_to_project(object) : object.to_s
186 html ? link_to_project(object) : object.to_s
187 when 'Version'
187 when 'Version'
188 html ? link_to_version(object) : object.to_s
188 html ? link_to_version(object) : object.to_s
189 when 'TrueClass'
189 when 'TrueClass'
190 l(:general_text_Yes)
190 l(:general_text_Yes)
191 when 'FalseClass'
191 when 'FalseClass'
192 l(:general_text_No)
192 l(:general_text_No)
193 when 'Issue'
193 when 'Issue'
194 object.visible? && html ? link_to_issue(object) : "##{object.id}"
194 object.visible? && html ? link_to_issue(object) : "##{object.id}"
195 when 'CustomValue', 'CustomFieldValue'
195 when 'CustomValue', 'CustomFieldValue'
196 if object.custom_field
196 if object.custom_field
197 f = object.custom_field.format.formatted_custom_value(self, object, html)
197 f = object.custom_field.format.formatted_custom_value(self, object, html)
198 if f.nil? || f.is_a?(String)
198 if f.nil? || f.is_a?(String)
199 f
199 f
200 else
200 else
201 format_object(f, html, &block)
201 format_object(f, html, &block)
202 end
202 end
203 else
203 else
204 object.value.to_s
204 object.value.to_s
205 end
205 end
206 else
206 else
207 html ? h(object) : object.to_s
207 html ? h(object) : object.to_s
208 end
208 end
209 end
209 end
210
210
211 def wiki_page_path(page, options={})
211 def wiki_page_path(page, options={})
212 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
212 url_for({:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title}.merge(options))
213 end
213 end
214
214
215 def thumbnail_tag(attachment)
215 def thumbnail_tag(attachment)
216 link_to image_tag(thumbnail_path(attachment)),
216 link_to image_tag(thumbnail_path(attachment)),
217 named_attachment_path(attachment, attachment.filename),
217 named_attachment_path(attachment, attachment.filename),
218 :title => attachment.filename
218 :title => attachment.filename
219 end
219 end
220
220
221 def toggle_link(name, id, options={})
221 def toggle_link(name, id, options={})
222 onclick = "$('##{id}').toggle(); "
222 onclick = "$('##{id}').toggle(); "
223 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
223 onclick << (options[:focus] ? "$('##{options[:focus]}').focus(); " : "this.blur(); ")
224 onclick << "return false;"
224 onclick << "return false;"
225 link_to(name, "#", :onclick => onclick)
225 link_to(name, "#", :onclick => onclick)
226 end
226 end
227
227
228 def image_to_function(name, function, html_options = {})
228 def image_to_function(name, function, html_options = {})
229 html_options.symbolize_keys!
229 html_options.symbolize_keys!
230 tag(:input, html_options.merge({
230 tag(:input, html_options.merge({
231 :type => "image", :src => image_path(name),
231 :type => "image", :src => image_path(name),
232 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
232 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
233 }))
233 }))
234 end
234 end
235
235
236 def format_activity_title(text)
236 def format_activity_title(text)
237 h(truncate_single_line_raw(text, 100))
237 h(truncate_single_line_raw(text, 100))
238 end
238 end
239
239
240 def format_activity_day(date)
240 def format_activity_day(date)
241 date == User.current.today ? l(:label_today).titleize : format_date(date)
241 date == User.current.today ? l(:label_today).titleize : format_date(date)
242 end
242 end
243
243
244 def format_activity_description(text)
244 def format_activity_description(text)
245 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
245 h(text.to_s.truncate(120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
246 ).gsub(/[\r\n]+/, "<br />").html_safe
246 ).gsub(/[\r\n]+/, "<br />").html_safe
247 end
247 end
248
248
249 def format_version_name(version)
249 def format_version_name(version)
250 if version.project == @project
250 if !version.shared? || version.project == @project
251 h(version)
251 h(version)
252 else
252 else
253 h("#{version.project} - #{version}")
253 h("#{version.project} - #{version}")
254 end
254 end
255 end
255 end
256
256
257 def due_date_distance_in_words(date)
257 def due_date_distance_in_words(date)
258 if date
258 if date
259 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
259 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
260 end
260 end
261 end
261 end
262
262
263 # Renders a tree of projects as a nested set of unordered lists
263 # Renders a tree of projects as a nested set of unordered lists
264 # The given collection may be a subset of the whole project tree
264 # The given collection may be a subset of the whole project tree
265 # (eg. some intermediate nodes are private and can not be seen)
265 # (eg. some intermediate nodes are private and can not be seen)
266 def render_project_nested_lists(projects)
266 def render_project_nested_lists(projects)
267 s = ''
267 s = ''
268 if projects.any?
268 if projects.any?
269 ancestors = []
269 ancestors = []
270 original_project = @project
270 original_project = @project
271 projects.sort_by(&:lft).each do |project|
271 projects.sort_by(&:lft).each do |project|
272 # set the project environment to please macros.
272 # set the project environment to please macros.
273 @project = project
273 @project = project
274 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
274 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
275 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
275 s << "<ul class='projects #{ ancestors.empty? ? 'root' : nil}'>\n"
276 else
276 else
277 ancestors.pop
277 ancestors.pop
278 s << "</li>"
278 s << "</li>"
279 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
279 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
280 ancestors.pop
280 ancestors.pop
281 s << "</ul></li>\n"
281 s << "</ul></li>\n"
282 end
282 end
283 end
283 end
284 classes = (ancestors.empty? ? 'root' : 'child')
284 classes = (ancestors.empty? ? 'root' : 'child')
285 s << "<li class='#{classes}'><div class='#{classes}'>"
285 s << "<li class='#{classes}'><div class='#{classes}'>"
286 s << h(block_given? ? yield(project) : project.name)
286 s << h(block_given? ? yield(project) : project.name)
287 s << "</div>\n"
287 s << "</div>\n"
288 ancestors << project
288 ancestors << project
289 end
289 end
290 s << ("</li></ul>\n" * ancestors.size)
290 s << ("</li></ul>\n" * ancestors.size)
291 @project = original_project
291 @project = original_project
292 end
292 end
293 s.html_safe
293 s.html_safe
294 end
294 end
295
295
296 def render_page_hierarchy(pages, node=nil, options={})
296 def render_page_hierarchy(pages, node=nil, options={})
297 content = ''
297 content = ''
298 if pages[node]
298 if pages[node]
299 content << "<ul class=\"pages-hierarchy\">\n"
299 content << "<ul class=\"pages-hierarchy\">\n"
300 pages[node].each do |page|
300 pages[node].each do |page|
301 content << "<li>"
301 content << "<li>"
302 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
302 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title, :version => nil},
303 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
303 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
304 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
304 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
305 content << "</li>\n"
305 content << "</li>\n"
306 end
306 end
307 content << "</ul>\n"
307 content << "</ul>\n"
308 end
308 end
309 content.html_safe
309 content.html_safe
310 end
310 end
311
311
312 # Renders flash messages
312 # Renders flash messages
313 def render_flash_messages
313 def render_flash_messages
314 s = ''
314 s = ''
315 flash.each do |k,v|
315 flash.each do |k,v|
316 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
316 s << content_tag('div', v.html_safe, :class => "flash #{k}", :id => "flash_#{k}")
317 end
317 end
318 s.html_safe
318 s.html_safe
319 end
319 end
320
320
321 # Renders tabs and their content
321 # Renders tabs and their content
322 def render_tabs(tabs, selected=params[:tab])
322 def render_tabs(tabs, selected=params[:tab])
323 if tabs.any?
323 if tabs.any?
324 unless tabs.detect {|tab| tab[:name] == selected}
324 unless tabs.detect {|tab| tab[:name] == selected}
325 selected = nil
325 selected = nil
326 end
326 end
327 selected ||= tabs.first[:name]
327 selected ||= tabs.first[:name]
328 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
328 render :partial => 'common/tabs', :locals => {:tabs => tabs, :selected_tab => selected}
329 else
329 else
330 content_tag 'p', l(:label_no_data), :class => "nodata"
330 content_tag 'p', l(:label_no_data), :class => "nodata"
331 end
331 end
332 end
332 end
333
333
334 # Renders the project quick-jump box
334 # Renders the project quick-jump box
335 def render_project_jump_box
335 def render_project_jump_box
336 return unless User.current.logged?
336 return unless User.current.logged?
337 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
337 projects = User.current.memberships.collect(&:project).compact.select(&:active?).uniq
338 if projects.any?
338 if projects.any?
339 options =
339 options =
340 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
340 ("<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
341 '<option value="" disabled="disabled">---</option>').html_safe
341 '<option value="" disabled="disabled">---</option>').html_safe
342
342
343 options << project_tree_options_for_select(projects, :selected => @project) do |p|
343 options << project_tree_options_for_select(projects, :selected => @project) do |p|
344 { :value => project_path(:id => p, :jump => current_menu_item) }
344 { :value => project_path(:id => p, :jump => current_menu_item) }
345 end
345 end
346
346
347 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
347 select_tag('project_quick_jump_box', options, :onchange => 'if (this.value != \'\') { window.location = this.value; }')
348 end
348 end
349 end
349 end
350
350
351 def project_tree_options_for_select(projects, options = {})
351 def project_tree_options_for_select(projects, options = {})
352 s = ''
352 s = ''
353 project_tree(projects) do |project, level|
353 project_tree(projects) do |project, level|
354 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
354 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
355 tag_options = {:value => project.id}
355 tag_options = {:value => project.id}
356 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
356 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
357 tag_options[:selected] = 'selected'
357 tag_options[:selected] = 'selected'
358 else
358 else
359 tag_options[:selected] = nil
359 tag_options[:selected] = nil
360 end
360 end
361 tag_options.merge!(yield(project)) if block_given?
361 tag_options.merge!(yield(project)) if block_given?
362 s << content_tag('option', name_prefix + h(project), tag_options)
362 s << content_tag('option', name_prefix + h(project), tag_options)
363 end
363 end
364 s.html_safe
364 s.html_safe
365 end
365 end
366
366
367 # Yields the given block for each project with its level in the tree
367 # Yields the given block for each project with its level in the tree
368 #
368 #
369 # Wrapper for Project#project_tree
369 # Wrapper for Project#project_tree
370 def project_tree(projects, &block)
370 def project_tree(projects, &block)
371 Project.project_tree(projects, &block)
371 Project.project_tree(projects, &block)
372 end
372 end
373
373
374 def principals_check_box_tags(name, principals)
374 def principals_check_box_tags(name, principals)
375 s = ''
375 s = ''
376 principals.each do |principal|
376 principals.each do |principal|
377 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
377 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
378 end
378 end
379 s.html_safe
379 s.html_safe
380 end
380 end
381
381
382 # Returns a string for users/groups option tags
382 # Returns a string for users/groups option tags
383 def principals_options_for_select(collection, selected=nil)
383 def principals_options_for_select(collection, selected=nil)
384 s = ''
384 s = ''
385 if collection.include?(User.current)
385 if collection.include?(User.current)
386 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
386 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
387 end
387 end
388 groups = ''
388 groups = ''
389 collection.sort.each do |element|
389 collection.sort.each do |element|
390 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
390 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
391 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
391 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
392 end
392 end
393 unless groups.empty?
393 unless groups.empty?
394 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
394 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
395 end
395 end
396 s.html_safe
396 s.html_safe
397 end
397 end
398
398
399 # Options for the new membership projects combo-box
399 # Options for the new membership projects combo-box
400 def options_for_membership_project_select(principal, projects)
400 def options_for_membership_project_select(principal, projects)
401 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
401 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
402 options << project_tree_options_for_select(projects) do |p|
402 options << project_tree_options_for_select(projects) do |p|
403 {:disabled => principal.projects.to_a.include?(p)}
403 {:disabled => principal.projects.to_a.include?(p)}
404 end
404 end
405 options
405 options
406 end
406 end
407
407
408 def option_tag(name, text, value, selected=nil, options={})
408 def option_tag(name, text, value, selected=nil, options={})
409 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
409 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
410 end
410 end
411
411
412 # Truncates and returns the string as a single line
412 # Truncates and returns the string as a single line
413 def truncate_single_line(string, *args)
413 def truncate_single_line(string, *args)
414 ActiveSupport::Deprecation.warn(
414 ActiveSupport::Deprecation.warn(
415 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
415 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
416 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
416 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
417 # So, result is broken.
417 # So, result is broken.
418 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
418 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
419 end
419 end
420
420
421 def truncate_single_line_raw(string, length)
421 def truncate_single_line_raw(string, length)
422 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
422 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
423 end
423 end
424
424
425 # Truncates at line break after 250 characters or options[:length]
425 # Truncates at line break after 250 characters or options[:length]
426 def truncate_lines(string, options={})
426 def truncate_lines(string, options={})
427 length = options[:length] || 250
427 length = options[:length] || 250
428 if string.to_s =~ /\A(.{#{length}}.*?)$/m
428 if string.to_s =~ /\A(.{#{length}}.*?)$/m
429 "#{$1}..."
429 "#{$1}..."
430 else
430 else
431 string
431 string
432 end
432 end
433 end
433 end
434
434
435 def anchor(text)
435 def anchor(text)
436 text.to_s.gsub(' ', '_')
436 text.to_s.gsub(' ', '_')
437 end
437 end
438
438
439 def html_hours(text)
439 def html_hours(text)
440 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
440 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
441 end
441 end
442
442
443 def authoring(created, author, options={})
443 def authoring(created, author, options={})
444 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
444 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
445 end
445 end
446
446
447 def time_tag(time)
447 def time_tag(time)
448 text = distance_of_time_in_words(Time.now, time)
448 text = distance_of_time_in_words(Time.now, time)
449 if @project
449 if @project
450 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
450 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
451 else
451 else
452 content_tag('abbr', text, :title => format_time(time))
452 content_tag('abbr', text, :title => format_time(time))
453 end
453 end
454 end
454 end
455
455
456 def syntax_highlight_lines(name, content)
456 def syntax_highlight_lines(name, content)
457 lines = []
457 lines = []
458 syntax_highlight(name, content).each_line { |line| lines << line }
458 syntax_highlight(name, content).each_line { |line| lines << line }
459 lines
459 lines
460 end
460 end
461
461
462 def syntax_highlight(name, content)
462 def syntax_highlight(name, content)
463 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
463 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
464 end
464 end
465
465
466 def to_path_param(path)
466 def to_path_param(path)
467 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
467 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
468 str.blank? ? nil : str
468 str.blank? ? nil : str
469 end
469 end
470
470
471 def reorder_links(name, url, method = :post)
471 def reorder_links(name, url, method = :post)
472 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
472 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
473 url.merge({"#{name}[move_to]" => 'highest'}),
473 url.merge({"#{name}[move_to]" => 'highest'}),
474 :method => method, :title => l(:label_sort_highest)) +
474 :method => method, :title => l(:label_sort_highest)) +
475 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
475 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
476 url.merge({"#{name}[move_to]" => 'higher'}),
476 url.merge({"#{name}[move_to]" => 'higher'}),
477 :method => method, :title => l(:label_sort_higher)) +
477 :method => method, :title => l(:label_sort_higher)) +
478 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
478 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
479 url.merge({"#{name}[move_to]" => 'lower'}),
479 url.merge({"#{name}[move_to]" => 'lower'}),
480 :method => method, :title => l(:label_sort_lower)) +
480 :method => method, :title => l(:label_sort_lower)) +
481 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
481 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
482 url.merge({"#{name}[move_to]" => 'lowest'}),
482 url.merge({"#{name}[move_to]" => 'lowest'}),
483 :method => method, :title => l(:label_sort_lowest))
483 :method => method, :title => l(:label_sort_lowest))
484 end
484 end
485
485
486 def breadcrumb(*args)
486 def breadcrumb(*args)
487 elements = args.flatten
487 elements = args.flatten
488 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
488 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
489 end
489 end
490
490
491 def other_formats_links(&block)
491 def other_formats_links(&block)
492 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
492 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
493 yield Redmine::Views::OtherFormatsBuilder.new(self)
493 yield Redmine::Views::OtherFormatsBuilder.new(self)
494 concat('</p>'.html_safe)
494 concat('</p>'.html_safe)
495 end
495 end
496
496
497 def page_header_title
497 def page_header_title
498 if @project.nil? || @project.new_record?
498 if @project.nil? || @project.new_record?
499 h(Setting.app_title)
499 h(Setting.app_title)
500 else
500 else
501 b = []
501 b = []
502 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
502 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
503 if ancestors.any?
503 if ancestors.any?
504 root = ancestors.shift
504 root = ancestors.shift
505 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
505 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
506 if ancestors.size > 2
506 if ancestors.size > 2
507 b << "\xe2\x80\xa6"
507 b << "\xe2\x80\xa6"
508 ancestors = ancestors[-2, 2]
508 ancestors = ancestors[-2, 2]
509 end
509 end
510 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
510 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
511 end
511 end
512 b << h(@project)
512 b << h(@project)
513 b.join(" \xc2\xbb ").html_safe
513 b.join(" \xc2\xbb ").html_safe
514 end
514 end
515 end
515 end
516
516
517 # Returns a h2 tag and sets the html title with the given arguments
517 # Returns a h2 tag and sets the html title with the given arguments
518 def title(*args)
518 def title(*args)
519 strings = args.map do |arg|
519 strings = args.map do |arg|
520 if arg.is_a?(Array) && arg.size >= 2
520 if arg.is_a?(Array) && arg.size >= 2
521 link_to(*arg)
521 link_to(*arg)
522 else
522 else
523 h(arg.to_s)
523 h(arg.to_s)
524 end
524 end
525 end
525 end
526 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
526 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
527 content_tag('h2', strings.join(' &#187; ').html_safe)
527 content_tag('h2', strings.join(' &#187; ').html_safe)
528 end
528 end
529
529
530 # Sets the html title
530 # Sets the html title
531 # Returns the html title when called without arguments
531 # Returns the html title when called without arguments
532 # Current project name and app_title and automatically appended
532 # Current project name and app_title and automatically appended
533 # Exemples:
533 # Exemples:
534 # html_title 'Foo', 'Bar'
534 # html_title 'Foo', 'Bar'
535 # html_title # => 'Foo - Bar - My Project - Redmine'
535 # html_title # => 'Foo - Bar - My Project - Redmine'
536 def html_title(*args)
536 def html_title(*args)
537 if args.empty?
537 if args.empty?
538 title = @html_title || []
538 title = @html_title || []
539 title << @project.name if @project
539 title << @project.name if @project
540 title << Setting.app_title unless Setting.app_title == title.last
540 title << Setting.app_title unless Setting.app_title == title.last
541 title.reject(&:blank?).join(' - ')
541 title.reject(&:blank?).join(' - ')
542 else
542 else
543 @html_title ||= []
543 @html_title ||= []
544 @html_title += args
544 @html_title += args
545 end
545 end
546 end
546 end
547
547
548 # Returns the theme, controller name, and action as css classes for the
548 # Returns the theme, controller name, and action as css classes for the
549 # HTML body.
549 # HTML body.
550 def body_css_classes
550 def body_css_classes
551 css = []
551 css = []
552 if theme = Redmine::Themes.theme(Setting.ui_theme)
552 if theme = Redmine::Themes.theme(Setting.ui_theme)
553 css << 'theme-' + theme.name
553 css << 'theme-' + theme.name
554 end
554 end
555
555
556 css << 'project-' + @project.identifier if @project && @project.identifier.present?
556 css << 'project-' + @project.identifier if @project && @project.identifier.present?
557 css << 'controller-' + controller_name
557 css << 'controller-' + controller_name
558 css << 'action-' + action_name
558 css << 'action-' + action_name
559 css.join(' ')
559 css.join(' ')
560 end
560 end
561
561
562 def accesskey(s)
562 def accesskey(s)
563 @used_accesskeys ||= []
563 @used_accesskeys ||= []
564 key = Redmine::AccessKeys.key_for(s)
564 key = Redmine::AccessKeys.key_for(s)
565 return nil if @used_accesskeys.include?(key)
565 return nil if @used_accesskeys.include?(key)
566 @used_accesskeys << key
566 @used_accesskeys << key
567 key
567 key
568 end
568 end
569
569
570 # Formats text according to system settings.
570 # Formats text according to system settings.
571 # 2 ways to call this method:
571 # 2 ways to call this method:
572 # * with a String: textilizable(text, options)
572 # * with a String: textilizable(text, options)
573 # * with an object and one of its attribute: textilizable(issue, :description, options)
573 # * with an object and one of its attribute: textilizable(issue, :description, options)
574 def textilizable(*args)
574 def textilizable(*args)
575 options = args.last.is_a?(Hash) ? args.pop : {}
575 options = args.last.is_a?(Hash) ? args.pop : {}
576 case args.size
576 case args.size
577 when 1
577 when 1
578 obj = options[:object]
578 obj = options[:object]
579 text = args.shift
579 text = args.shift
580 when 2
580 when 2
581 obj = args.shift
581 obj = args.shift
582 attr = args.shift
582 attr = args.shift
583 text = obj.send(attr).to_s
583 text = obj.send(attr).to_s
584 else
584 else
585 raise ArgumentError, 'invalid arguments to textilizable'
585 raise ArgumentError, 'invalid arguments to textilizable'
586 end
586 end
587 return '' if text.blank?
587 return '' if text.blank?
588 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
588 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
589 only_path = options.delete(:only_path) == false ? false : true
589 only_path = options.delete(:only_path) == false ? false : true
590
590
591 text = text.dup
591 text = text.dup
592 macros = catch_macros(text)
592 macros = catch_macros(text)
593 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
593 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
594
594
595 @parsed_headings = []
595 @parsed_headings = []
596 @heading_anchors = {}
596 @heading_anchors = {}
597 @current_section = 0 if options[:edit_section_links]
597 @current_section = 0 if options[:edit_section_links]
598
598
599 parse_sections(text, project, obj, attr, only_path, options)
599 parse_sections(text, project, obj, attr, only_path, options)
600 text = parse_non_pre_blocks(text, obj, macros) do |text|
600 text = parse_non_pre_blocks(text, obj, macros) do |text|
601 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
601 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
602 send method_name, text, project, obj, attr, only_path, options
602 send method_name, text, project, obj, attr, only_path, options
603 end
603 end
604 end
604 end
605 parse_headings(text, project, obj, attr, only_path, options)
605 parse_headings(text, project, obj, attr, only_path, options)
606
606
607 if @parsed_headings.any?
607 if @parsed_headings.any?
608 replace_toc(text, @parsed_headings)
608 replace_toc(text, @parsed_headings)
609 end
609 end
610
610
611 text.html_safe
611 text.html_safe
612 end
612 end
613
613
614 def parse_non_pre_blocks(text, obj, macros)
614 def parse_non_pre_blocks(text, obj, macros)
615 s = StringScanner.new(text)
615 s = StringScanner.new(text)
616 tags = []
616 tags = []
617 parsed = ''
617 parsed = ''
618 while !s.eos?
618 while !s.eos?
619 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
619 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
620 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
620 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
621 if tags.empty?
621 if tags.empty?
622 yield text
622 yield text
623 inject_macros(text, obj, macros) if macros.any?
623 inject_macros(text, obj, macros) if macros.any?
624 else
624 else
625 inject_macros(text, obj, macros, false) if macros.any?
625 inject_macros(text, obj, macros, false) if macros.any?
626 end
626 end
627 parsed << text
627 parsed << text
628 if tag
628 if tag
629 if closing
629 if closing
630 if tags.last == tag.downcase
630 if tags.last == tag.downcase
631 tags.pop
631 tags.pop
632 end
632 end
633 else
633 else
634 tags << tag.downcase
634 tags << tag.downcase
635 end
635 end
636 parsed << full_tag
636 parsed << full_tag
637 end
637 end
638 end
638 end
639 # Close any non closing tags
639 # Close any non closing tags
640 while tag = tags.pop
640 while tag = tags.pop
641 parsed << "</#{tag}>"
641 parsed << "</#{tag}>"
642 end
642 end
643 parsed
643 parsed
644 end
644 end
645
645
646 def parse_inline_attachments(text, project, obj, attr, only_path, options)
646 def parse_inline_attachments(text, project, obj, attr, only_path, options)
647 # when using an image link, try to use an attachment, if possible
647 # when using an image link, try to use an attachment, if possible
648 attachments = options[:attachments] || []
648 attachments = options[:attachments] || []
649 attachments += obj.attachments if obj.respond_to?(:attachments)
649 attachments += obj.attachments if obj.respond_to?(:attachments)
650 if attachments.present?
650 if attachments.present?
651 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
651 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
652 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
652 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
653 # search for the picture in attachments
653 # search for the picture in attachments
654 if found = Attachment.latest_attach(attachments, filename)
654 if found = Attachment.latest_attach(attachments, filename)
655 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
655 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
656 desc = found.description.to_s.gsub('"', '')
656 desc = found.description.to_s.gsub('"', '')
657 if !desc.blank? && alttext.blank?
657 if !desc.blank? && alttext.blank?
658 alt = " title=\"#{desc}\" alt=\"#{desc}\""
658 alt = " title=\"#{desc}\" alt=\"#{desc}\""
659 end
659 end
660 "src=\"#{image_url}\"#{alt}"
660 "src=\"#{image_url}\"#{alt}"
661 else
661 else
662 m
662 m
663 end
663 end
664 end
664 end
665 end
665 end
666 end
666 end
667
667
668 # Wiki links
668 # Wiki links
669 #
669 #
670 # Examples:
670 # Examples:
671 # [[mypage]]
671 # [[mypage]]
672 # [[mypage|mytext]]
672 # [[mypage|mytext]]
673 # wiki links can refer other project wikis, using project name or identifier:
673 # wiki links can refer other project wikis, using project name or identifier:
674 # [[project:]] -> wiki starting page
674 # [[project:]] -> wiki starting page
675 # [[project:|mytext]]
675 # [[project:|mytext]]
676 # [[project:mypage]]
676 # [[project:mypage]]
677 # [[project:mypage|mytext]]
677 # [[project:mypage|mytext]]
678 def parse_wiki_links(text, project, obj, attr, only_path, options)
678 def parse_wiki_links(text, project, obj, attr, only_path, options)
679 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
679 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
680 link_project = project
680 link_project = project
681 esc, all, page, title = $1, $2, $3, $5
681 esc, all, page, title = $1, $2, $3, $5
682 if esc.nil?
682 if esc.nil?
683 if page =~ /^([^\:]+)\:(.*)$/
683 if page =~ /^([^\:]+)\:(.*)$/
684 identifier, page = $1, $2
684 identifier, page = $1, $2
685 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
685 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
686 title ||= identifier if page.blank?
686 title ||= identifier if page.blank?
687 end
687 end
688
688
689 if link_project && link_project.wiki
689 if link_project && link_project.wiki
690 # extract anchor
690 # extract anchor
691 anchor = nil
691 anchor = nil
692 if page =~ /^(.+?)\#(.+)$/
692 if page =~ /^(.+?)\#(.+)$/
693 page, anchor = $1, $2
693 page, anchor = $1, $2
694 end
694 end
695 anchor = sanitize_anchor_name(anchor) if anchor.present?
695 anchor = sanitize_anchor_name(anchor) if anchor.present?
696 # check if page exists
696 # check if page exists
697 wiki_page = link_project.wiki.find_page(page)
697 wiki_page = link_project.wiki.find_page(page)
698 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
698 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
699 "##{anchor}"
699 "##{anchor}"
700 else
700 else
701 case options[:wiki_links]
701 case options[:wiki_links]
702 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
702 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
703 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
703 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
704 else
704 else
705 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
705 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
706 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
706 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
707 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
707 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
708 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
708 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
709 end
709 end
710 end
710 end
711 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
711 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
712 else
712 else
713 # project or wiki doesn't exist
713 # project or wiki doesn't exist
714 all
714 all
715 end
715 end
716 else
716 else
717 all
717 all
718 end
718 end
719 end
719 end
720 end
720 end
721
721
722 # Redmine links
722 # Redmine links
723 #
723 #
724 # Examples:
724 # Examples:
725 # Issues:
725 # Issues:
726 # #52 -> Link to issue #52
726 # #52 -> Link to issue #52
727 # Changesets:
727 # Changesets:
728 # r52 -> Link to revision 52
728 # r52 -> Link to revision 52
729 # commit:a85130f -> Link to scmid starting with a85130f
729 # commit:a85130f -> Link to scmid starting with a85130f
730 # Documents:
730 # Documents:
731 # document#17 -> Link to document with id 17
731 # document#17 -> Link to document with id 17
732 # document:Greetings -> Link to the document with title "Greetings"
732 # document:Greetings -> Link to the document with title "Greetings"
733 # document:"Some document" -> Link to the document with title "Some document"
733 # document:"Some document" -> Link to the document with title "Some document"
734 # Versions:
734 # Versions:
735 # version#3 -> Link to version with id 3
735 # version#3 -> Link to version with id 3
736 # version:1.0.0 -> Link to version named "1.0.0"
736 # version:1.0.0 -> Link to version named "1.0.0"
737 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
737 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
738 # Attachments:
738 # Attachments:
739 # attachment:file.zip -> Link to the attachment of the current object named file.zip
739 # attachment:file.zip -> Link to the attachment of the current object named file.zip
740 # Source files:
740 # Source files:
741 # source:some/file -> Link to the file located at /some/file in the project's repository
741 # source:some/file -> Link to the file located at /some/file in the project's repository
742 # source:some/file@52 -> Link to the file's revision 52
742 # source:some/file@52 -> Link to the file's revision 52
743 # source:some/file#L120 -> Link to line 120 of the file
743 # source:some/file#L120 -> Link to line 120 of the file
744 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
744 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
745 # export:some/file -> Force the download of the file
745 # export:some/file -> Force the download of the file
746 # Forum messages:
746 # Forum messages:
747 # message#1218 -> Link to message with id 1218
747 # message#1218 -> Link to message with id 1218
748 # Projects:
748 # Projects:
749 # project:someproject -> Link to project named "someproject"
749 # project:someproject -> Link to project named "someproject"
750 # project#3 -> Link to project with id 3
750 # project#3 -> Link to project with id 3
751 #
751 #
752 # Links can refer other objects from other projects, using project identifier:
752 # Links can refer other objects from other projects, using project identifier:
753 # identifier:r52
753 # identifier:r52
754 # identifier:document:"Some document"
754 # identifier:document:"Some document"
755 # identifier:version:1.0.0
755 # identifier:version:1.0.0
756 # identifier:source:some/file
756 # identifier:source:some/file
757 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
757 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
758 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
758 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-_]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-_]+)\|)?(r)))((\d+)((#note)?-(\d+))?)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
759 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
759 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier, comment_suffix, comment_id = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $18, $14 || $19, $15, $17
760 link = nil
760 link = nil
761 project = default_project
761 project = default_project
762 if project_identifier
762 if project_identifier
763 project = Project.visible.find_by_identifier(project_identifier)
763 project = Project.visible.find_by_identifier(project_identifier)
764 end
764 end
765 if esc.nil?
765 if esc.nil?
766 if prefix.nil? && sep == 'r'
766 if prefix.nil? && sep == 'r'
767 if project
767 if project
768 repository = nil
768 repository = nil
769 if repo_identifier
769 if repo_identifier
770 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
770 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
771 else
771 else
772 repository = project.repository
772 repository = project.repository
773 end
773 end
774 # project.changesets.visible raises an SQL error because of a double join on repositories
774 # project.changesets.visible raises an SQL error because of a double join on repositories
775 if repository &&
775 if repository &&
776 (changeset = Changeset.visible.
776 (changeset = Changeset.visible.
777 find_by_repository_id_and_revision(repository.id, identifier))
777 find_by_repository_id_and_revision(repository.id, identifier))
778 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
778 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
779 {:only_path => only_path, :controller => 'repositories',
779 {:only_path => only_path, :controller => 'repositories',
780 :action => 'revision', :id => project,
780 :action => 'revision', :id => project,
781 :repository_id => repository.identifier_param,
781 :repository_id => repository.identifier_param,
782 :rev => changeset.revision},
782 :rev => changeset.revision},
783 :class => 'changeset',
783 :class => 'changeset',
784 :title => truncate_single_line_raw(changeset.comments, 100))
784 :title => truncate_single_line_raw(changeset.comments, 100))
785 end
785 end
786 end
786 end
787 elsif sep == '#'
787 elsif sep == '#'
788 oid = identifier.to_i
788 oid = identifier.to_i
789 case prefix
789 case prefix
790 when nil
790 when nil
791 if oid.to_s == identifier &&
791 if oid.to_s == identifier &&
792 issue = Issue.visible.includes(:status).find_by_id(oid)
792 issue = Issue.visible.includes(:status).find_by_id(oid)
793 anchor = comment_id ? "note-#{comment_id}" : nil
793 anchor = comment_id ? "note-#{comment_id}" : nil
794 link = link_to(h("##{oid}#{comment_suffix}"),
794 link = link_to(h("##{oid}#{comment_suffix}"),
795 {:only_path => only_path, :controller => 'issues',
795 {:only_path => only_path, :controller => 'issues',
796 :action => 'show', :id => oid, :anchor => anchor},
796 :action => 'show', :id => oid, :anchor => anchor},
797 :class => issue.css_classes,
797 :class => issue.css_classes,
798 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
798 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
799 end
799 end
800 when 'document'
800 when 'document'
801 if document = Document.visible.find_by_id(oid)
801 if document = Document.visible.find_by_id(oid)
802 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
802 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
803 :class => 'document'
803 :class => 'document'
804 end
804 end
805 when 'version'
805 when 'version'
806 if version = Version.visible.find_by_id(oid)
806 if version = Version.visible.find_by_id(oid)
807 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
807 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
808 :class => 'version'
808 :class => 'version'
809 end
809 end
810 when 'message'
810 when 'message'
811 if message = Message.visible.includes(:parent).find_by_id(oid)
811 if message = Message.visible.includes(:parent).find_by_id(oid)
812 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
812 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
813 end
813 end
814 when 'forum'
814 when 'forum'
815 if board = Board.visible.find_by_id(oid)
815 if board = Board.visible.find_by_id(oid)
816 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
816 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
817 :class => 'board'
817 :class => 'board'
818 end
818 end
819 when 'news'
819 when 'news'
820 if news = News.visible.find_by_id(oid)
820 if news = News.visible.find_by_id(oid)
821 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
821 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
822 :class => 'news'
822 :class => 'news'
823 end
823 end
824 when 'project'
824 when 'project'
825 if p = Project.visible.find_by_id(oid)
825 if p = Project.visible.find_by_id(oid)
826 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
826 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
827 end
827 end
828 end
828 end
829 elsif sep == ':'
829 elsif sep == ':'
830 # removes the double quotes if any
830 # removes the double quotes if any
831 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
831 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
832 name = CGI.unescapeHTML(name)
832 name = CGI.unescapeHTML(name)
833 case prefix
833 case prefix
834 when 'document'
834 when 'document'
835 if project && document = project.documents.visible.find_by_title(name)
835 if project && document = project.documents.visible.find_by_title(name)
836 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
836 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
837 :class => 'document'
837 :class => 'document'
838 end
838 end
839 when 'version'
839 when 'version'
840 if project && version = project.versions.visible.find_by_name(name)
840 if project && version = project.versions.visible.find_by_name(name)
841 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
841 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
842 :class => 'version'
842 :class => 'version'
843 end
843 end
844 when 'forum'
844 when 'forum'
845 if project && board = project.boards.visible.find_by_name(name)
845 if project && board = project.boards.visible.find_by_name(name)
846 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
846 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
847 :class => 'board'
847 :class => 'board'
848 end
848 end
849 when 'news'
849 when 'news'
850 if project && news = project.news.visible.find_by_title(name)
850 if project && news = project.news.visible.find_by_title(name)
851 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
851 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
852 :class => 'news'
852 :class => 'news'
853 end
853 end
854 when 'commit', 'source', 'export'
854 when 'commit', 'source', 'export'
855 if project
855 if project
856 repository = nil
856 repository = nil
857 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
857 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
858 repo_prefix, repo_identifier, name = $1, $2, $3
858 repo_prefix, repo_identifier, name = $1, $2, $3
859 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
859 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
860 else
860 else
861 repository = project.repository
861 repository = project.repository
862 end
862 end
863 if prefix == 'commit'
863 if prefix == 'commit'
864 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
864 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
865 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
865 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
866 :class => 'changeset',
866 :class => 'changeset',
867 :title => truncate_single_line_raw(changeset.comments, 100)
867 :title => truncate_single_line_raw(changeset.comments, 100)
868 end
868 end
869 else
869 else
870 if repository && User.current.allowed_to?(:browse_repository, project)
870 if repository && User.current.allowed_to?(:browse_repository, project)
871 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
871 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
872 path, rev, anchor = $1, $3, $5
872 path, rev, anchor = $1, $3, $5
873 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
873 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => (prefix == 'export' ? 'raw' : 'entry'), :id => project, :repository_id => repository.identifier_param,
874 :path => to_path_param(path),
874 :path => to_path_param(path),
875 :rev => rev,
875 :rev => rev,
876 :anchor => anchor},
876 :anchor => anchor},
877 :class => (prefix == 'export' ? 'source download' : 'source')
877 :class => (prefix == 'export' ? 'source download' : 'source')
878 end
878 end
879 end
879 end
880 repo_prefix = nil
880 repo_prefix = nil
881 end
881 end
882 when 'attachment'
882 when 'attachment'
883 attachments = options[:attachments] || []
883 attachments = options[:attachments] || []
884 attachments += obj.attachments if obj.respond_to?(:attachments)
884 attachments += obj.attachments if obj.respond_to?(:attachments)
885 if attachments && attachment = Attachment.latest_attach(attachments, name)
885 if attachments && attachment = Attachment.latest_attach(attachments, name)
886 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
886 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
887 end
887 end
888 when 'project'
888 when 'project'
889 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
889 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
890 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
890 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
891 end
891 end
892 end
892 end
893 end
893 end
894 end
894 end
895 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
895 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
896 end
896 end
897 end
897 end
898
898
899 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
899 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
900
900
901 def parse_sections(text, project, obj, attr, only_path, options)
901 def parse_sections(text, project, obj, attr, only_path, options)
902 return unless options[:edit_section_links]
902 return unless options[:edit_section_links]
903 text.gsub!(HEADING_RE) do
903 text.gsub!(HEADING_RE) do
904 heading = $1
904 heading = $1
905 @current_section += 1
905 @current_section += 1
906 if @current_section > 1
906 if @current_section > 1
907 content_tag('div',
907 content_tag('div',
908 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
908 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
909 :class => 'contextual',
909 :class => 'contextual',
910 :title => l(:button_edit_section),
910 :title => l(:button_edit_section),
911 :id => "section-#{@current_section}") + heading.html_safe
911 :id => "section-#{@current_section}") + heading.html_safe
912 else
912 else
913 heading
913 heading
914 end
914 end
915 end
915 end
916 end
916 end
917
917
918 # Headings and TOC
918 # Headings and TOC
919 # Adds ids and links to headings unless options[:headings] is set to false
919 # Adds ids and links to headings unless options[:headings] is set to false
920 def parse_headings(text, project, obj, attr, only_path, options)
920 def parse_headings(text, project, obj, attr, only_path, options)
921 return if options[:headings] == false
921 return if options[:headings] == false
922
922
923 text.gsub!(HEADING_RE) do
923 text.gsub!(HEADING_RE) do
924 level, attrs, content = $2.to_i, $3, $4
924 level, attrs, content = $2.to_i, $3, $4
925 item = strip_tags(content).strip
925 item = strip_tags(content).strip
926 anchor = sanitize_anchor_name(item)
926 anchor = sanitize_anchor_name(item)
927 # used for single-file wiki export
927 # used for single-file wiki export
928 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
928 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
929 @heading_anchors[anchor] ||= 0
929 @heading_anchors[anchor] ||= 0
930 idx = (@heading_anchors[anchor] += 1)
930 idx = (@heading_anchors[anchor] += 1)
931 if idx > 1
931 if idx > 1
932 anchor = "#{anchor}-#{idx}"
932 anchor = "#{anchor}-#{idx}"
933 end
933 end
934 @parsed_headings << [level, anchor, item]
934 @parsed_headings << [level, anchor, item]
935 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
935 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
936 end
936 end
937 end
937 end
938
938
939 MACROS_RE = /(
939 MACROS_RE = /(
940 (!)? # escaping
940 (!)? # escaping
941 (
941 (
942 \{\{ # opening tag
942 \{\{ # opening tag
943 ([\w]+) # macro name
943 ([\w]+) # macro name
944 (\(([^\n\r]*?)\))? # optional arguments
944 (\(([^\n\r]*?)\))? # optional arguments
945 ([\n\r].*?[\n\r])? # optional block of text
945 ([\n\r].*?[\n\r])? # optional block of text
946 \}\} # closing tag
946 \}\} # closing tag
947 )
947 )
948 )/mx unless const_defined?(:MACROS_RE)
948 )/mx unless const_defined?(:MACROS_RE)
949
949
950 MACRO_SUB_RE = /(
950 MACRO_SUB_RE = /(
951 \{\{
951 \{\{
952 macro\((\d+)\)
952 macro\((\d+)\)
953 \}\}
953 \}\}
954 )/x unless const_defined?(:MACRO_SUB_RE)
954 )/x unless const_defined?(:MACRO_SUB_RE)
955
955
956 # Extracts macros from text
956 # Extracts macros from text
957 def catch_macros(text)
957 def catch_macros(text)
958 macros = {}
958 macros = {}
959 text.gsub!(MACROS_RE) do
959 text.gsub!(MACROS_RE) do
960 all, macro = $1, $4.downcase
960 all, macro = $1, $4.downcase
961 if macro_exists?(macro) || all =~ MACRO_SUB_RE
961 if macro_exists?(macro) || all =~ MACRO_SUB_RE
962 index = macros.size
962 index = macros.size
963 macros[index] = all
963 macros[index] = all
964 "{{macro(#{index})}}"
964 "{{macro(#{index})}}"
965 else
965 else
966 all
966 all
967 end
967 end
968 end
968 end
969 macros
969 macros
970 end
970 end
971
971
972 # Executes and replaces macros in text
972 # Executes and replaces macros in text
973 def inject_macros(text, obj, macros, execute=true)
973 def inject_macros(text, obj, macros, execute=true)
974 text.gsub!(MACRO_SUB_RE) do
974 text.gsub!(MACRO_SUB_RE) do
975 all, index = $1, $2.to_i
975 all, index = $1, $2.to_i
976 orig = macros.delete(index)
976 orig = macros.delete(index)
977 if execute && orig && orig =~ MACROS_RE
977 if execute && orig && orig =~ MACROS_RE
978 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
978 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
979 if esc.nil?
979 if esc.nil?
980 h(exec_macro(macro, obj, args, block) || all)
980 h(exec_macro(macro, obj, args, block) || all)
981 else
981 else
982 h(all)
982 h(all)
983 end
983 end
984 elsif orig
984 elsif orig
985 h(orig)
985 h(orig)
986 else
986 else
987 h(all)
987 h(all)
988 end
988 end
989 end
989 end
990 end
990 end
991
991
992 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
992 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
993
993
994 # Renders the TOC with given headings
994 # Renders the TOC with given headings
995 def replace_toc(text, headings)
995 def replace_toc(text, headings)
996 text.gsub!(TOC_RE) do
996 text.gsub!(TOC_RE) do
997 left_align, right_align = $2, $3
997 left_align, right_align = $2, $3
998 # Keep only the 4 first levels
998 # Keep only the 4 first levels
999 headings = headings.select{|level, anchor, item| level <= 4}
999 headings = headings.select{|level, anchor, item| level <= 4}
1000 if headings.empty?
1000 if headings.empty?
1001 ''
1001 ''
1002 else
1002 else
1003 div_class = 'toc'
1003 div_class = 'toc'
1004 div_class << ' right' if right_align
1004 div_class << ' right' if right_align
1005 div_class << ' left' if left_align
1005 div_class << ' left' if left_align
1006 out = "<ul class=\"#{div_class}\"><li>"
1006 out = "<ul class=\"#{div_class}\"><li>"
1007 root = headings.map(&:first).min
1007 root = headings.map(&:first).min
1008 current = root
1008 current = root
1009 started = false
1009 started = false
1010 headings.each do |level, anchor, item|
1010 headings.each do |level, anchor, item|
1011 if level > current
1011 if level > current
1012 out << '<ul><li>' * (level - current)
1012 out << '<ul><li>' * (level - current)
1013 elsif level < current
1013 elsif level < current
1014 out << "</li></ul>\n" * (current - level) + "</li><li>"
1014 out << "</li></ul>\n" * (current - level) + "</li><li>"
1015 elsif started
1015 elsif started
1016 out << '</li><li>'
1016 out << '</li><li>'
1017 end
1017 end
1018 out << "<a href=\"##{anchor}\">#{item}</a>"
1018 out << "<a href=\"##{anchor}\">#{item}</a>"
1019 current = level
1019 current = level
1020 started = true
1020 started = true
1021 end
1021 end
1022 out << '</li></ul>' * (current - root)
1022 out << '</li></ul>' * (current - root)
1023 out << '</li></ul>'
1023 out << '</li></ul>'
1024 end
1024 end
1025 end
1025 end
1026 end
1026 end
1027
1027
1028 # Same as Rails' simple_format helper without using paragraphs
1028 # Same as Rails' simple_format helper without using paragraphs
1029 def simple_format_without_paragraph(text)
1029 def simple_format_without_paragraph(text)
1030 text.to_s.
1030 text.to_s.
1031 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1031 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1032 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1032 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1033 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1033 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1034 html_safe
1034 html_safe
1035 end
1035 end
1036
1036
1037 def lang_options_for_select(blank=true)
1037 def lang_options_for_select(blank=true)
1038 (blank ? [["(auto)", ""]] : []) + languages_options
1038 (blank ? [["(auto)", ""]] : []) + languages_options
1039 end
1039 end
1040
1040
1041 def label_tag_for(name, option_tags = nil, options = {})
1041 def label_tag_for(name, option_tags = nil, options = {})
1042 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1042 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1043 content_tag("label", label_text)
1043 content_tag("label", label_text)
1044 end
1044 end
1045
1045
1046 def labelled_form_for(*args, &proc)
1046 def labelled_form_for(*args, &proc)
1047 args << {} unless args.last.is_a?(Hash)
1047 args << {} unless args.last.is_a?(Hash)
1048 options = args.last
1048 options = args.last
1049 if args.first.is_a?(Symbol)
1049 if args.first.is_a?(Symbol)
1050 options.merge!(:as => args.shift)
1050 options.merge!(:as => args.shift)
1051 end
1051 end
1052 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1052 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1053 form_for(*args, &proc)
1053 form_for(*args, &proc)
1054 end
1054 end
1055
1055
1056 def labelled_fields_for(*args, &proc)
1056 def labelled_fields_for(*args, &proc)
1057 args << {} unless args.last.is_a?(Hash)
1057 args << {} unless args.last.is_a?(Hash)
1058 options = args.last
1058 options = args.last
1059 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1059 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1060 fields_for(*args, &proc)
1060 fields_for(*args, &proc)
1061 end
1061 end
1062
1062
1063 def labelled_remote_form_for(*args, &proc)
1063 def labelled_remote_form_for(*args, &proc)
1064 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1064 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1065 args << {} unless args.last.is_a?(Hash)
1065 args << {} unless args.last.is_a?(Hash)
1066 options = args.last
1066 options = args.last
1067 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1067 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1068 form_for(*args, &proc)
1068 form_for(*args, &proc)
1069 end
1069 end
1070
1070
1071 def error_messages_for(*objects)
1071 def error_messages_for(*objects)
1072 html = ""
1072 html = ""
1073 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1073 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1074 errors = objects.map {|o| o.errors.full_messages}.flatten
1074 errors = objects.map {|o| o.errors.full_messages}.flatten
1075 if errors.any?
1075 if errors.any?
1076 html << "<div id='errorExplanation'><ul>\n"
1076 html << "<div id='errorExplanation'><ul>\n"
1077 errors.each do |error|
1077 errors.each do |error|
1078 html << "<li>#{h error}</li>\n"
1078 html << "<li>#{h error}</li>\n"
1079 end
1079 end
1080 html << "</ul></div>\n"
1080 html << "</ul></div>\n"
1081 end
1081 end
1082 html.html_safe
1082 html.html_safe
1083 end
1083 end
1084
1084
1085 def delete_link(url, options={})
1085 def delete_link(url, options={})
1086 options = {
1086 options = {
1087 :method => :delete,
1087 :method => :delete,
1088 :data => {:confirm => l(:text_are_you_sure)},
1088 :data => {:confirm => l(:text_are_you_sure)},
1089 :class => 'icon icon-del'
1089 :class => 'icon icon-del'
1090 }.merge(options)
1090 }.merge(options)
1091
1091
1092 link_to l(:button_delete), url, options
1092 link_to l(:button_delete), url, options
1093 end
1093 end
1094
1094
1095 def preview_link(url, form, target='preview', options={})
1095 def preview_link(url, form, target='preview', options={})
1096 content_tag 'a', l(:label_preview), {
1096 content_tag 'a', l(:label_preview), {
1097 :href => "#",
1097 :href => "#",
1098 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1098 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1099 :accesskey => accesskey(:preview)
1099 :accesskey => accesskey(:preview)
1100 }.merge(options)
1100 }.merge(options)
1101 end
1101 end
1102
1102
1103 def link_to_function(name, function, html_options={})
1103 def link_to_function(name, function, html_options={})
1104 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1104 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1105 end
1105 end
1106
1106
1107 # Helper to render JSON in views
1107 # Helper to render JSON in views
1108 def raw_json(arg)
1108 def raw_json(arg)
1109 arg.to_json.to_s.gsub('/', '\/').html_safe
1109 arg.to_json.to_s.gsub('/', '\/').html_safe
1110 end
1110 end
1111
1111
1112 def back_url
1112 def back_url
1113 url = params[:back_url]
1113 url = params[:back_url]
1114 if url.nil? && referer = request.env['HTTP_REFERER']
1114 if url.nil? && referer = request.env['HTTP_REFERER']
1115 url = CGI.unescape(referer.to_s)
1115 url = CGI.unescape(referer.to_s)
1116 end
1116 end
1117 url
1117 url
1118 end
1118 end
1119
1119
1120 def back_url_hidden_field_tag
1120 def back_url_hidden_field_tag
1121 url = back_url
1121 url = back_url
1122 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1122 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1123 end
1123 end
1124
1124
1125 def check_all_links(form_name)
1125 def check_all_links(form_name)
1126 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1126 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1127 " | ".html_safe +
1127 " | ".html_safe +
1128 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1128 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1129 end
1129 end
1130
1130
1131 def progress_bar(pcts, options={})
1131 def progress_bar(pcts, options={})
1132 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1132 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1133 pcts = pcts.collect(&:round)
1133 pcts = pcts.collect(&:round)
1134 pcts[1] = pcts[1] - pcts[0]
1134 pcts[1] = pcts[1] - pcts[0]
1135 pcts << (100 - pcts[1] - pcts[0])
1135 pcts << (100 - pcts[1] - pcts[0])
1136 width = options[:width] || '100px;'
1136 width = options[:width] || '100px;'
1137 legend = options[:legend] || ''
1137 legend = options[:legend] || ''
1138 content_tag('table',
1138 content_tag('table',
1139 content_tag('tr',
1139 content_tag('tr',
1140 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1140 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1141 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1141 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1142 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1142 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1143 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1143 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1144 content_tag('p', legend, :class => 'percent').html_safe
1144 content_tag('p', legend, :class => 'percent').html_safe
1145 end
1145 end
1146
1146
1147 def checked_image(checked=true)
1147 def checked_image(checked=true)
1148 if checked
1148 if checked
1149 image_tag 'toggle_check.png'
1149 image_tag 'toggle_check.png'
1150 end
1150 end
1151 end
1151 end
1152
1152
1153 def context_menu(url)
1153 def context_menu(url)
1154 unless @context_menu_included
1154 unless @context_menu_included
1155 content_for :header_tags do
1155 content_for :header_tags do
1156 javascript_include_tag('context_menu') +
1156 javascript_include_tag('context_menu') +
1157 stylesheet_link_tag('context_menu')
1157 stylesheet_link_tag('context_menu')
1158 end
1158 end
1159 if l(:direction) == 'rtl'
1159 if l(:direction) == 'rtl'
1160 content_for :header_tags do
1160 content_for :header_tags do
1161 stylesheet_link_tag('context_menu_rtl')
1161 stylesheet_link_tag('context_menu_rtl')
1162 end
1162 end
1163 end
1163 end
1164 @context_menu_included = true
1164 @context_menu_included = true
1165 end
1165 end
1166 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1166 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1167 end
1167 end
1168
1168
1169 def calendar_for(field_id)
1169 def calendar_for(field_id)
1170 include_calendar_headers_tags
1170 include_calendar_headers_tags
1171 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1171 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1172 end
1172 end
1173
1173
1174 def include_calendar_headers_tags
1174 def include_calendar_headers_tags
1175 unless @calendar_headers_tags_included
1175 unless @calendar_headers_tags_included
1176 tags = javascript_include_tag("datepicker")
1176 tags = javascript_include_tag("datepicker")
1177 @calendar_headers_tags_included = true
1177 @calendar_headers_tags_included = true
1178 content_for :header_tags do
1178 content_for :header_tags do
1179 start_of_week = Setting.start_of_week
1179 start_of_week = Setting.start_of_week
1180 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1180 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1181 # Redmine uses 1..7 (monday..sunday) in settings and locales
1181 # Redmine uses 1..7 (monday..sunday) in settings and locales
1182 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1182 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1183 start_of_week = start_of_week.to_i % 7
1183 start_of_week = start_of_week.to_i % 7
1184 tags << javascript_tag(
1184 tags << javascript_tag(
1185 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1185 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1186 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1186 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1187 path_to_image('/images/calendar.png') +
1187 path_to_image('/images/calendar.png') +
1188 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1188 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1189 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1189 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1190 "beforeShow: beforeShowDatePicker};")
1190 "beforeShow: beforeShowDatePicker};")
1191 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1191 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1192 unless jquery_locale == 'en'
1192 unless jquery_locale == 'en'
1193 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1193 tags << javascript_include_tag("i18n/jquery.ui.datepicker-#{jquery_locale}.js")
1194 end
1194 end
1195 tags
1195 tags
1196 end
1196 end
1197 end
1197 end
1198 end
1198 end
1199
1199
1200 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1200 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1201 # Examples:
1201 # Examples:
1202 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1202 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1203 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1203 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1204 #
1204 #
1205 def stylesheet_link_tag(*sources)
1205 def stylesheet_link_tag(*sources)
1206 options = sources.last.is_a?(Hash) ? sources.pop : {}
1206 options = sources.last.is_a?(Hash) ? sources.pop : {}
1207 plugin = options.delete(:plugin)
1207 plugin = options.delete(:plugin)
1208 sources = sources.map do |source|
1208 sources = sources.map do |source|
1209 if plugin
1209 if plugin
1210 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1210 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1211 elsif current_theme && current_theme.stylesheets.include?(source)
1211 elsif current_theme && current_theme.stylesheets.include?(source)
1212 current_theme.stylesheet_path(source)
1212 current_theme.stylesheet_path(source)
1213 else
1213 else
1214 source
1214 source
1215 end
1215 end
1216 end
1216 end
1217 super sources, options
1217 super sources, options
1218 end
1218 end
1219
1219
1220 # Overrides Rails' image_tag with themes and plugins support.
1220 # Overrides Rails' image_tag with themes and plugins support.
1221 # Examples:
1221 # Examples:
1222 # image_tag('image.png') # => picks image.png from the current theme or defaults
1222 # image_tag('image.png') # => picks image.png from the current theme or defaults
1223 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1223 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1224 #
1224 #
1225 def image_tag(source, options={})
1225 def image_tag(source, options={})
1226 if plugin = options.delete(:plugin)
1226 if plugin = options.delete(:plugin)
1227 source = "/plugin_assets/#{plugin}/images/#{source}"
1227 source = "/plugin_assets/#{plugin}/images/#{source}"
1228 elsif current_theme && current_theme.images.include?(source)
1228 elsif current_theme && current_theme.images.include?(source)
1229 source = current_theme.image_path(source)
1229 source = current_theme.image_path(source)
1230 end
1230 end
1231 super source, options
1231 super source, options
1232 end
1232 end
1233
1233
1234 # Overrides Rails' javascript_include_tag with plugins support
1234 # Overrides Rails' javascript_include_tag with plugins support
1235 # Examples:
1235 # Examples:
1236 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1236 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1237 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1237 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1238 #
1238 #
1239 def javascript_include_tag(*sources)
1239 def javascript_include_tag(*sources)
1240 options = sources.last.is_a?(Hash) ? sources.pop : {}
1240 options = sources.last.is_a?(Hash) ? sources.pop : {}
1241 if plugin = options.delete(:plugin)
1241 if plugin = options.delete(:plugin)
1242 sources = sources.map do |source|
1242 sources = sources.map do |source|
1243 if plugin
1243 if plugin
1244 "/plugin_assets/#{plugin}/javascripts/#{source}"
1244 "/plugin_assets/#{plugin}/javascripts/#{source}"
1245 else
1245 else
1246 source
1246 source
1247 end
1247 end
1248 end
1248 end
1249 end
1249 end
1250 super sources, options
1250 super sources, options
1251 end
1251 end
1252
1252
1253 # TODO: remove this in 2.5.0
1253 # TODO: remove this in 2.5.0
1254 def has_content?(name)
1254 def has_content?(name)
1255 content_for?(name)
1255 content_for?(name)
1256 end
1256 end
1257
1257
1258 def sidebar_content?
1258 def sidebar_content?
1259 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1259 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1260 end
1260 end
1261
1261
1262 def view_layouts_base_sidebar_hook_response
1262 def view_layouts_base_sidebar_hook_response
1263 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1263 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1264 end
1264 end
1265
1265
1266 def email_delivery_enabled?
1266 def email_delivery_enabled?
1267 !!ActionMailer::Base.perform_deliveries
1267 !!ActionMailer::Base.perform_deliveries
1268 end
1268 end
1269
1269
1270 # Returns the avatar image tag for the given +user+ if avatars are enabled
1270 # Returns the avatar image tag for the given +user+ if avatars are enabled
1271 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1271 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1272 def avatar(user, options = { })
1272 def avatar(user, options = { })
1273 if Setting.gravatar_enabled?
1273 if Setting.gravatar_enabled?
1274 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1274 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1275 email = nil
1275 email = nil
1276 if user.respond_to?(:mail)
1276 if user.respond_to?(:mail)
1277 email = user.mail
1277 email = user.mail
1278 elsif user.to_s =~ %r{<(.+?)>}
1278 elsif user.to_s =~ %r{<(.+?)>}
1279 email = $1
1279 email = $1
1280 end
1280 end
1281 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1281 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1282 else
1282 else
1283 ''
1283 ''
1284 end
1284 end
1285 end
1285 end
1286
1286
1287 def sanitize_anchor_name(anchor)
1287 def sanitize_anchor_name(anchor)
1288 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1288 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1289 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1289 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1290 else
1290 else
1291 # TODO: remove when ruby1.8 is no longer supported
1291 # TODO: remove when ruby1.8 is no longer supported
1292 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1292 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1293 end
1293 end
1294 end
1294 end
1295
1295
1296 # Returns the javascript tags that are included in the html layout head
1296 # Returns the javascript tags that are included in the html layout head
1297 def javascript_heads
1297 def javascript_heads
1298 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1298 tags = javascript_include_tag('jquery-1.8.3-ui-1.9.2-ujs-2.0.3', 'application')
1299 unless User.current.pref.warn_on_leaving_unsaved == '0'
1299 unless User.current.pref.warn_on_leaving_unsaved == '0'
1300 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1300 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1301 end
1301 end
1302 tags
1302 tags
1303 end
1303 end
1304
1304
1305 def favicon
1305 def favicon
1306 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1306 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1307 end
1307 end
1308
1308
1309 # Returns the path to the favicon
1309 # Returns the path to the favicon
1310 def favicon_path
1310 def favicon_path
1311 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1311 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1312 image_path(icon)
1312 image_path(icon)
1313 end
1313 end
1314
1314
1315 # Returns the full URL to the favicon
1315 # Returns the full URL to the favicon
1316 def favicon_url
1316 def favicon_url
1317 # TODO: use #image_url introduced in Rails4
1317 # TODO: use #image_url introduced in Rails4
1318 path = favicon_path
1318 path = favicon_path
1319 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1319 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1320 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1320 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1321 end
1321 end
1322
1322
1323 def robot_exclusion_tag
1323 def robot_exclusion_tag
1324 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1324 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1325 end
1325 end
1326
1326
1327 # Returns true if arg is expected in the API response
1327 # Returns true if arg is expected in the API response
1328 def include_in_api_response?(arg)
1328 def include_in_api_response?(arg)
1329 unless @included_in_api_response
1329 unless @included_in_api_response
1330 param = params[:include]
1330 param = params[:include]
1331 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1331 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1332 @included_in_api_response.collect!(&:strip)
1332 @included_in_api_response.collect!(&:strip)
1333 end
1333 end
1334 @included_in_api_response.include?(arg.to_s)
1334 @included_in_api_response.include?(arg.to_s)
1335 end
1335 end
1336
1336
1337 # Returns options or nil if nometa param or X-Redmine-Nometa header
1337 # Returns options or nil if nometa param or X-Redmine-Nometa header
1338 # was set in the request
1338 # was set in the request
1339 def api_meta(options)
1339 def api_meta(options)
1340 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1340 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1341 # compatibility mode for activeresource clients that raise
1341 # compatibility mode for activeresource clients that raise
1342 # an error when deserializing an array with attributes
1342 # an error when deserializing an array with attributes
1343 nil
1343 nil
1344 else
1344 else
1345 options
1345 options
1346 end
1346 end
1347 end
1347 end
1348
1348
1349 private
1349 private
1350
1350
1351 def wiki_helper
1351 def wiki_helper
1352 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1352 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1353 extend helper
1353 extend helper
1354 return self
1354 return self
1355 end
1355 end
1356
1356
1357 def link_to_content_update(text, url_params = {}, html_options = {})
1357 def link_to_content_update(text, url_params = {}, html_options = {})
1358 link_to(text, url_params, html_options)
1358 link_to(text, url_params, html_options)
1359 end
1359 end
1360 end
1360 end
@@ -1,290 +1,295
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 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 class Version < ActiveRecord::Base
18 class Version < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 after_update :update_issues_from_sharing_change
20 after_update :update_issues_from_sharing_change
21 belongs_to :project
21 belongs_to :project
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
22 has_many :fixed_issues, :class_name => 'Issue', :foreign_key => 'fixed_version_id', :dependent => :nullify
23 acts_as_customizable
23 acts_as_customizable
24 acts_as_attachable :view_permission => :view_files,
24 acts_as_attachable :view_permission => :view_files,
25 :delete_permission => :manage_files
25 :delete_permission => :manage_files
26
26
27 VERSION_STATUSES = %w(open locked closed)
27 VERSION_STATUSES = %w(open locked closed)
28 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
28 VERSION_SHARINGS = %w(none descendants hierarchy tree system)
29
29
30 validates_presence_of :name
30 validates_presence_of :name
31 validates_uniqueness_of :name, :scope => [:project_id]
31 validates_uniqueness_of :name, :scope => [:project_id]
32 validates_length_of :name, :maximum => 60
32 validates_length_of :name, :maximum => 60
33 validates :effective_date, :date => true
33 validates :effective_date, :date => true
34 validates_inclusion_of :status, :in => VERSION_STATUSES
34 validates_inclusion_of :status, :in => VERSION_STATUSES
35 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
35 validates_inclusion_of :sharing, :in => VERSION_SHARINGS
36
36
37 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
37 scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
38 scope :open, lambda { where(:status => 'open') }
38 scope :open, lambda { where(:status => 'open') }
39 scope :visible, lambda {|*args|
39 scope :visible, lambda {|*args|
40 includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
40 includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues))
41 }
41 }
42
42
43 safe_attributes 'name',
43 safe_attributes 'name',
44 'description',
44 'description',
45 'effective_date',
45 'effective_date',
46 'due_date',
46 'due_date',
47 'wiki_page_title',
47 'wiki_page_title',
48 'status',
48 'status',
49 'sharing',
49 'sharing',
50 'custom_field_values',
50 'custom_field_values',
51 'custom_fields'
51 'custom_fields'
52
52
53 # Returns true if +user+ or current user is allowed to view the version
53 # Returns true if +user+ or current user is allowed to view the version
54 def visible?(user=User.current)
54 def visible?(user=User.current)
55 user.allowed_to?(:view_issues, self.project)
55 user.allowed_to?(:view_issues, self.project)
56 end
56 end
57
57
58 # Version files have same visibility as project files
58 # Version files have same visibility as project files
59 def attachments_visible?(*args)
59 def attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
60 project.present? && project.attachments_visible?(*args)
61 end
61 end
62
62
63 def start_date
63 def start_date
64 @start_date ||= fixed_issues.minimum('start_date')
64 @start_date ||= fixed_issues.minimum('start_date')
65 end
65 end
66
66
67 def due_date
67 def due_date
68 effective_date
68 effective_date
69 end
69 end
70
70
71 def due_date=(arg)
71 def due_date=(arg)
72 self.effective_date=(arg)
72 self.effective_date=(arg)
73 end
73 end
74
74
75 # Returns the total estimated time for this version
75 # Returns the total estimated time for this version
76 # (sum of leaves estimated_hours)
76 # (sum of leaves estimated_hours)
77 def estimated_hours
77 def estimated_hours
78 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
78 @estimated_hours ||= fixed_issues.leaves.sum(:estimated_hours).to_f
79 end
79 end
80
80
81 # Returns the total reported time for this version
81 # Returns the total reported time for this version
82 def spent_hours
82 def spent_hours
83 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
83 @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f
84 end
84 end
85
85
86 def closed?
86 def closed?
87 status == 'closed'
87 status == 'closed'
88 end
88 end
89
89
90 def open?
90 def open?
91 status == 'open'
91 status == 'open'
92 end
92 end
93
93
94 # Returns true if the version is completed: due date reached and no open issues
94 # Returns true if the version is completed: due date reached and no open issues
95 def completed?
95 def completed?
96 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
96 effective_date && (effective_date < Date.today) && (open_issues_count == 0)
97 end
97 end
98
98
99 def behind_schedule?
99 def behind_schedule?
100 if completed_percent == 100
100 if completed_percent == 100
101 return false
101 return false
102 elsif due_date && start_date
102 elsif due_date && start_date
103 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
103 done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
104 return done_date <= Date.today
104 return done_date <= Date.today
105 else
105 else
106 false # No issues so it's not late
106 false # No issues so it's not late
107 end
107 end
108 end
108 end
109
109
110 # Returns the completion percentage of this version based on the amount of open/closed issues
110 # Returns the completion percentage of this version based on the amount of open/closed issues
111 # and the time spent on the open issues.
111 # and the time spent on the open issues.
112 def completed_percent
112 def completed_percent
113 if issues_count == 0
113 if issues_count == 0
114 0
114 0
115 elsif open_issues_count == 0
115 elsif open_issues_count == 0
116 100
116 100
117 else
117 else
118 issues_progress(false) + issues_progress(true)
118 issues_progress(false) + issues_progress(true)
119 end
119 end
120 end
120 end
121
121
122 # TODO: remove in Redmine 3.0
122 # TODO: remove in Redmine 3.0
123 def completed_pourcent
123 def completed_pourcent
124 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
124 ActiveSupport::Deprecation.warn "Version#completed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #completed_percent instead."
125 completed_percent
125 completed_percent
126 end
126 end
127
127
128 # Returns the percentage of issues that have been marked as 'closed'.
128 # Returns the percentage of issues that have been marked as 'closed'.
129 def closed_percent
129 def closed_percent
130 if issues_count == 0
130 if issues_count == 0
131 0
131 0
132 else
132 else
133 issues_progress(false)
133 issues_progress(false)
134 end
134 end
135 end
135 end
136
136
137 # TODO: remove in Redmine 3.0
137 # TODO: remove in Redmine 3.0
138 def closed_pourcent
138 def closed_pourcent
139 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
139 ActiveSupport::Deprecation.warn "Version#closed_pourcent is deprecated and will be removed in Redmine 3.0. Please use #closed_percent instead."
140 closed_percent
140 closed_percent
141 end
141 end
142
142
143 # Returns true if the version is overdue: due date reached and some open issues
143 # Returns true if the version is overdue: due date reached and some open issues
144 def overdue?
144 def overdue?
145 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
145 effective_date && (effective_date < Date.today) && (open_issues_count > 0)
146 end
146 end
147
147
148 # Returns assigned issues count
148 # Returns assigned issues count
149 def issues_count
149 def issues_count
150 load_issue_counts
150 load_issue_counts
151 @issue_count
151 @issue_count
152 end
152 end
153
153
154 # Returns the total amount of open issues for this version.
154 # Returns the total amount of open issues for this version.
155 def open_issues_count
155 def open_issues_count
156 load_issue_counts
156 load_issue_counts
157 @open_issues_count
157 @open_issues_count
158 end
158 end
159
159
160 # Returns the total amount of closed issues for this version.
160 # Returns the total amount of closed issues for this version.
161 def closed_issues_count
161 def closed_issues_count
162 load_issue_counts
162 load_issue_counts
163 @closed_issues_count
163 @closed_issues_count
164 end
164 end
165
165
166 def wiki_page
166 def wiki_page
167 if project.wiki && !wiki_page_title.blank?
167 if project.wiki && !wiki_page_title.blank?
168 @wiki_page ||= project.wiki.find_page(wiki_page_title)
168 @wiki_page ||= project.wiki.find_page(wiki_page_title)
169 end
169 end
170 @wiki_page
170 @wiki_page
171 end
171 end
172
172
173 def to_s; name end
173 def to_s; name end
174
174
175 def to_s_with_project
175 def to_s_with_project
176 "#{project} - #{name}"
176 "#{project} - #{name}"
177 end
177 end
178
178
179 # Versions are sorted by effective_date and name
179 # Versions are sorted by effective_date and name
180 # Those with no effective_date are at the end, sorted by name
180 # Those with no effective_date are at the end, sorted by name
181 def <=>(version)
181 def <=>(version)
182 if self.effective_date
182 if self.effective_date
183 if version.effective_date
183 if version.effective_date
184 if self.effective_date == version.effective_date
184 if self.effective_date == version.effective_date
185 name == version.name ? id <=> version.id : name <=> version.name
185 name == version.name ? id <=> version.id : name <=> version.name
186 else
186 else
187 self.effective_date <=> version.effective_date
187 self.effective_date <=> version.effective_date
188 end
188 end
189 else
189 else
190 -1
190 -1
191 end
191 end
192 else
192 else
193 if version.effective_date
193 if version.effective_date
194 1
194 1
195 else
195 else
196 name == version.name ? id <=> version.id : name <=> version.name
196 name == version.name ? id <=> version.id : name <=> version.name
197 end
197 end
198 end
198 end
199 end
199 end
200
200
201 def self.fields_for_order_statement(table=nil)
201 def self.fields_for_order_statement(table=nil)
202 table ||= table_name
202 table ||= table_name
203 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
203 ["(CASE WHEN #{table}.effective_date IS NULL THEN 1 ELSE 0 END)", "#{table}.effective_date", "#{table}.name", "#{table}.id"]
204 end
204 end
205
205
206 scope :sorted, lambda { order(fields_for_order_statement) }
206 scope :sorted, lambda { order(fields_for_order_statement) }
207
207
208 # Returns the sharings that +user+ can set the version to
208 # Returns the sharings that +user+ can set the version to
209 def allowed_sharings(user = User.current)
209 def allowed_sharings(user = User.current)
210 VERSION_SHARINGS.select do |s|
210 VERSION_SHARINGS.select do |s|
211 if sharing == s
211 if sharing == s
212 true
212 true
213 else
213 else
214 case s
214 case s
215 when 'system'
215 when 'system'
216 # Only admin users can set a systemwide sharing
216 # Only admin users can set a systemwide sharing
217 user.admin?
217 user.admin?
218 when 'hierarchy', 'tree'
218 when 'hierarchy', 'tree'
219 # Only users allowed to manage versions of the root project can
219 # Only users allowed to manage versions of the root project can
220 # set sharing to hierarchy or tree
220 # set sharing to hierarchy or tree
221 project.nil? || user.allowed_to?(:manage_versions, project.root)
221 project.nil? || user.allowed_to?(:manage_versions, project.root)
222 else
222 else
223 true
223 true
224 end
224 end
225 end
225 end
226 end
226 end
227 end
227 end
228
228
229 # Returns true if the version is shared, otherwise false
230 def shared?
231 sharing != 'none'
232 end
233
229 private
234 private
230
235
231 def load_issue_counts
236 def load_issue_counts
232 unless @issue_count
237 unless @issue_count
233 @open_issues_count = 0
238 @open_issues_count = 0
234 @closed_issues_count = 0
239 @closed_issues_count = 0
235 fixed_issues.group(:status).count.each do |status, count|
240 fixed_issues.group(:status).count.each do |status, count|
236 if status.is_closed?
241 if status.is_closed?
237 @closed_issues_count += count
242 @closed_issues_count += count
238 else
243 else
239 @open_issues_count += count
244 @open_issues_count += count
240 end
245 end
241 end
246 end
242 @issue_count = @open_issues_count + @closed_issues_count
247 @issue_count = @open_issues_count + @closed_issues_count
243 end
248 end
244 end
249 end
245
250
246 # Update the issue's fixed versions. Used if a version's sharing changes.
251 # Update the issue's fixed versions. Used if a version's sharing changes.
247 def update_issues_from_sharing_change
252 def update_issues_from_sharing_change
248 if sharing_changed?
253 if sharing_changed?
249 if VERSION_SHARINGS.index(sharing_was).nil? ||
254 if VERSION_SHARINGS.index(sharing_was).nil? ||
250 VERSION_SHARINGS.index(sharing).nil? ||
255 VERSION_SHARINGS.index(sharing).nil? ||
251 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
256 VERSION_SHARINGS.index(sharing_was) > VERSION_SHARINGS.index(sharing)
252 Issue.update_versions_from_sharing_change self
257 Issue.update_versions_from_sharing_change self
253 end
258 end
254 end
259 end
255 end
260 end
256
261
257 # Returns the average estimated time of assigned issues
262 # Returns the average estimated time of assigned issues
258 # or 1 if no issue has an estimated time
263 # or 1 if no issue has an estimated time
259 # Used to weight unestimated issues in progress calculation
264 # Used to weight unestimated issues in progress calculation
260 def estimated_average
265 def estimated_average
261 if @estimated_average.nil?
266 if @estimated_average.nil?
262 average = fixed_issues.average(:estimated_hours).to_f
267 average = fixed_issues.average(:estimated_hours).to_f
263 if average == 0
268 if average == 0
264 average = 1
269 average = 1
265 end
270 end
266 @estimated_average = average
271 @estimated_average = average
267 end
272 end
268 @estimated_average
273 @estimated_average
269 end
274 end
270
275
271 # Returns the total progress of open or closed issues. The returned percentage takes into account
276 # Returns the total progress of open or closed issues. The returned percentage takes into account
272 # the amount of estimated time set for this version.
277 # the amount of estimated time set for this version.
273 #
278 #
274 # Examples:
279 # Examples:
275 # issues_progress(true) => returns the progress percentage for open issues.
280 # issues_progress(true) => returns the progress percentage for open issues.
276 # issues_progress(false) => returns the progress percentage for closed issues.
281 # issues_progress(false) => returns the progress percentage for closed issues.
277 def issues_progress(open)
282 def issues_progress(open)
278 @issues_progress ||= {}
283 @issues_progress ||= {}
279 @issues_progress[open] ||= begin
284 @issues_progress[open] ||= begin
280 progress = 0
285 progress = 0
281 if issues_count > 0
286 if issues_count > 0
282 ratio = open ? 'done_ratio' : 100
287 ratio = open ? 'done_ratio' : 100
283
288
284 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
289 done = fixed_issues.open(open).sum("COALESCE(estimated_hours, #{estimated_average}) * #{ratio}").to_f
285 progress = done / (estimated_average * issues_count)
290 progress = done / (estimated_average * issues_count)
286 end
291 end
287 progress
292 progress
288 end
293 end
289 end
294 end
290 end
295 end
@@ -1,3998 +1,3998
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssuesControllerTest < ActionController::TestCase
20 class IssuesControllerTest < ActionController::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :users,
22 :users,
23 :roles,
23 :roles,
24 :members,
24 :members,
25 :member_roles,
25 :member_roles,
26 :issues,
26 :issues,
27 :issue_statuses,
27 :issue_statuses,
28 :versions,
28 :versions,
29 :trackers,
29 :trackers,
30 :projects_trackers,
30 :projects_trackers,
31 :issue_categories,
31 :issue_categories,
32 :enabled_modules,
32 :enabled_modules,
33 :enumerations,
33 :enumerations,
34 :attachments,
34 :attachments,
35 :workflows,
35 :workflows,
36 :custom_fields,
36 :custom_fields,
37 :custom_values,
37 :custom_values,
38 :custom_fields_projects,
38 :custom_fields_projects,
39 :custom_fields_trackers,
39 :custom_fields_trackers,
40 :time_entries,
40 :time_entries,
41 :journals,
41 :journals,
42 :journal_details,
42 :journal_details,
43 :queries,
43 :queries,
44 :repositories,
44 :repositories,
45 :changesets
45 :changesets
46
46
47 include Redmine::I18n
47 include Redmine::I18n
48
48
49 def setup
49 def setup
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index
53 def test_index
54 with_settings :default_language => "en" do
54 with_settings :default_language => "en" do
55 get :index
55 get :index
56 assert_response :success
56 assert_response :success
57 assert_template 'index'
57 assert_template 'index'
58 assert_not_nil assigns(:issues)
58 assert_not_nil assigns(:issues)
59 assert_nil assigns(:project)
59 assert_nil assigns(:project)
60
60
61 # links to visible issues
61 # links to visible issues
62 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
62 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
63 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
63 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
64 # private projects hidden
64 # private projects hidden
65 assert_select 'a[href=/issues/6]', 0
65 assert_select 'a[href=/issues/6]', 0
66 assert_select 'a[href=/issues/4]', 0
66 assert_select 'a[href=/issues/4]', 0
67 # project column
67 # project column
68 assert_select 'th', :text => /Project/
68 assert_select 'th', :text => /Project/
69 end
69 end
70 end
70 end
71
71
72 def test_index_should_not_list_issues_when_module_disabled
72 def test_index_should_not_list_issues_when_module_disabled
73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 get :index
74 get :index
75 assert_response :success
75 assert_response :success
76 assert_template 'index'
76 assert_template 'index'
77 assert_not_nil assigns(:issues)
77 assert_not_nil assigns(:issues)
78 assert_nil assigns(:project)
78 assert_nil assigns(:project)
79
79
80 assert_select 'a[href=/issues/1]', 0
80 assert_select 'a[href=/issues/1]', 0
81 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
81 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
82 end
82 end
83
83
84 def test_index_should_list_visible_issues_only
84 def test_index_should_list_visible_issues_only
85 get :index, :per_page => 100
85 get :index, :per_page => 100
86 assert_response :success
86 assert_response :success
87 assert_not_nil assigns(:issues)
87 assert_not_nil assigns(:issues)
88 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
88 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
89 end
89 end
90
90
91 def test_index_with_project
91 def test_index_with_project
92 Setting.display_subprojects_issues = 0
92 Setting.display_subprojects_issues = 0
93 get :index, :project_id => 1
93 get :index, :project_id => 1
94 assert_response :success
94 assert_response :success
95 assert_template 'index'
95 assert_template 'index'
96 assert_not_nil assigns(:issues)
96 assert_not_nil assigns(:issues)
97
97
98 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
98 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
99 assert_select 'a[href=/issues/5]', 0
99 assert_select 'a[href=/issues/5]', 0
100 end
100 end
101
101
102 def test_index_with_project_and_subprojects
102 def test_index_with_project_and_subprojects
103 Setting.display_subprojects_issues = 1
103 Setting.display_subprojects_issues = 1
104 get :index, :project_id => 1
104 get :index, :project_id => 1
105 assert_response :success
105 assert_response :success
106 assert_template 'index'
106 assert_template 'index'
107 assert_not_nil assigns(:issues)
107 assert_not_nil assigns(:issues)
108
108
109 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
109 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
110 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
110 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
111 assert_select 'a[href=/issues/6]', 0
111 assert_select 'a[href=/issues/6]', 0
112 end
112 end
113
113
114 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
114 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
115 @request.session[:user_id] = 2
115 @request.session[:user_id] = 2
116 Setting.display_subprojects_issues = 1
116 Setting.display_subprojects_issues = 1
117 get :index, :project_id => 1
117 get :index, :project_id => 1
118 assert_response :success
118 assert_response :success
119 assert_template 'index'
119 assert_template 'index'
120 assert_not_nil assigns(:issues)
120 assert_not_nil assigns(:issues)
121
121
122 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
122 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
123 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
123 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
124 assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/
124 assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/
125 end
125 end
126
126
127 def test_index_with_project_and_default_filter
127 def test_index_with_project_and_default_filter
128 get :index, :project_id => 1, :set_filter => 1
128 get :index, :project_id => 1, :set_filter => 1
129 assert_response :success
129 assert_response :success
130 assert_template 'index'
130 assert_template 'index'
131 assert_not_nil assigns(:issues)
131 assert_not_nil assigns(:issues)
132
132
133 query = assigns(:query)
133 query = assigns(:query)
134 assert_not_nil query
134 assert_not_nil query
135 # default filter
135 # default filter
136 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
136 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
137 end
137 end
138
138
139 def test_index_with_project_and_filter
139 def test_index_with_project_and_filter
140 get :index, :project_id => 1, :set_filter => 1,
140 get :index, :project_id => 1, :set_filter => 1,
141 :f => ['tracker_id'],
141 :f => ['tracker_id'],
142 :op => {'tracker_id' => '='},
142 :op => {'tracker_id' => '='},
143 :v => {'tracker_id' => ['1']}
143 :v => {'tracker_id' => ['1']}
144 assert_response :success
144 assert_response :success
145 assert_template 'index'
145 assert_template 'index'
146 assert_not_nil assigns(:issues)
146 assert_not_nil assigns(:issues)
147
147
148 query = assigns(:query)
148 query = assigns(:query)
149 assert_not_nil query
149 assert_not_nil query
150 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
150 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
151 end
151 end
152
152
153 def test_index_with_short_filters
153 def test_index_with_short_filters
154 to_test = {
154 to_test = {
155 'status_id' => {
155 'status_id' => {
156 'o' => { :op => 'o', :values => [''] },
156 'o' => { :op => 'o', :values => [''] },
157 'c' => { :op => 'c', :values => [''] },
157 'c' => { :op => 'c', :values => [''] },
158 '7' => { :op => '=', :values => ['7'] },
158 '7' => { :op => '=', :values => ['7'] },
159 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
159 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
160 '=7' => { :op => '=', :values => ['7'] },
160 '=7' => { :op => '=', :values => ['7'] },
161 '!3' => { :op => '!', :values => ['3'] },
161 '!3' => { :op => '!', :values => ['3'] },
162 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
162 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
163 'subject' => {
163 'subject' => {
164 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
164 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
165 'o' => { :op => '=', :values => ['o'] },
165 'o' => { :op => '=', :values => ['o'] },
166 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
166 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
167 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
167 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
168 'tracker_id' => {
168 'tracker_id' => {
169 '3' => { :op => '=', :values => ['3'] },
169 '3' => { :op => '=', :values => ['3'] },
170 '=3' => { :op => '=', :values => ['3'] }},
170 '=3' => { :op => '=', :values => ['3'] }},
171 'start_date' => {
171 'start_date' => {
172 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
172 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
173 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
173 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
174 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
174 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
175 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
175 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
176 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
176 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
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 't' => { :op => 't', :values => [''] },
180 't' => { :op => 't', :values => [''] },
181 'w' => { :op => 'w', :values => [''] },
181 'w' => { :op => 'w', :values => [''] },
182 '>t-2' => { :op => '>t-', :values => ['2'] },
182 '>t-2' => { :op => '>t-', :values => ['2'] },
183 '<t-2' => { :op => '<t-', :values => ['2'] },
183 '<t-2' => { :op => '<t-', :values => ['2'] },
184 't-2' => { :op => 't-', :values => ['2'] }},
184 't-2' => { :op => 't-', :values => ['2'] }},
185 'created_on' => {
185 'created_on' => {
186 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
186 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
187 '<t-2' => { :op => '<t-', :values => ['2'] },
187 '<t-2' => { :op => '<t-', :values => ['2'] },
188 '>t-2' => { :op => '>t-', :values => ['2'] },
188 '>t-2' => { :op => '>t-', :values => ['2'] },
189 't-2' => { :op => 't-', :values => ['2'] }},
189 't-2' => { :op => 't-', :values => ['2'] }},
190 'cf_1' => {
190 'cf_1' => {
191 'c' => { :op => '=', :values => ['c'] },
191 'c' => { :op => '=', :values => ['c'] },
192 '!c' => { :op => '!', :values => ['c'] },
192 '!c' => { :op => '!', :values => ['c'] },
193 '!*' => { :op => '!*', :values => [''] },
193 '!*' => { :op => '!*', :values => [''] },
194 '*' => { :op => '*', :values => [''] }},
194 '*' => { :op => '*', :values => [''] }},
195 'estimated_hours' => {
195 'estimated_hours' => {
196 '=13.4' => { :op => '=', :values => ['13.4'] },
196 '=13.4' => { :op => '=', :values => ['13.4'] },
197 '>=45' => { :op => '>=', :values => ['45'] },
197 '>=45' => { :op => '>=', :values => ['45'] },
198 '<=125' => { :op => '<=', :values => ['125'] },
198 '<=125' => { :op => '<=', :values => ['125'] },
199 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
199 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
200 '!*' => { :op => '!*', :values => [''] },
200 '!*' => { :op => '!*', :values => [''] },
201 '*' => { :op => '*', :values => [''] }}
201 '*' => { :op => '*', :values => [''] }}
202 }
202 }
203
203
204 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
204 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
205
205
206 to_test.each do |field, expression_and_expected|
206 to_test.each do |field, expression_and_expected|
207 expression_and_expected.each do |filter_expression, expected|
207 expression_and_expected.each do |filter_expression, expected|
208
208
209 get :index, :set_filter => 1, field => filter_expression
209 get :index, :set_filter => 1, field => filter_expression
210
210
211 assert_response :success
211 assert_response :success
212 assert_template 'index'
212 assert_template 'index'
213 assert_not_nil assigns(:issues)
213 assert_not_nil assigns(:issues)
214
214
215 query = assigns(:query)
215 query = assigns(:query)
216 assert_not_nil query
216 assert_not_nil query
217 assert query.has_filter?(field)
217 assert query.has_filter?(field)
218 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
218 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
219 end
219 end
220 end
220 end
221 end
221 end
222
222
223 def test_index_with_project_and_empty_filters
223 def test_index_with_project_and_empty_filters
224 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
224 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
225 assert_response :success
225 assert_response :success
226 assert_template 'index'
226 assert_template 'index'
227 assert_not_nil assigns(:issues)
227 assert_not_nil assigns(:issues)
228
228
229 query = assigns(:query)
229 query = assigns(:query)
230 assert_not_nil query
230 assert_not_nil query
231 # no filter
231 # no filter
232 assert_equal({}, query.filters)
232 assert_equal({}, query.filters)
233 end
233 end
234
234
235 def test_index_with_project_custom_field_filter
235 def test_index_with_project_custom_field_filter
236 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
236 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
237 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
237 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
238 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
238 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
239 filter_name = "project.cf_#{field.id}"
239 filter_name = "project.cf_#{field.id}"
240 @request.session[:user_id] = 1
240 @request.session[:user_id] = 1
241
241
242 get :index, :set_filter => 1,
242 get :index, :set_filter => 1,
243 :f => [filter_name],
243 :f => [filter_name],
244 :op => {filter_name => '='},
244 :op => {filter_name => '='},
245 :v => {filter_name => ['Foo']}
245 :v => {filter_name => ['Foo']}
246 assert_response :success
246 assert_response :success
247 assert_template 'index'
247 assert_template 'index'
248 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
248 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
249 end
249 end
250
250
251 def test_index_with_query
251 def test_index_with_query
252 get :index, :project_id => 1, :query_id => 5
252 get :index, :project_id => 1, :query_id => 5
253 assert_response :success
253 assert_response :success
254 assert_template 'index'
254 assert_template 'index'
255 assert_not_nil assigns(:issues)
255 assert_not_nil assigns(:issues)
256 assert_nil assigns(:issue_count_by_group)
256 assert_nil assigns(:issue_count_by_group)
257 end
257 end
258
258
259 def test_index_with_query_grouped_by_tracker
259 def test_index_with_query_grouped_by_tracker
260 get :index, :project_id => 1, :query_id => 6
260 get :index, :project_id => 1, :query_id => 6
261 assert_response :success
261 assert_response :success
262 assert_template 'index'
262 assert_template 'index'
263 assert_not_nil assigns(:issues)
263 assert_not_nil assigns(:issues)
264 assert_not_nil assigns(:issue_count_by_group)
264 assert_not_nil assigns(:issue_count_by_group)
265 end
265 end
266
266
267 def test_index_with_query_grouped_by_list_custom_field
267 def test_index_with_query_grouped_by_list_custom_field
268 get :index, :project_id => 1, :query_id => 9
268 get :index, :project_id => 1, :query_id => 9
269 assert_response :success
269 assert_response :success
270 assert_template 'index'
270 assert_template 'index'
271 assert_not_nil assigns(:issues)
271 assert_not_nil assigns(:issues)
272 assert_not_nil assigns(:issue_count_by_group)
272 assert_not_nil assigns(:issue_count_by_group)
273 end
273 end
274
274
275 def test_index_with_query_grouped_by_user_custom_field
275 def test_index_with_query_grouped_by_user_custom_field
276 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
276 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
277 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
277 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
278 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
278 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
279 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
279 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
280 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
280 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
281
281
282 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
282 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
283 assert_response :success
283 assert_response :success
284
284
285 assert_select 'tr.group', 3
285 assert_select 'tr.group', 3
286 assert_select 'tr.group' do
286 assert_select 'tr.group' do
287 assert_select 'a', :text => 'John Smith'
287 assert_select 'a', :text => 'John Smith'
288 assert_select 'span.count', :text => '1'
288 assert_select 'span.count', :text => '1'
289 end
289 end
290 assert_select 'tr.group' do
290 assert_select 'tr.group' do
291 assert_select 'a', :text => 'Dave Lopper'
291 assert_select 'a', :text => 'Dave Lopper'
292 assert_select 'span.count', :text => '2'
292 assert_select 'span.count', :text => '2'
293 end
293 end
294 end
294 end
295
295
296 def test_index_with_query_grouped_by_tracker_in_normal_order
296 def test_index_with_query_grouped_by_tracker_in_normal_order
297 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
297 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
298
298
299 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
299 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
300 assert_response :success
300 assert_response :success
301
301
302 trackers = assigns(:issues).map(&:tracker).uniq
302 trackers = assigns(:issues).map(&:tracker).uniq
303 assert_equal [1, 2, 3], trackers.map(&:id)
303 assert_equal [1, 2, 3], trackers.map(&:id)
304 end
304 end
305
305
306 def test_index_with_query_grouped_by_tracker_in_reverse_order
306 def test_index_with_query_grouped_by_tracker_in_reverse_order
307 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
307 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
308
308
309 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
309 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
310 assert_response :success
310 assert_response :success
311
311
312 trackers = assigns(:issues).map(&:tracker).uniq
312 trackers = assigns(:issues).map(&:tracker).uniq
313 assert_equal [3, 2, 1], trackers.map(&:id)
313 assert_equal [3, 2, 1], trackers.map(&:id)
314 end
314 end
315
315
316 def test_index_with_query_id_and_project_id_should_set_session_query
316 def test_index_with_query_id_and_project_id_should_set_session_query
317 get :index, :project_id => 1, :query_id => 4
317 get :index, :project_id => 1, :query_id => 4
318 assert_response :success
318 assert_response :success
319 assert_kind_of Hash, session[:query]
319 assert_kind_of Hash, session[:query]
320 assert_equal 4, session[:query][:id]
320 assert_equal 4, session[:query][:id]
321 assert_equal 1, session[:query][:project_id]
321 assert_equal 1, session[:query][:project_id]
322 end
322 end
323
323
324 def test_index_with_invalid_query_id_should_respond_404
324 def test_index_with_invalid_query_id_should_respond_404
325 get :index, :project_id => 1, :query_id => 999
325 get :index, :project_id => 1, :query_id => 999
326 assert_response 404
326 assert_response 404
327 end
327 end
328
328
329 def test_index_with_cross_project_query_in_session_should_show_project_issues
329 def test_index_with_cross_project_query_in_session_should_show_project_issues
330 q = IssueQuery.create!(:name => "test", :user_id => 2, :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
330 q = IssueQuery.create!(:name => "test", :user_id => 2, :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
331 @request.session[:query] = {:id => q.id, :project_id => 1}
331 @request.session[:query] = {:id => q.id, :project_id => 1}
332
332
333 with_settings :display_subprojects_issues => '0' do
333 with_settings :display_subprojects_issues => '0' do
334 get :index, :project_id => 1
334 get :index, :project_id => 1
335 end
335 end
336 assert_response :success
336 assert_response :success
337 assert_not_nil assigns(:query)
337 assert_not_nil assigns(:query)
338 assert_equal q.id, assigns(:query).id
338 assert_equal q.id, assigns(:query).id
339 assert_equal 1, assigns(:query).project_id
339 assert_equal 1, assigns(:query).project_id
340 assert_equal [1], assigns(:issues).map(&:project_id).uniq
340 assert_equal [1], assigns(:issues).map(&:project_id).uniq
341 end
341 end
342
342
343 def test_private_query_should_not_be_available_to_other_users
343 def test_private_query_should_not_be_available_to_other_users
344 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
344 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
345 @request.session[:user_id] = 3
345 @request.session[:user_id] = 3
346
346
347 get :index, :query_id => q.id
347 get :index, :query_id => q.id
348 assert_response 403
348 assert_response 403
349 end
349 end
350
350
351 def test_private_query_should_be_available_to_its_user
351 def test_private_query_should_be_available_to_its_user
352 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
352 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
353 @request.session[:user_id] = 2
353 @request.session[:user_id] = 2
354
354
355 get :index, :query_id => q.id
355 get :index, :query_id => q.id
356 assert_response :success
356 assert_response :success
357 end
357 end
358
358
359 def test_public_query_should_be_available_to_other_users
359 def test_public_query_should_be_available_to_other_users
360 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
360 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
361 @request.session[:user_id] = 3
361 @request.session[:user_id] = 3
362
362
363 get :index, :query_id => q.id
363 get :index, :query_id => q.id
364 assert_response :success
364 assert_response :success
365 end
365 end
366
366
367 def test_index_should_omit_page_param_in_export_links
367 def test_index_should_omit_page_param_in_export_links
368 get :index, :page => 2
368 get :index, :page => 2
369 assert_response :success
369 assert_response :success
370 assert_select 'a.atom[href=/issues.atom]'
370 assert_select 'a.atom[href=/issues.atom]'
371 assert_select 'a.csv[href=/issues.csv]'
371 assert_select 'a.csv[href=/issues.csv]'
372 assert_select 'a.pdf[href=/issues.pdf]'
372 assert_select 'a.pdf[href=/issues.pdf]'
373 assert_select 'form#csv-export-form[action=/issues.csv]'
373 assert_select 'form#csv-export-form[action=/issues.csv]'
374 end
374 end
375
375
376 def test_index_should_not_warn_when_not_exceeding_export_limit
376 def test_index_should_not_warn_when_not_exceeding_export_limit
377 with_settings :issues_export_limit => 200 do
377 with_settings :issues_export_limit => 200 do
378 get :index
378 get :index
379 assert_select '#csv-export-options p.icon-warning', 0
379 assert_select '#csv-export-options p.icon-warning', 0
380 end
380 end
381 end
381 end
382
382
383 def test_index_should_warn_when_exceeding_export_limit
383 def test_index_should_warn_when_exceeding_export_limit
384 with_settings :issues_export_limit => 2 do
384 with_settings :issues_export_limit => 2 do
385 get :index
385 get :index
386 assert_select '#csv-export-options p.icon-warning', :text => %r{limit: 2}
386 assert_select '#csv-export-options p.icon-warning', :text => %r{limit: 2}
387 end
387 end
388 end
388 end
389
389
390 def test_index_csv
390 def test_index_csv
391 get :index, :format => 'csv'
391 get :index, :format => 'csv'
392 assert_response :success
392 assert_response :success
393 assert_not_nil assigns(:issues)
393 assert_not_nil assigns(:issues)
394 assert_equal 'text/csv; header=present', @response.content_type
394 assert_equal 'text/csv; header=present', @response.content_type
395 assert @response.body.starts_with?("#,")
395 assert @response.body.starts_with?("#,")
396 lines = @response.body.chomp.split("\n")
396 lines = @response.body.chomp.split("\n")
397 assert_equal assigns(:query).columns.size, lines[0].split(',').size
397 assert_equal assigns(:query).columns.size, lines[0].split(',').size
398 end
398 end
399
399
400 def test_index_csv_with_project
400 def test_index_csv_with_project
401 get :index, :project_id => 1, :format => 'csv'
401 get :index, :project_id => 1, :format => 'csv'
402 assert_response :success
402 assert_response :success
403 assert_not_nil assigns(:issues)
403 assert_not_nil assigns(:issues)
404 assert_equal 'text/csv; header=present', @response.content_type
404 assert_equal 'text/csv; header=present', @response.content_type
405 end
405 end
406
406
407 def test_index_csv_with_description
407 def test_index_csv_with_description
408 Issue.generate!(:description => 'test_index_csv_with_description')
408 Issue.generate!(:description => 'test_index_csv_with_description')
409
409
410 with_settings :default_language => 'en' do
410 with_settings :default_language => 'en' do
411 get :index, :format => 'csv', :description => '1'
411 get :index, :format => 'csv', :description => '1'
412 assert_response :success
412 assert_response :success
413 assert_not_nil assigns(:issues)
413 assert_not_nil assigns(:issues)
414 end
414 end
415
415
416 assert_equal 'text/csv; header=present', response.content_type
416 assert_equal 'text/csv; header=present', response.content_type
417 headers = response.body.chomp.split("\n").first.split(',')
417 headers = response.body.chomp.split("\n").first.split(',')
418 assert_include 'Description', headers
418 assert_include 'Description', headers
419 assert_include 'test_index_csv_with_description', response.body
419 assert_include 'test_index_csv_with_description', response.body
420 end
420 end
421
421
422 def test_index_csv_with_spent_time_column
422 def test_index_csv_with_spent_time_column
423 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
423 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
424 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
424 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
425
425
426 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
426 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
427 assert_response :success
427 assert_response :success
428 assert_equal 'text/csv; header=present', @response.content_type
428 assert_equal 'text/csv; header=present', @response.content_type
429 lines = @response.body.chomp.split("\n")
429 lines = @response.body.chomp.split("\n")
430 assert_include "#{issue.id},#{issue.subject},7.33", lines
430 assert_include "#{issue.id},#{issue.subject},7.33", lines
431 end
431 end
432
432
433 def test_index_csv_with_all_columns
433 def test_index_csv_with_all_columns
434 get :index, :format => 'csv', :columns => 'all'
434 get :index, :format => 'csv', :columns => 'all'
435 assert_response :success
435 assert_response :success
436 assert_not_nil assigns(:issues)
436 assert_not_nil assigns(:issues)
437 assert_equal 'text/csv; header=present', @response.content_type
437 assert_equal 'text/csv; header=present', @response.content_type
438 assert_match /\A#,/, response.body
438 assert_match /\A#,/, response.body
439 lines = response.body.chomp.split("\n")
439 lines = response.body.chomp.split("\n")
440 assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size
440 assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size
441 end
441 end
442
442
443 def test_index_csv_with_multi_column_field
443 def test_index_csv_with_multi_column_field
444 CustomField.find(1).update_attribute :multiple, true
444 CustomField.find(1).update_attribute :multiple, true
445 issue = Issue.find(1)
445 issue = Issue.find(1)
446 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
446 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
447 issue.save!
447 issue.save!
448
448
449 get :index, :format => 'csv', :columns => 'all'
449 get :index, :format => 'csv', :columns => 'all'
450 assert_response :success
450 assert_response :success
451 lines = @response.body.chomp.split("\n")
451 lines = @response.body.chomp.split("\n")
452 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
452 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
453 end
453 end
454
454
455 def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator
455 def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator
456 field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float')
456 field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float')
457 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'})
457 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'})
458
458
459 with_settings :default_language => 'fr' do
459 with_settings :default_language => 'fr' do
460 get :index, :format => 'csv', :columns => 'all'
460 get :index, :format => 'csv', :columns => 'all'
461 assert_response :success
461 assert_response :success
462 issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s}
462 issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s}
463 assert_include '185,60', issue_line
463 assert_include '185,60', issue_line
464 end
464 end
465
465
466 with_settings :default_language => 'en' do
466 with_settings :default_language => 'en' do
467 get :index, :format => 'csv', :columns => 'all'
467 get :index, :format => 'csv', :columns => 'all'
468 assert_response :success
468 assert_response :success
469 issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s}
469 issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s}
470 assert_include '185.60', issue_line
470 assert_include '185.60', issue_line
471 end
471 end
472 end
472 end
473
473
474 def test_index_csv_big_5
474 def test_index_csv_big_5
475 with_settings :default_language => "zh-TW" do
475 with_settings :default_language => "zh-TW" do
476 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
476 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
477 str_big5 = "\xa4@\xa4\xeb"
477 str_big5 = "\xa4@\xa4\xeb"
478 if str_utf8.respond_to?(:force_encoding)
478 if str_utf8.respond_to?(:force_encoding)
479 str_utf8.force_encoding('UTF-8')
479 str_utf8.force_encoding('UTF-8')
480 str_big5.force_encoding('Big5')
480 str_big5.force_encoding('Big5')
481 end
481 end
482 issue = Issue.generate!(:subject => str_utf8)
482 issue = Issue.generate!(:subject => str_utf8)
483
483
484 get :index, :project_id => 1,
484 get :index, :project_id => 1,
485 :f => ['subject'],
485 :f => ['subject'],
486 :op => '=', :values => [str_utf8],
486 :op => '=', :values => [str_utf8],
487 :format => 'csv'
487 :format => 'csv'
488 assert_equal 'text/csv; header=present', @response.content_type
488 assert_equal 'text/csv; header=present', @response.content_type
489 lines = @response.body.chomp.split("\n")
489 lines = @response.body.chomp.split("\n")
490 s1 = "\xaa\xac\xbaA"
490 s1 = "\xaa\xac\xbaA"
491 if str_utf8.respond_to?(:force_encoding)
491 if str_utf8.respond_to?(:force_encoding)
492 s1.force_encoding('Big5')
492 s1.force_encoding('Big5')
493 end
493 end
494 assert_include s1, lines[0]
494 assert_include s1, lines[0]
495 assert_include str_big5, lines[1]
495 assert_include str_big5, lines[1]
496 end
496 end
497 end
497 end
498
498
499 def test_index_csv_cannot_convert_should_be_replaced_big_5
499 def test_index_csv_cannot_convert_should_be_replaced_big_5
500 with_settings :default_language => "zh-TW" do
500 with_settings :default_language => "zh-TW" do
501 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
501 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
502 if str_utf8.respond_to?(:force_encoding)
502 if str_utf8.respond_to?(:force_encoding)
503 str_utf8.force_encoding('UTF-8')
503 str_utf8.force_encoding('UTF-8')
504 end
504 end
505 issue = Issue.generate!(:subject => str_utf8)
505 issue = Issue.generate!(:subject => str_utf8)
506
506
507 get :index, :project_id => 1,
507 get :index, :project_id => 1,
508 :f => ['subject'],
508 :f => ['subject'],
509 :op => '=', :values => [str_utf8],
509 :op => '=', :values => [str_utf8],
510 :c => ['status', 'subject'],
510 :c => ['status', 'subject'],
511 :format => 'csv',
511 :format => 'csv',
512 :set_filter => 1
512 :set_filter => 1
513 assert_equal 'text/csv; header=present', @response.content_type
513 assert_equal 'text/csv; header=present', @response.content_type
514 lines = @response.body.chomp.split("\n")
514 lines = @response.body.chomp.split("\n")
515 s1 = "\xaa\xac\xbaA" # status
515 s1 = "\xaa\xac\xbaA" # status
516 if str_utf8.respond_to?(:force_encoding)
516 if str_utf8.respond_to?(:force_encoding)
517 s1.force_encoding('Big5')
517 s1.force_encoding('Big5')
518 end
518 end
519 assert lines[0].include?(s1)
519 assert lines[0].include?(s1)
520 s2 = lines[1].split(",")[2]
520 s2 = lines[1].split(",")[2]
521 if s1.respond_to?(:force_encoding)
521 if s1.respond_to?(:force_encoding)
522 s3 = "\xa5H?" # subject
522 s3 = "\xa5H?" # subject
523 s3.force_encoding('Big5')
523 s3.force_encoding('Big5')
524 assert_equal s3, s2
524 assert_equal s3, s2
525 elsif RUBY_PLATFORM == 'java'
525 elsif RUBY_PLATFORM == 'java'
526 assert_equal "??", s2
526 assert_equal "??", s2
527 else
527 else
528 assert_equal "\xa5H???", s2
528 assert_equal "\xa5H???", s2
529 end
529 end
530 end
530 end
531 end
531 end
532
532
533 def test_index_csv_tw
533 def test_index_csv_tw
534 with_settings :default_language => "zh-TW" do
534 with_settings :default_language => "zh-TW" do
535 str1 = "test_index_csv_tw"
535 str1 = "test_index_csv_tw"
536 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
536 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
537
537
538 get :index, :project_id => 1,
538 get :index, :project_id => 1,
539 :f => ['subject'],
539 :f => ['subject'],
540 :op => '=', :values => [str1],
540 :op => '=', :values => [str1],
541 :c => ['estimated_hours', 'subject'],
541 :c => ['estimated_hours', 'subject'],
542 :format => 'csv',
542 :format => 'csv',
543 :set_filter => 1
543 :set_filter => 1
544 assert_equal 'text/csv; header=present', @response.content_type
544 assert_equal 'text/csv; header=present', @response.content_type
545 lines = @response.body.chomp.split("\n")
545 lines = @response.body.chomp.split("\n")
546 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
546 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
547 end
547 end
548 end
548 end
549
549
550 def test_index_csv_fr
550 def test_index_csv_fr
551 with_settings :default_language => "fr" do
551 with_settings :default_language => "fr" do
552 str1 = "test_index_csv_fr"
552 str1 = "test_index_csv_fr"
553 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
553 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
554
554
555 get :index, :project_id => 1,
555 get :index, :project_id => 1,
556 :f => ['subject'],
556 :f => ['subject'],
557 :op => '=', :values => [str1],
557 :op => '=', :values => [str1],
558 :c => ['estimated_hours', 'subject'],
558 :c => ['estimated_hours', 'subject'],
559 :format => 'csv',
559 :format => 'csv',
560 :set_filter => 1
560 :set_filter => 1
561 assert_equal 'text/csv; header=present', @response.content_type
561 assert_equal 'text/csv; header=present', @response.content_type
562 lines = @response.body.chomp.split("\n")
562 lines = @response.body.chomp.split("\n")
563 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
563 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
564 end
564 end
565 end
565 end
566
566
567 def test_index_pdf
567 def test_index_pdf
568 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
568 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
569 with_settings :default_language => lang do
569 with_settings :default_language => lang do
570
570
571 get :index
571 get :index
572 assert_response :success
572 assert_response :success
573 assert_template 'index'
573 assert_template 'index'
574
574
575 if lang == "ja"
575 if lang == "ja"
576 if RUBY_PLATFORM != 'java'
576 if RUBY_PLATFORM != 'java'
577 assert_equal "CP932", l(:general_pdf_encoding)
577 assert_equal "CP932", l(:general_pdf_encoding)
578 end
578 end
579 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
579 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
580 next
580 next
581 end
581 end
582 end
582 end
583
583
584 get :index, :format => 'pdf'
584 get :index, :format => 'pdf'
585 assert_response :success
585 assert_response :success
586 assert_not_nil assigns(:issues)
586 assert_not_nil assigns(:issues)
587 assert_equal 'application/pdf', @response.content_type
587 assert_equal 'application/pdf', @response.content_type
588
588
589 get :index, :project_id => 1, :format => 'pdf'
589 get :index, :project_id => 1, :format => 'pdf'
590 assert_response :success
590 assert_response :success
591 assert_not_nil assigns(:issues)
591 assert_not_nil assigns(:issues)
592 assert_equal 'application/pdf', @response.content_type
592 assert_equal 'application/pdf', @response.content_type
593
593
594 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
594 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
595 assert_response :success
595 assert_response :success
596 assert_not_nil assigns(:issues)
596 assert_not_nil assigns(:issues)
597 assert_equal 'application/pdf', @response.content_type
597 assert_equal 'application/pdf', @response.content_type
598 end
598 end
599 end
599 end
600 end
600 end
601
601
602 def test_index_pdf_with_query_grouped_by_list_custom_field
602 def test_index_pdf_with_query_grouped_by_list_custom_field
603 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
603 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
604 assert_response :success
604 assert_response :success
605 assert_not_nil assigns(:issues)
605 assert_not_nil assigns(:issues)
606 assert_not_nil assigns(:issue_count_by_group)
606 assert_not_nil assigns(:issue_count_by_group)
607 assert_equal 'application/pdf', @response.content_type
607 assert_equal 'application/pdf', @response.content_type
608 end
608 end
609
609
610 def test_index_atom
610 def test_index_atom
611 get :index, :project_id => 'ecookbook', :format => 'atom'
611 get :index, :project_id => 'ecookbook', :format => 'atom'
612 assert_response :success
612 assert_response :success
613 assert_template 'common/feed'
613 assert_template 'common/feed'
614 assert_equal 'application/atom+xml', response.content_type
614 assert_equal 'application/atom+xml', response.content_type
615
615
616 assert_select 'feed' do
616 assert_select 'feed' do
617 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
617 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
618 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
618 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
619 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
619 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
620 end
620 end
621 end
621 end
622
622
623 def test_index_sort
623 def test_index_sort
624 get :index, :sort => 'tracker,id:desc'
624 get :index, :sort => 'tracker,id:desc'
625 assert_response :success
625 assert_response :success
626
626
627 sort_params = @request.session['issues_index_sort']
627 sort_params = @request.session['issues_index_sort']
628 assert sort_params.is_a?(String)
628 assert sort_params.is_a?(String)
629 assert_equal 'tracker,id:desc', sort_params
629 assert_equal 'tracker,id:desc', sort_params
630
630
631 issues = assigns(:issues)
631 issues = assigns(:issues)
632 assert_not_nil issues
632 assert_not_nil issues
633 assert !issues.empty?
633 assert !issues.empty?
634 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
634 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
635 end
635 end
636
636
637 def test_index_sort_by_field_not_included_in_columns
637 def test_index_sort_by_field_not_included_in_columns
638 Setting.issue_list_default_columns = %w(subject author)
638 Setting.issue_list_default_columns = %w(subject author)
639 get :index, :sort => 'tracker'
639 get :index, :sort => 'tracker'
640 end
640 end
641
641
642 def test_index_sort_by_assigned_to
642 def test_index_sort_by_assigned_to
643 get :index, :sort => 'assigned_to'
643 get :index, :sort => 'assigned_to'
644 assert_response :success
644 assert_response :success
645 assignees = assigns(:issues).collect(&:assigned_to).compact
645 assignees = assigns(:issues).collect(&:assigned_to).compact
646 assert_equal assignees.sort, assignees
646 assert_equal assignees.sort, assignees
647 end
647 end
648
648
649 def test_index_sort_by_assigned_to_desc
649 def test_index_sort_by_assigned_to_desc
650 get :index, :sort => 'assigned_to:desc'
650 get :index, :sort => 'assigned_to:desc'
651 assert_response :success
651 assert_response :success
652 assignees = assigns(:issues).collect(&:assigned_to).compact
652 assignees = assigns(:issues).collect(&:assigned_to).compact
653 assert_equal assignees.sort.reverse, assignees
653 assert_equal assignees.sort.reverse, assignees
654 end
654 end
655
655
656 def test_index_group_by_assigned_to
656 def test_index_group_by_assigned_to
657 get :index, :group_by => 'assigned_to', :sort => 'priority'
657 get :index, :group_by => 'assigned_to', :sort => 'priority'
658 assert_response :success
658 assert_response :success
659 end
659 end
660
660
661 def test_index_sort_by_author
661 def test_index_sort_by_author
662 get :index, :sort => 'author'
662 get :index, :sort => 'author'
663 assert_response :success
663 assert_response :success
664 authors = assigns(:issues).collect(&:author)
664 authors = assigns(:issues).collect(&:author)
665 assert_equal authors.sort, authors
665 assert_equal authors.sort, authors
666 end
666 end
667
667
668 def test_index_sort_by_author_desc
668 def test_index_sort_by_author_desc
669 get :index, :sort => 'author:desc'
669 get :index, :sort => 'author:desc'
670 assert_response :success
670 assert_response :success
671 authors = assigns(:issues).collect(&:author)
671 authors = assigns(:issues).collect(&:author)
672 assert_equal authors.sort.reverse, authors
672 assert_equal authors.sort.reverse, authors
673 end
673 end
674
674
675 def test_index_group_by_author
675 def test_index_group_by_author
676 get :index, :group_by => 'author', :sort => 'priority'
676 get :index, :group_by => 'author', :sort => 'priority'
677 assert_response :success
677 assert_response :success
678 end
678 end
679
679
680 def test_index_sort_by_spent_hours
680 def test_index_sort_by_spent_hours
681 get :index, :sort => 'spent_hours:desc'
681 get :index, :sort => 'spent_hours:desc'
682 assert_response :success
682 assert_response :success
683 hours = assigns(:issues).collect(&:spent_hours)
683 hours = assigns(:issues).collect(&:spent_hours)
684 assert_equal hours.sort.reverse, hours
684 assert_equal hours.sort.reverse, hours
685 end
685 end
686
686
687 def test_index_sort_by_user_custom_field
687 def test_index_sort_by_user_custom_field
688 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
688 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
689 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
689 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
690 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
690 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
691 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
691 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
692 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
692 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
693
693
694 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
694 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
695 assert_response :success
695 assert_response :success
696
696
697 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
697 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
698 end
698 end
699
699
700 def test_index_with_columns
700 def test_index_with_columns
701 columns = ['tracker', 'subject', 'assigned_to']
701 columns = ['tracker', 'subject', 'assigned_to']
702 get :index, :set_filter => 1, :c => columns
702 get :index, :set_filter => 1, :c => columns
703 assert_response :success
703 assert_response :success
704
704
705 # query should use specified columns
705 # query should use specified columns
706 query = assigns(:query)
706 query = assigns(:query)
707 assert_kind_of IssueQuery, query
707 assert_kind_of IssueQuery, query
708 assert_equal columns, query.column_names.map(&:to_s)
708 assert_equal columns, query.column_names.map(&:to_s)
709
709
710 # columns should be stored in session
710 # columns should be stored in session
711 assert_kind_of Hash, session[:query]
711 assert_kind_of Hash, session[:query]
712 assert_kind_of Array, session[:query][:column_names]
712 assert_kind_of Array, session[:query][:column_names]
713 assert_equal columns, session[:query][:column_names].map(&:to_s)
713 assert_equal columns, session[:query][:column_names].map(&:to_s)
714
714
715 # ensure only these columns are kept in the selected columns list
715 # ensure only these columns are kept in the selected columns list
716 assert_select 'select#selected_columns option' do
716 assert_select 'select#selected_columns option' do
717 assert_select 'option', 3
717 assert_select 'option', 3
718 assert_select 'option[value=tracker]'
718 assert_select 'option[value=tracker]'
719 assert_select 'option[value=project]', 0
719 assert_select 'option[value=project]', 0
720 end
720 end
721 end
721 end
722
722
723 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
723 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
724 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
724 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
725 get :index, :set_filter => 1
725 get :index, :set_filter => 1
726
726
727 # query should use specified columns
727 # query should use specified columns
728 query = assigns(:query)
728 query = assigns(:query)
729 assert_kind_of IssueQuery, query
729 assert_kind_of IssueQuery, query
730 assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
730 assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
731 end
731 end
732
732
733 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
733 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
734 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
734 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
735 columns = ['id', 'tracker', 'subject', 'assigned_to']
735 columns = ['id', 'tracker', 'subject', 'assigned_to']
736 get :index, :set_filter => 1, :c => columns
736 get :index, :set_filter => 1, :c => columns
737
737
738 # query should use specified columns
738 # query should use specified columns
739 query = assigns(:query)
739 query = assigns(:query)
740 assert_kind_of IssueQuery, query
740 assert_kind_of IssueQuery, query
741 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
741 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
742 end
742 end
743
743
744 def test_index_with_custom_field_column
744 def test_index_with_custom_field_column
745 columns = %w(tracker subject cf_2)
745 columns = %w(tracker subject cf_2)
746 get :index, :set_filter => 1, :c => columns
746 get :index, :set_filter => 1, :c => columns
747 assert_response :success
747 assert_response :success
748
748
749 # query should use specified columns
749 # query should use specified columns
750 query = assigns(:query)
750 query = assigns(:query)
751 assert_kind_of IssueQuery, query
751 assert_kind_of IssueQuery, query
752 assert_equal columns, query.column_names.map(&:to_s)
752 assert_equal columns, query.column_names.map(&:to_s)
753
753
754 assert_select 'table.issues td.cf_2.string'
754 assert_select 'table.issues td.cf_2.string'
755 end
755 end
756
756
757 def test_index_with_multi_custom_field_column
757 def test_index_with_multi_custom_field_column
758 field = CustomField.find(1)
758 field = CustomField.find(1)
759 field.update_attribute :multiple, true
759 field.update_attribute :multiple, true
760 issue = Issue.find(1)
760 issue = Issue.find(1)
761 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
761 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
762 issue.save!
762 issue.save!
763
763
764 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
764 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
765 assert_response :success
765 assert_response :success
766
766
767 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
767 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
768 end
768 end
769
769
770 def test_index_with_multi_user_custom_field_column
770 def test_index_with_multi_user_custom_field_column
771 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
771 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
772 :tracker_ids => [1], :is_for_all => true)
772 :tracker_ids => [1], :is_for_all => true)
773 issue = Issue.find(1)
773 issue = Issue.find(1)
774 issue.custom_field_values = {field.id => ['2', '3']}
774 issue.custom_field_values = {field.id => ['2', '3']}
775 issue.save!
775 issue.save!
776
776
777 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
777 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
778 assert_response :success
778 assert_response :success
779
779
780 assert_select "table.issues td.cf_#{field.id}" do
780 assert_select "table.issues td.cf_#{field.id}" do
781 assert_select 'a', 2
781 assert_select 'a', 2
782 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
782 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
783 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
783 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
784 end
784 end
785 end
785 end
786
786
787 def test_index_with_date_column
787 def test_index_with_date_column
788 with_settings :date_format => '%d/%m/%Y' do
788 with_settings :date_format => '%d/%m/%Y' do
789 Issue.find(1).update_attribute :start_date, '1987-08-24'
789 Issue.find(1).update_attribute :start_date, '1987-08-24'
790 get :index, :set_filter => 1, :c => %w(start_date)
790 get :index, :set_filter => 1, :c => %w(start_date)
791 assert_select "table.issues td.start_date", :text => '24/08/1987'
791 assert_select "table.issues td.start_date", :text => '24/08/1987'
792 end
792 end
793 end
793 end
794
794
795 def test_index_with_done_ratio_column
795 def test_index_with_done_ratio_column
796 Issue.find(1).update_attribute :done_ratio, 40
796 Issue.find(1).update_attribute :done_ratio, 40
797 get :index, :set_filter => 1, :c => %w(done_ratio)
797 get :index, :set_filter => 1, :c => %w(done_ratio)
798 assert_select 'table.issues td.done_ratio' do
798 assert_select 'table.issues td.done_ratio' do
799 assert_select 'table.progress' do
799 assert_select 'table.progress' do
800 assert_select 'td.closed[style=?]', 'width: 40%;'
800 assert_select 'td.closed[style=?]', 'width: 40%;'
801 end
801 end
802 end
802 end
803 end
803 end
804
804
805 def test_index_with_spent_hours_column
805 def test_index_with_spent_hours_column
806 get :index, :set_filter => 1, :c => %w(subject spent_hours)
806 get :index, :set_filter => 1, :c => %w(subject spent_hours)
807 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
807 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
808 end
808 end
809
809
810 def test_index_should_not_show_spent_hours_column_without_permission
810 def test_index_should_not_show_spent_hours_column_without_permission
811 Role.anonymous.remove_permission! :view_time_entries
811 Role.anonymous.remove_permission! :view_time_entries
812 get :index, :set_filter => 1, :c => %w(subject spent_hours)
812 get :index, :set_filter => 1, :c => %w(subject spent_hours)
813 assert_select 'td.spent_hours', 0
813 assert_select 'td.spent_hours', 0
814 end
814 end
815
815
816 def test_index_with_fixed_version_column
816 def test_index_with_fixed_version_column
817 get :index, :set_filter => 1, :c => %w(fixed_version)
817 get :index, :set_filter => 1, :c => %w(fixed_version)
818 assert_select 'table.issues td.fixed_version' do
818 assert_select 'table.issues td.fixed_version' do
819 assert_select 'a[href=?]', '/versions/2', :text => 'eCookbook - 1.0'
819 assert_select 'a[href=?]', '/versions/2', :text => '1.0'
820 end
820 end
821 end
821 end
822
822
823 def test_index_with_relations_column
823 def test_index_with_relations_column
824 IssueRelation.delete_all
824 IssueRelation.delete_all
825 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
825 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
826 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
826 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
827 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
827 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
828 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
828 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
829
829
830 get :index, :set_filter => 1, :c => %w(subject relations)
830 get :index, :set_filter => 1, :c => %w(subject relations)
831 assert_response :success
831 assert_response :success
832 assert_select "tr#issue-1 td.relations" do
832 assert_select "tr#issue-1 td.relations" do
833 assert_select "span", 3
833 assert_select "span", 3
834 assert_select "span", :text => "Related to #7"
834 assert_select "span", :text => "Related to #7"
835 assert_select "span", :text => "Related to #8"
835 assert_select "span", :text => "Related to #8"
836 assert_select "span", :text => "Blocks #11"
836 assert_select "span", :text => "Blocks #11"
837 end
837 end
838 assert_select "tr#issue-2 td.relations" do
838 assert_select "tr#issue-2 td.relations" do
839 assert_select "span", 1
839 assert_select "span", 1
840 assert_select "span", :text => "Blocked by #12"
840 assert_select "span", :text => "Blocked by #12"
841 end
841 end
842 assert_select "tr#issue-3 td.relations" do
842 assert_select "tr#issue-3 td.relations" do
843 assert_select "span", 0
843 assert_select "span", 0
844 end
844 end
845
845
846 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
846 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
847 assert_response :success
847 assert_response :success
848 assert_equal 'text/csv; header=present', response.content_type
848 assert_equal 'text/csv; header=present', response.content_type
849 lines = response.body.chomp.split("\n")
849 lines = response.body.chomp.split("\n")
850 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
850 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
851 assert_include '2,Blocked by #12', lines
851 assert_include '2,Blocked by #12', lines
852 assert_include '3,""', lines
852 assert_include '3,""', lines
853
853
854 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
854 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
855 assert_response :success
855 assert_response :success
856 assert_equal 'application/pdf', response.content_type
856 assert_equal 'application/pdf', response.content_type
857 end
857 end
858
858
859 def test_index_with_description_column
859 def test_index_with_description_column
860 get :index, :set_filter => 1, :c => %w(subject description)
860 get :index, :set_filter => 1, :c => %w(subject description)
861
861
862 assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject
862 assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject
863 assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes'
863 assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes'
864
864
865 get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf'
865 get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf'
866 assert_response :success
866 assert_response :success
867 assert_equal 'application/pdf', response.content_type
867 assert_equal 'application/pdf', response.content_type
868 end
868 end
869
869
870 def test_index_send_html_if_query_is_invalid
870 def test_index_send_html_if_query_is_invalid
871 get :index, :f => ['start_date'], :op => {:start_date => '='}
871 get :index, :f => ['start_date'], :op => {:start_date => '='}
872 assert_equal 'text/html', @response.content_type
872 assert_equal 'text/html', @response.content_type
873 assert_template 'index'
873 assert_template 'index'
874 end
874 end
875
875
876 def test_index_send_nothing_if_query_is_invalid
876 def test_index_send_nothing_if_query_is_invalid
877 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
877 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
878 assert_equal 'text/csv', @response.content_type
878 assert_equal 'text/csv', @response.content_type
879 assert @response.body.blank?
879 assert @response.body.blank?
880 end
880 end
881
881
882 def test_show_by_anonymous
882 def test_show_by_anonymous
883 get :show, :id => 1
883 get :show, :id => 1
884 assert_response :success
884 assert_response :success
885 assert_template 'show'
885 assert_template 'show'
886 assert_equal Issue.find(1), assigns(:issue)
886 assert_equal Issue.find(1), assigns(:issue)
887 assert_select 'div.issue div.description', :text => /Unable to print recipes/
887 assert_select 'div.issue div.description', :text => /Unable to print recipes/
888 # anonymous role is allowed to add a note
888 # anonymous role is allowed to add a note
889 assert_select 'form#issue-form' do
889 assert_select 'form#issue-form' do
890 assert_select 'fieldset' do
890 assert_select 'fieldset' do
891 assert_select 'legend', :text => 'Notes'
891 assert_select 'legend', :text => 'Notes'
892 assert_select 'textarea[name=?]', 'issue[notes]'
892 assert_select 'textarea[name=?]', 'issue[notes]'
893 end
893 end
894 end
894 end
895 assert_select 'title', :text => "Bug #1: #{ESCAPED_UCANT} print recipes - eCookbook - Redmine"
895 assert_select 'title', :text => "Bug #1: #{ESCAPED_UCANT} print recipes - eCookbook - Redmine"
896 end
896 end
897
897
898 def test_show_by_manager
898 def test_show_by_manager
899 @request.session[:user_id] = 2
899 @request.session[:user_id] = 2
900 get :show, :id => 1
900 get :show, :id => 1
901 assert_response :success
901 assert_response :success
902 assert_select 'a', :text => /Quote/
902 assert_select 'a', :text => /Quote/
903 assert_select 'form#issue-form' do
903 assert_select 'form#issue-form' do
904 assert_select 'fieldset' do
904 assert_select 'fieldset' do
905 assert_select 'legend', :text => 'Change properties'
905 assert_select 'legend', :text => 'Change properties'
906 assert_select 'input[name=?]', 'issue[subject]'
906 assert_select 'input[name=?]', 'issue[subject]'
907 end
907 end
908 assert_select 'fieldset' do
908 assert_select 'fieldset' do
909 assert_select 'legend', :text => 'Log time'
909 assert_select 'legend', :text => 'Log time'
910 assert_select 'input[name=?]', 'time_entry[hours]'
910 assert_select 'input[name=?]', 'time_entry[hours]'
911 end
911 end
912 assert_select 'fieldset' do
912 assert_select 'fieldset' do
913 assert_select 'legend', :text => 'Notes'
913 assert_select 'legend', :text => 'Notes'
914 assert_select 'textarea[name=?]', 'issue[notes]'
914 assert_select 'textarea[name=?]', 'issue[notes]'
915 end
915 end
916 end
916 end
917 end
917 end
918
918
919 def test_show_should_display_update_form
919 def test_show_should_display_update_form
920 @request.session[:user_id] = 2
920 @request.session[:user_id] = 2
921 get :show, :id => 1
921 get :show, :id => 1
922 assert_response :success
922 assert_response :success
923
923
924 assert_select 'form#issue-form' do
924 assert_select 'form#issue-form' do
925 assert_select 'input[name=?]', 'issue[is_private]'
925 assert_select 'input[name=?]', 'issue[is_private]'
926 assert_select 'select[name=?]', 'issue[project_id]'
926 assert_select 'select[name=?]', 'issue[project_id]'
927 assert_select 'select[name=?]', 'issue[tracker_id]'
927 assert_select 'select[name=?]', 'issue[tracker_id]'
928 assert_select 'input[name=?]', 'issue[subject]'
928 assert_select 'input[name=?]', 'issue[subject]'
929 assert_select 'textarea[name=?]', 'issue[description]'
929 assert_select 'textarea[name=?]', 'issue[description]'
930 assert_select 'select[name=?]', 'issue[status_id]'
930 assert_select 'select[name=?]', 'issue[status_id]'
931 assert_select 'select[name=?]', 'issue[priority_id]'
931 assert_select 'select[name=?]', 'issue[priority_id]'
932 assert_select 'select[name=?]', 'issue[assigned_to_id]'
932 assert_select 'select[name=?]', 'issue[assigned_to_id]'
933 assert_select 'select[name=?]', 'issue[category_id]'
933 assert_select 'select[name=?]', 'issue[category_id]'
934 assert_select 'select[name=?]', 'issue[fixed_version_id]'
934 assert_select 'select[name=?]', 'issue[fixed_version_id]'
935 assert_select 'input[name=?]', 'issue[parent_issue_id]'
935 assert_select 'input[name=?]', 'issue[parent_issue_id]'
936 assert_select 'input[name=?]', 'issue[start_date]'
936 assert_select 'input[name=?]', 'issue[start_date]'
937 assert_select 'input[name=?]', 'issue[due_date]'
937 assert_select 'input[name=?]', 'issue[due_date]'
938 assert_select 'select[name=?]', 'issue[done_ratio]'
938 assert_select 'select[name=?]', 'issue[done_ratio]'
939 assert_select 'input[name=?]', 'issue[custom_field_values][2]'
939 assert_select 'input[name=?]', 'issue[custom_field_values][2]'
940 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
940 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
941 assert_select 'textarea[name=?]', 'issue[notes]'
941 assert_select 'textarea[name=?]', 'issue[notes]'
942 end
942 end
943 end
943 end
944
944
945 def test_show_should_display_update_form_with_minimal_permissions
945 def test_show_should_display_update_form_with_minimal_permissions
946 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
946 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
947 WorkflowTransition.delete_all :role_id => 1
947 WorkflowTransition.delete_all :role_id => 1
948
948
949 @request.session[:user_id] = 2
949 @request.session[:user_id] = 2
950 get :show, :id => 1
950 get :show, :id => 1
951 assert_response :success
951 assert_response :success
952
952
953 assert_select 'form#issue-form' do
953 assert_select 'form#issue-form' do
954 assert_select 'input[name=?]', 'issue[is_private]', 0
954 assert_select 'input[name=?]', 'issue[is_private]', 0
955 assert_select 'select[name=?]', 'issue[project_id]', 0
955 assert_select 'select[name=?]', 'issue[project_id]', 0
956 assert_select 'select[name=?]', 'issue[tracker_id]', 0
956 assert_select 'select[name=?]', 'issue[tracker_id]', 0
957 assert_select 'input[name=?]', 'issue[subject]', 0
957 assert_select 'input[name=?]', 'issue[subject]', 0
958 assert_select 'textarea[name=?]', 'issue[description]', 0
958 assert_select 'textarea[name=?]', 'issue[description]', 0
959 assert_select 'select[name=?]', 'issue[status_id]', 0
959 assert_select 'select[name=?]', 'issue[status_id]', 0
960 assert_select 'select[name=?]', 'issue[priority_id]', 0
960 assert_select 'select[name=?]', 'issue[priority_id]', 0
961 assert_select 'select[name=?]', 'issue[assigned_to_id]', 0
961 assert_select 'select[name=?]', 'issue[assigned_to_id]', 0
962 assert_select 'select[name=?]', 'issue[category_id]', 0
962 assert_select 'select[name=?]', 'issue[category_id]', 0
963 assert_select 'select[name=?]', 'issue[fixed_version_id]', 0
963 assert_select 'select[name=?]', 'issue[fixed_version_id]', 0
964 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
964 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
965 assert_select 'input[name=?]', 'issue[start_date]', 0
965 assert_select 'input[name=?]', 'issue[start_date]', 0
966 assert_select 'input[name=?]', 'issue[due_date]', 0
966 assert_select 'input[name=?]', 'issue[due_date]', 0
967 assert_select 'select[name=?]', 'issue[done_ratio]', 0
967 assert_select 'select[name=?]', 'issue[done_ratio]', 0
968 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
968 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
969 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
969 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
970 assert_select 'textarea[name=?]', 'issue[notes]'
970 assert_select 'textarea[name=?]', 'issue[notes]'
971 end
971 end
972 end
972 end
973
973
974 def test_show_should_display_update_form_with_workflow_permissions
974 def test_show_should_display_update_form_with_workflow_permissions
975 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
975 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
976
976
977 @request.session[:user_id] = 2
977 @request.session[:user_id] = 2
978 get :show, :id => 1
978 get :show, :id => 1
979 assert_response :success
979 assert_response :success
980
980
981 assert_select 'form#issue-form' do
981 assert_select 'form#issue-form' do
982 assert_select 'input[name=?]', 'issue[is_private]', 0
982 assert_select 'input[name=?]', 'issue[is_private]', 0
983 assert_select 'select[name=?]', 'issue[project_id]', 0
983 assert_select 'select[name=?]', 'issue[project_id]', 0
984 assert_select 'select[name=?]', 'issue[tracker_id]', 0
984 assert_select 'select[name=?]', 'issue[tracker_id]', 0
985 assert_select 'input[name=?]', 'issue[subject]', 0
985 assert_select 'input[name=?]', 'issue[subject]', 0
986 assert_select 'textarea[name=?]', 'issue[description]', 0
986 assert_select 'textarea[name=?]', 'issue[description]', 0
987 assert_select 'select[name=?]', 'issue[status_id]'
987 assert_select 'select[name=?]', 'issue[status_id]'
988 assert_select 'select[name=?]', 'issue[priority_id]', 0
988 assert_select 'select[name=?]', 'issue[priority_id]', 0
989 assert_select 'select[name=?]', 'issue[assigned_to_id]'
989 assert_select 'select[name=?]', 'issue[assigned_to_id]'
990 assert_select 'select[name=?]', 'issue[category_id]', 0
990 assert_select 'select[name=?]', 'issue[category_id]', 0
991 assert_select 'select[name=?]', 'issue[fixed_version_id]'
991 assert_select 'select[name=?]', 'issue[fixed_version_id]'
992 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
992 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
993 assert_select 'input[name=?]', 'issue[start_date]', 0
993 assert_select 'input[name=?]', 'issue[start_date]', 0
994 assert_select 'input[name=?]', 'issue[due_date]', 0
994 assert_select 'input[name=?]', 'issue[due_date]', 0
995 assert_select 'select[name=?]', 'issue[done_ratio]'
995 assert_select 'select[name=?]', 'issue[done_ratio]'
996 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
996 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
997 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
997 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
998 assert_select 'textarea[name=?]', 'issue[notes]'
998 assert_select 'textarea[name=?]', 'issue[notes]'
999 end
999 end
1000 end
1000 end
1001
1001
1002 def test_show_should_not_display_update_form_without_permissions
1002 def test_show_should_not_display_update_form_without_permissions
1003 Role.find(1).update_attribute :permissions, [:view_issues]
1003 Role.find(1).update_attribute :permissions, [:view_issues]
1004
1004
1005 @request.session[:user_id] = 2
1005 @request.session[:user_id] = 2
1006 get :show, :id => 1
1006 get :show, :id => 1
1007 assert_response :success
1007 assert_response :success
1008
1008
1009 assert_select 'form#issue-form', 0
1009 assert_select 'form#issue-form', 0
1010 end
1010 end
1011
1011
1012 def test_update_form_should_not_display_inactive_enumerations
1012 def test_update_form_should_not_display_inactive_enumerations
1013 assert !IssuePriority.find(15).active?
1013 assert !IssuePriority.find(15).active?
1014
1014
1015 @request.session[:user_id] = 2
1015 @request.session[:user_id] = 2
1016 get :show, :id => 1
1016 get :show, :id => 1
1017 assert_response :success
1017 assert_response :success
1018
1018
1019 assert_select 'form#issue-form' do
1019 assert_select 'form#issue-form' do
1020 assert_select 'select[name=?]', 'issue[priority_id]' do
1020 assert_select 'select[name=?]', 'issue[priority_id]' do
1021 assert_select 'option[value=4]'
1021 assert_select 'option[value=4]'
1022 assert_select 'option[value=15]', 0
1022 assert_select 'option[value=15]', 0
1023 end
1023 end
1024 end
1024 end
1025 end
1025 end
1026
1026
1027 def test_update_form_should_allow_attachment_upload
1027 def test_update_form_should_allow_attachment_upload
1028 @request.session[:user_id] = 2
1028 @request.session[:user_id] = 2
1029 get :show, :id => 1
1029 get :show, :id => 1
1030
1030
1031 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
1031 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
1032 assert_select 'input[type=file][name=?]', 'attachments[dummy][file]'
1032 assert_select 'input[type=file][name=?]', 'attachments[dummy][file]'
1033 end
1033 end
1034 end
1034 end
1035
1035
1036 def test_show_should_deny_anonymous_access_without_permission
1036 def test_show_should_deny_anonymous_access_without_permission
1037 Role.anonymous.remove_permission!(:view_issues)
1037 Role.anonymous.remove_permission!(:view_issues)
1038 get :show, :id => 1
1038 get :show, :id => 1
1039 assert_response :redirect
1039 assert_response :redirect
1040 end
1040 end
1041
1041
1042 def test_show_should_deny_anonymous_access_to_private_issue
1042 def test_show_should_deny_anonymous_access_to_private_issue
1043 Issue.where(:id => 1).update_all(["is_private = ?", true])
1043 Issue.where(:id => 1).update_all(["is_private = ?", true])
1044 get :show, :id => 1
1044 get :show, :id => 1
1045 assert_response :redirect
1045 assert_response :redirect
1046 end
1046 end
1047
1047
1048 def test_show_should_deny_non_member_access_without_permission
1048 def test_show_should_deny_non_member_access_without_permission
1049 Role.non_member.remove_permission!(:view_issues)
1049 Role.non_member.remove_permission!(:view_issues)
1050 @request.session[:user_id] = 9
1050 @request.session[:user_id] = 9
1051 get :show, :id => 1
1051 get :show, :id => 1
1052 assert_response 403
1052 assert_response 403
1053 end
1053 end
1054
1054
1055 def test_show_should_deny_non_member_access_to_private_issue
1055 def test_show_should_deny_non_member_access_to_private_issue
1056 Issue.where(:id => 1).update_all(["is_private = ?", true])
1056 Issue.where(:id => 1).update_all(["is_private = ?", true])
1057 @request.session[:user_id] = 9
1057 @request.session[:user_id] = 9
1058 get :show, :id => 1
1058 get :show, :id => 1
1059 assert_response 403
1059 assert_response 403
1060 end
1060 end
1061
1061
1062 def test_show_should_deny_member_access_without_permission
1062 def test_show_should_deny_member_access_without_permission
1063 Role.find(1).remove_permission!(:view_issues)
1063 Role.find(1).remove_permission!(:view_issues)
1064 @request.session[:user_id] = 2
1064 @request.session[:user_id] = 2
1065 get :show, :id => 1
1065 get :show, :id => 1
1066 assert_response 403
1066 assert_response 403
1067 end
1067 end
1068
1068
1069 def test_show_should_deny_member_access_to_private_issue_without_permission
1069 def test_show_should_deny_member_access_to_private_issue_without_permission
1070 Issue.where(:id => 1).update_all(["is_private = ?", true])
1070 Issue.where(:id => 1).update_all(["is_private = ?", true])
1071 @request.session[:user_id] = 3
1071 @request.session[:user_id] = 3
1072 get :show, :id => 1
1072 get :show, :id => 1
1073 assert_response 403
1073 assert_response 403
1074 end
1074 end
1075
1075
1076 def test_show_should_allow_author_access_to_private_issue
1076 def test_show_should_allow_author_access_to_private_issue
1077 Issue.where(:id => 1).update_all(["is_private = ?, author_id = 3", true])
1077 Issue.where(:id => 1).update_all(["is_private = ?, author_id = 3", true])
1078 @request.session[:user_id] = 3
1078 @request.session[:user_id] = 3
1079 get :show, :id => 1
1079 get :show, :id => 1
1080 assert_response :success
1080 assert_response :success
1081 end
1081 end
1082
1082
1083 def test_show_should_allow_assignee_access_to_private_issue
1083 def test_show_should_allow_assignee_access_to_private_issue
1084 Issue.where(:id => 1).update_all(["is_private = ?, assigned_to_id = 3", true])
1084 Issue.where(:id => 1).update_all(["is_private = ?, assigned_to_id = 3", true])
1085 @request.session[:user_id] = 3
1085 @request.session[:user_id] = 3
1086 get :show, :id => 1
1086 get :show, :id => 1
1087 assert_response :success
1087 assert_response :success
1088 end
1088 end
1089
1089
1090 def test_show_should_allow_member_access_to_private_issue_with_permission
1090 def test_show_should_allow_member_access_to_private_issue_with_permission
1091 Issue.where(:id => 1).update_all(["is_private = ?", true])
1091 Issue.where(:id => 1).update_all(["is_private = ?", true])
1092 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1092 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1093 @request.session[:user_id] = 3
1093 @request.session[:user_id] = 3
1094 get :show, :id => 1
1094 get :show, :id => 1
1095 assert_response :success
1095 assert_response :success
1096 end
1096 end
1097
1097
1098 def test_show_should_not_disclose_relations_to_invisible_issues
1098 def test_show_should_not_disclose_relations_to_invisible_issues
1099 Setting.cross_project_issue_relations = '1'
1099 Setting.cross_project_issue_relations = '1'
1100 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1100 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1101 # Relation to a private project issue
1101 # Relation to a private project issue
1102 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1102 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1103
1103
1104 get :show, :id => 1
1104 get :show, :id => 1
1105 assert_response :success
1105 assert_response :success
1106
1106
1107 assert_select 'div#relations' do
1107 assert_select 'div#relations' do
1108 assert_select 'a', :text => /#2$/
1108 assert_select 'a', :text => /#2$/
1109 assert_select 'a', :text => /#4$/, :count => 0
1109 assert_select 'a', :text => /#4$/, :count => 0
1110 end
1110 end
1111 end
1111 end
1112
1112
1113 def test_show_should_list_subtasks
1113 def test_show_should_list_subtasks
1114 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1114 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1115
1115
1116 get :show, :id => 1
1116 get :show, :id => 1
1117 assert_response :success
1117 assert_response :success
1118
1118
1119 assert_select 'div#issue_tree' do
1119 assert_select 'div#issue_tree' do
1120 assert_select 'td.subject', :text => /Child Issue/
1120 assert_select 'td.subject', :text => /Child Issue/
1121 end
1121 end
1122 end
1122 end
1123
1123
1124 def test_show_should_list_parents
1124 def test_show_should_list_parents
1125 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1125 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1126
1126
1127 get :show, :id => issue.id
1127 get :show, :id => issue.id
1128 assert_response :success
1128 assert_response :success
1129
1129
1130 assert_select 'div.subject' do
1130 assert_select 'div.subject' do
1131 assert_select 'h3', 'Child Issue'
1131 assert_select 'h3', 'Child Issue'
1132 assert_select 'a[href=/issues/1]'
1132 assert_select 'a[href=/issues/1]'
1133 end
1133 end
1134 end
1134 end
1135
1135
1136 def test_show_should_not_display_prev_next_links_without_query_in_session
1136 def test_show_should_not_display_prev_next_links_without_query_in_session
1137 get :show, :id => 1
1137 get :show, :id => 1
1138 assert_response :success
1138 assert_response :success
1139 assert_nil assigns(:prev_issue_id)
1139 assert_nil assigns(:prev_issue_id)
1140 assert_nil assigns(:next_issue_id)
1140 assert_nil assigns(:next_issue_id)
1141
1141
1142 assert_select 'div.next-prev-links', 0
1142 assert_select 'div.next-prev-links', 0
1143 end
1143 end
1144
1144
1145 def test_show_should_display_prev_next_links_with_query_in_session
1145 def test_show_should_display_prev_next_links_with_query_in_session
1146 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1146 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1147 @request.session['issues_index_sort'] = 'id'
1147 @request.session['issues_index_sort'] = 'id'
1148
1148
1149 with_settings :display_subprojects_issues => '0' do
1149 with_settings :display_subprojects_issues => '0' do
1150 get :show, :id => 3
1150 get :show, :id => 3
1151 end
1151 end
1152
1152
1153 assert_response :success
1153 assert_response :success
1154 # Previous and next issues for all projects
1154 # Previous and next issues for all projects
1155 assert_equal 2, assigns(:prev_issue_id)
1155 assert_equal 2, assigns(:prev_issue_id)
1156 assert_equal 5, assigns(:next_issue_id)
1156 assert_equal 5, assigns(:next_issue_id)
1157
1157
1158 count = Issue.open.visible.count
1158 count = Issue.open.visible.count
1159
1159
1160 assert_select 'div.next-prev-links' do
1160 assert_select 'div.next-prev-links' do
1161 assert_select 'a[href=/issues/2]', :text => /Previous/
1161 assert_select 'a[href=/issues/2]', :text => /Previous/
1162 assert_select 'a[href=/issues/5]', :text => /Next/
1162 assert_select 'a[href=/issues/5]', :text => /Next/
1163 assert_select 'span.position', :text => "3 of #{count}"
1163 assert_select 'span.position', :text => "3 of #{count}"
1164 end
1164 end
1165 end
1165 end
1166
1166
1167 def test_show_should_display_prev_next_links_with_saved_query_in_session
1167 def test_show_should_display_prev_next_links_with_saved_query_in_session
1168 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1,
1168 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1,
1169 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1169 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1170 :sort_criteria => [['id', 'asc']])
1170 :sort_criteria => [['id', 'asc']])
1171 @request.session[:query] = {:id => query.id, :project_id => nil}
1171 @request.session[:query] = {:id => query.id, :project_id => nil}
1172
1172
1173 get :show, :id => 11
1173 get :show, :id => 11
1174
1174
1175 assert_response :success
1175 assert_response :success
1176 assert_equal query, assigns(:query)
1176 assert_equal query, assigns(:query)
1177 # Previous and next issues for all projects
1177 # Previous and next issues for all projects
1178 assert_equal 8, assigns(:prev_issue_id)
1178 assert_equal 8, assigns(:prev_issue_id)
1179 assert_equal 12, assigns(:next_issue_id)
1179 assert_equal 12, assigns(:next_issue_id)
1180
1180
1181 assert_select 'div.next-prev-links' do
1181 assert_select 'div.next-prev-links' do
1182 assert_select 'a[href=/issues/8]', :text => /Previous/
1182 assert_select 'a[href=/issues/8]', :text => /Previous/
1183 assert_select 'a[href=/issues/12]', :text => /Next/
1183 assert_select 'a[href=/issues/12]', :text => /Next/
1184 end
1184 end
1185 end
1185 end
1186
1186
1187 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1187 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1188 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1188 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1189
1189
1190 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1190 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1191 @request.session['issues_index_sort'] = assoc_sort
1191 @request.session['issues_index_sort'] = assoc_sort
1192
1192
1193 get :show, :id => 3
1193 get :show, :id => 3
1194 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1194 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1195
1195
1196 assert_select 'div.next-prev-links' do
1196 assert_select 'div.next-prev-links' do
1197 assert_select 'a', :text => /(Previous|Next)/
1197 assert_select 'a', :text => /(Previous|Next)/
1198 end
1198 end
1199 end
1199 end
1200 end
1200 end
1201
1201
1202 def test_show_should_display_prev_next_links_with_project_query_in_session
1202 def test_show_should_display_prev_next_links_with_project_query_in_session
1203 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1203 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1204 @request.session['issues_index_sort'] = 'id'
1204 @request.session['issues_index_sort'] = 'id'
1205
1205
1206 with_settings :display_subprojects_issues => '0' do
1206 with_settings :display_subprojects_issues => '0' do
1207 get :show, :id => 3
1207 get :show, :id => 3
1208 end
1208 end
1209
1209
1210 assert_response :success
1210 assert_response :success
1211 # Previous and next issues inside project
1211 # Previous and next issues inside project
1212 assert_equal 2, assigns(:prev_issue_id)
1212 assert_equal 2, assigns(:prev_issue_id)
1213 assert_equal 7, assigns(:next_issue_id)
1213 assert_equal 7, assigns(:next_issue_id)
1214
1214
1215 assert_select 'div.next-prev-links' do
1215 assert_select 'div.next-prev-links' do
1216 assert_select 'a[href=/issues/2]', :text => /Previous/
1216 assert_select 'a[href=/issues/2]', :text => /Previous/
1217 assert_select 'a[href=/issues/7]', :text => /Next/
1217 assert_select 'a[href=/issues/7]', :text => /Next/
1218 end
1218 end
1219 end
1219 end
1220
1220
1221 def test_show_should_not_display_prev_link_for_first_issue
1221 def test_show_should_not_display_prev_link_for_first_issue
1222 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1222 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1223 @request.session['issues_index_sort'] = 'id'
1223 @request.session['issues_index_sort'] = 'id'
1224
1224
1225 with_settings :display_subprojects_issues => '0' do
1225 with_settings :display_subprojects_issues => '0' do
1226 get :show, :id => 1
1226 get :show, :id => 1
1227 end
1227 end
1228
1228
1229 assert_response :success
1229 assert_response :success
1230 assert_nil assigns(:prev_issue_id)
1230 assert_nil assigns(:prev_issue_id)
1231 assert_equal 2, assigns(:next_issue_id)
1231 assert_equal 2, assigns(:next_issue_id)
1232
1232
1233 assert_select 'div.next-prev-links' do
1233 assert_select 'div.next-prev-links' do
1234 assert_select 'a', :text => /Previous/, :count => 0
1234 assert_select 'a', :text => /Previous/, :count => 0
1235 assert_select 'a[href=/issues/2]', :text => /Next/
1235 assert_select 'a[href=/issues/2]', :text => /Next/
1236 end
1236 end
1237 end
1237 end
1238
1238
1239 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1239 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1240 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1240 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1241 @request.session['issues_index_sort'] = 'id'
1241 @request.session['issues_index_sort'] = 'id'
1242
1242
1243 get :show, :id => 1
1243 get :show, :id => 1
1244
1244
1245 assert_response :success
1245 assert_response :success
1246 assert_nil assigns(:prev_issue_id)
1246 assert_nil assigns(:prev_issue_id)
1247 assert_nil assigns(:next_issue_id)
1247 assert_nil assigns(:next_issue_id)
1248
1248
1249 assert_select 'a', :text => /Previous/, :count => 0
1249 assert_select 'a', :text => /Previous/, :count => 0
1250 assert_select 'a', :text => /Next/, :count => 0
1250 assert_select 'a', :text => /Next/, :count => 0
1251 end
1251 end
1252
1252
1253 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1253 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1254 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1254 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1255 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1255 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1256 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1256 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1257 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1257 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1258 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1258 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1259
1259
1260 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1, :filters => {},
1260 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1, :filters => {},
1261 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1261 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1262 @request.session[:query] = {:id => query.id, :project_id => nil}
1262 @request.session[:query] = {:id => query.id, :project_id => nil}
1263
1263
1264 get :show, :id => 3
1264 get :show, :id => 3
1265 assert_response :success
1265 assert_response :success
1266
1266
1267 assert_equal 2, assigns(:prev_issue_id)
1267 assert_equal 2, assigns(:prev_issue_id)
1268 assert_equal 1, assigns(:next_issue_id)
1268 assert_equal 1, assigns(:next_issue_id)
1269
1269
1270 assert_select 'div.next-prev-links' do
1270 assert_select 'div.next-prev-links' do
1271 assert_select 'a[href=/issues/2]', :text => /Previous/
1271 assert_select 'a[href=/issues/2]', :text => /Previous/
1272 assert_select 'a[href=/issues/1]', :text => /Next/
1272 assert_select 'a[href=/issues/1]', :text => /Next/
1273 end
1273 end
1274 end
1274 end
1275
1275
1276 def test_show_should_display_link_to_the_assignee
1276 def test_show_should_display_link_to_the_assignee
1277 get :show, :id => 2
1277 get :show, :id => 2
1278 assert_response :success
1278 assert_response :success
1279 assert_select '.assigned-to' do
1279 assert_select '.assigned-to' do
1280 assert_select 'a[href=/users/3]'
1280 assert_select 'a[href=/users/3]'
1281 end
1281 end
1282 end
1282 end
1283
1283
1284 def test_show_should_display_visible_changesets_from_other_projects
1284 def test_show_should_display_visible_changesets_from_other_projects
1285 project = Project.find(2)
1285 project = Project.find(2)
1286 issue = project.issues.first
1286 issue = project.issues.first
1287 issue.changeset_ids = [102]
1287 issue.changeset_ids = [102]
1288 issue.save!
1288 issue.save!
1289 # changesets from other projects should be displayed even if repository
1289 # changesets from other projects should be displayed even if repository
1290 # is disabled on issue's project
1290 # is disabled on issue's project
1291 project.disable_module! :repository
1291 project.disable_module! :repository
1292
1292
1293 @request.session[:user_id] = 2
1293 @request.session[:user_id] = 2
1294 get :show, :id => issue.id
1294 get :show, :id => issue.id
1295
1295
1296 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1296 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1297 end
1297 end
1298
1298
1299 def test_show_should_display_watchers
1299 def test_show_should_display_watchers
1300 @request.session[:user_id] = 2
1300 @request.session[:user_id] = 2
1301 Issue.find(1).add_watcher User.find(2)
1301 Issue.find(1).add_watcher User.find(2)
1302
1302
1303 get :show, :id => 1
1303 get :show, :id => 1
1304 assert_select 'div#watchers ul' do
1304 assert_select 'div#watchers ul' do
1305 assert_select 'li' do
1305 assert_select 'li' do
1306 assert_select 'a[href=/users/2]'
1306 assert_select 'a[href=/users/2]'
1307 assert_select 'a img[alt=Delete]'
1307 assert_select 'a img[alt=Delete]'
1308 end
1308 end
1309 end
1309 end
1310 end
1310 end
1311
1311
1312 def test_show_should_display_watchers_with_gravatars
1312 def test_show_should_display_watchers_with_gravatars
1313 @request.session[:user_id] = 2
1313 @request.session[:user_id] = 2
1314 Issue.find(1).add_watcher User.find(2)
1314 Issue.find(1).add_watcher User.find(2)
1315
1315
1316 with_settings :gravatar_enabled => '1' do
1316 with_settings :gravatar_enabled => '1' do
1317 get :show, :id => 1
1317 get :show, :id => 1
1318 end
1318 end
1319
1319
1320 assert_select 'div#watchers ul' do
1320 assert_select 'div#watchers ul' do
1321 assert_select 'li' do
1321 assert_select 'li' do
1322 assert_select 'img.gravatar'
1322 assert_select 'img.gravatar'
1323 assert_select 'a[href=/users/2]'
1323 assert_select 'a[href=/users/2]'
1324 assert_select 'a img[alt=Delete]'
1324 assert_select 'a img[alt=Delete]'
1325 end
1325 end
1326 end
1326 end
1327 end
1327 end
1328
1328
1329 def test_show_with_thumbnails_enabled_should_display_thumbnails
1329 def test_show_with_thumbnails_enabled_should_display_thumbnails
1330 @request.session[:user_id] = 2
1330 @request.session[:user_id] = 2
1331
1331
1332 with_settings :thumbnails_enabled => '1' do
1332 with_settings :thumbnails_enabled => '1' do
1333 get :show, :id => 14
1333 get :show, :id => 14
1334 assert_response :success
1334 assert_response :success
1335 end
1335 end
1336
1336
1337 assert_select 'div.thumbnails' do
1337 assert_select 'div.thumbnails' do
1338 assert_select 'a[href=/attachments/16/testfile.png]' do
1338 assert_select 'a[href=/attachments/16/testfile.png]' do
1339 assert_select 'img[src=/attachments/thumbnail/16]'
1339 assert_select 'img[src=/attachments/thumbnail/16]'
1340 end
1340 end
1341 end
1341 end
1342 end
1342 end
1343
1343
1344 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1344 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1345 @request.session[:user_id] = 2
1345 @request.session[:user_id] = 2
1346
1346
1347 with_settings :thumbnails_enabled => '0' do
1347 with_settings :thumbnails_enabled => '0' do
1348 get :show, :id => 14
1348 get :show, :id => 14
1349 assert_response :success
1349 assert_response :success
1350 end
1350 end
1351
1351
1352 assert_select 'div.thumbnails', 0
1352 assert_select 'div.thumbnails', 0
1353 end
1353 end
1354
1354
1355 def test_show_with_multi_custom_field
1355 def test_show_with_multi_custom_field
1356 field = CustomField.find(1)
1356 field = CustomField.find(1)
1357 field.update_attribute :multiple, true
1357 field.update_attribute :multiple, true
1358 issue = Issue.find(1)
1358 issue = Issue.find(1)
1359 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1359 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1360 issue.save!
1360 issue.save!
1361
1361
1362 get :show, :id => 1
1362 get :show, :id => 1
1363 assert_response :success
1363 assert_response :success
1364
1364
1365 assert_select 'td', :text => 'MySQL, Oracle'
1365 assert_select 'td', :text => 'MySQL, Oracle'
1366 end
1366 end
1367
1367
1368 def test_show_with_multi_user_custom_field
1368 def test_show_with_multi_user_custom_field
1369 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1369 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1370 :tracker_ids => [1], :is_for_all => true)
1370 :tracker_ids => [1], :is_for_all => true)
1371 issue = Issue.find(1)
1371 issue = Issue.find(1)
1372 issue.custom_field_values = {field.id => ['2', '3']}
1372 issue.custom_field_values = {field.id => ['2', '3']}
1373 issue.save!
1373 issue.save!
1374
1374
1375 get :show, :id => 1
1375 get :show, :id => 1
1376 assert_response :success
1376 assert_response :success
1377
1377
1378 assert_select "td.cf_#{field.id}", :text => 'Dave Lopper, John Smith' do
1378 assert_select "td.cf_#{field.id}", :text => 'Dave Lopper, John Smith' do
1379 assert_select 'a', :text => 'Dave Lopper'
1379 assert_select 'a', :text => 'Dave Lopper'
1380 assert_select 'a', :text => 'John Smith'
1380 assert_select 'a', :text => 'John Smith'
1381 end
1381 end
1382 end
1382 end
1383
1383
1384 def test_show_should_display_private_notes_with_permission_only
1384 def test_show_should_display_private_notes_with_permission_only
1385 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1385 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1386 @request.session[:user_id] = 2
1386 @request.session[:user_id] = 2
1387
1387
1388 get :show, :id => 2
1388 get :show, :id => 2
1389 assert_response :success
1389 assert_response :success
1390 assert_include journal, assigns(:journals)
1390 assert_include journal, assigns(:journals)
1391
1391
1392 Role.find(1).remove_permission! :view_private_notes
1392 Role.find(1).remove_permission! :view_private_notes
1393 get :show, :id => 2
1393 get :show, :id => 2
1394 assert_response :success
1394 assert_response :success
1395 assert_not_include journal, assigns(:journals)
1395 assert_not_include journal, assigns(:journals)
1396 end
1396 end
1397
1397
1398 def test_show_atom
1398 def test_show_atom
1399 get :show, :id => 2, :format => 'atom'
1399 get :show, :id => 2, :format => 'atom'
1400 assert_response :success
1400 assert_response :success
1401 assert_template 'journals/index'
1401 assert_template 'journals/index'
1402 # Inline image
1402 # Inline image
1403 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1403 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1404 end
1404 end
1405
1405
1406 def test_show_export_to_pdf
1406 def test_show_export_to_pdf
1407 get :show, :id => 3, :format => 'pdf'
1407 get :show, :id => 3, :format => 'pdf'
1408 assert_response :success
1408 assert_response :success
1409 assert_equal 'application/pdf', @response.content_type
1409 assert_equal 'application/pdf', @response.content_type
1410 assert @response.body.starts_with?('%PDF')
1410 assert @response.body.starts_with?('%PDF')
1411 assert_not_nil assigns(:issue)
1411 assert_not_nil assigns(:issue)
1412 end
1412 end
1413
1413
1414 def test_show_export_to_pdf_with_ancestors
1414 def test_show_export_to_pdf_with_ancestors
1415 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1415 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1416
1416
1417 get :show, :id => issue.id, :format => 'pdf'
1417 get :show, :id => issue.id, :format => 'pdf'
1418 assert_response :success
1418 assert_response :success
1419 assert_equal 'application/pdf', @response.content_type
1419 assert_equal 'application/pdf', @response.content_type
1420 assert @response.body.starts_with?('%PDF')
1420 assert @response.body.starts_with?('%PDF')
1421 end
1421 end
1422
1422
1423 def test_show_export_to_pdf_with_descendants
1423 def test_show_export_to_pdf_with_descendants
1424 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1424 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1425 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1425 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1426 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1426 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1427
1427
1428 get :show, :id => 1, :format => 'pdf'
1428 get :show, :id => 1, :format => 'pdf'
1429 assert_response :success
1429 assert_response :success
1430 assert_equal 'application/pdf', @response.content_type
1430 assert_equal 'application/pdf', @response.content_type
1431 assert @response.body.starts_with?('%PDF')
1431 assert @response.body.starts_with?('%PDF')
1432 end
1432 end
1433
1433
1434 def test_show_export_to_pdf_with_journals
1434 def test_show_export_to_pdf_with_journals
1435 get :show, :id => 1, :format => 'pdf'
1435 get :show, :id => 1, :format => 'pdf'
1436 assert_response :success
1436 assert_response :success
1437 assert_equal 'application/pdf', @response.content_type
1437 assert_equal 'application/pdf', @response.content_type
1438 assert @response.body.starts_with?('%PDF')
1438 assert @response.body.starts_with?('%PDF')
1439 end
1439 end
1440
1440
1441 def test_show_export_to_pdf_with_changesets
1441 def test_show_export_to_pdf_with_changesets
1442 [[100], [100, 101], [100, 101, 102]].each do |cs|
1442 [[100], [100, 101], [100, 101, 102]].each do |cs|
1443 issue1 = Issue.find(3)
1443 issue1 = Issue.find(3)
1444 issue1.changesets = Changeset.find(cs)
1444 issue1.changesets = Changeset.find(cs)
1445 issue1.save!
1445 issue1.save!
1446 issue = Issue.find(3)
1446 issue = Issue.find(3)
1447 assert_equal issue.changesets.count, cs.size
1447 assert_equal issue.changesets.count, cs.size
1448 get :show, :id => 3, :format => 'pdf'
1448 get :show, :id => 3, :format => 'pdf'
1449 assert_response :success
1449 assert_response :success
1450 assert_equal 'application/pdf', @response.content_type
1450 assert_equal 'application/pdf', @response.content_type
1451 assert @response.body.starts_with?('%PDF')
1451 assert @response.body.starts_with?('%PDF')
1452 end
1452 end
1453 end
1453 end
1454
1454
1455 def test_show_invalid_should_respond_with_404
1455 def test_show_invalid_should_respond_with_404
1456 get :show, :id => 999
1456 get :show, :id => 999
1457 assert_response 404
1457 assert_response 404
1458 end
1458 end
1459
1459
1460 def test_get_new
1460 def test_get_new
1461 @request.session[:user_id] = 2
1461 @request.session[:user_id] = 2
1462 get :new, :project_id => 1, :tracker_id => 1
1462 get :new, :project_id => 1, :tracker_id => 1
1463 assert_response :success
1463 assert_response :success
1464 assert_template 'new'
1464 assert_template 'new'
1465
1465
1466 assert_select 'form#issue-form' do
1466 assert_select 'form#issue-form' do
1467 assert_select 'input[name=?]', 'issue[is_private]'
1467 assert_select 'input[name=?]', 'issue[is_private]'
1468 assert_select 'select[name=?]', 'issue[project_id]', 0
1468 assert_select 'select[name=?]', 'issue[project_id]', 0
1469 assert_select 'select[name=?]', 'issue[tracker_id]'
1469 assert_select 'select[name=?]', 'issue[tracker_id]'
1470 assert_select 'input[name=?]', 'issue[subject]'
1470 assert_select 'input[name=?]', 'issue[subject]'
1471 assert_select 'textarea[name=?]', 'issue[description]'
1471 assert_select 'textarea[name=?]', 'issue[description]'
1472 assert_select 'select[name=?]', 'issue[status_id]'
1472 assert_select 'select[name=?]', 'issue[status_id]'
1473 assert_select 'select[name=?]', 'issue[priority_id]'
1473 assert_select 'select[name=?]', 'issue[priority_id]'
1474 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1474 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1475 assert_select 'select[name=?]', 'issue[category_id]'
1475 assert_select 'select[name=?]', 'issue[category_id]'
1476 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1476 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1477 assert_select 'input[name=?]', 'issue[parent_issue_id]'
1477 assert_select 'input[name=?]', 'issue[parent_issue_id]'
1478 assert_select 'input[name=?]', 'issue[start_date]'
1478 assert_select 'input[name=?]', 'issue[start_date]'
1479 assert_select 'input[name=?]', 'issue[due_date]'
1479 assert_select 'input[name=?]', 'issue[due_date]'
1480 assert_select 'select[name=?]', 'issue[done_ratio]'
1480 assert_select 'select[name=?]', 'issue[done_ratio]'
1481 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1481 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1482 assert_select 'input[name=?]', 'issue[watcher_user_ids][]'
1482 assert_select 'input[name=?]', 'issue[watcher_user_ids][]'
1483 end
1483 end
1484
1484
1485 # Be sure we don't display inactive IssuePriorities
1485 # Be sure we don't display inactive IssuePriorities
1486 assert ! IssuePriority.find(15).active?
1486 assert ! IssuePriority.find(15).active?
1487 assert_select 'select[name=?]', 'issue[priority_id]' do
1487 assert_select 'select[name=?]', 'issue[priority_id]' do
1488 assert_select 'option[value=15]', 0
1488 assert_select 'option[value=15]', 0
1489 end
1489 end
1490 end
1490 end
1491
1491
1492 def test_get_new_with_minimal_permissions
1492 def test_get_new_with_minimal_permissions
1493 Role.find(1).update_attribute :permissions, [:add_issues]
1493 Role.find(1).update_attribute :permissions, [:add_issues]
1494 WorkflowTransition.delete_all :role_id => 1
1494 WorkflowTransition.delete_all :role_id => 1
1495
1495
1496 @request.session[:user_id] = 2
1496 @request.session[:user_id] = 2
1497 get :new, :project_id => 1, :tracker_id => 1
1497 get :new, :project_id => 1, :tracker_id => 1
1498 assert_response :success
1498 assert_response :success
1499 assert_template 'new'
1499 assert_template 'new'
1500
1500
1501 assert_select 'form#issue-form' do
1501 assert_select 'form#issue-form' do
1502 assert_select 'input[name=?]', 'issue[is_private]', 0
1502 assert_select 'input[name=?]', 'issue[is_private]', 0
1503 assert_select 'select[name=?]', 'issue[project_id]', 0
1503 assert_select 'select[name=?]', 'issue[project_id]', 0
1504 assert_select 'select[name=?]', 'issue[tracker_id]'
1504 assert_select 'select[name=?]', 'issue[tracker_id]'
1505 assert_select 'input[name=?]', 'issue[subject]'
1505 assert_select 'input[name=?]', 'issue[subject]'
1506 assert_select 'textarea[name=?]', 'issue[description]'
1506 assert_select 'textarea[name=?]', 'issue[description]'
1507 assert_select 'select[name=?]', 'issue[status_id]'
1507 assert_select 'select[name=?]', 'issue[status_id]'
1508 assert_select 'select[name=?]', 'issue[priority_id]'
1508 assert_select 'select[name=?]', 'issue[priority_id]'
1509 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1509 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1510 assert_select 'select[name=?]', 'issue[category_id]'
1510 assert_select 'select[name=?]', 'issue[category_id]'
1511 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1511 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1512 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1512 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1513 assert_select 'input[name=?]', 'issue[start_date]'
1513 assert_select 'input[name=?]', 'issue[start_date]'
1514 assert_select 'input[name=?]', 'issue[due_date]'
1514 assert_select 'input[name=?]', 'issue[due_date]'
1515 assert_select 'select[name=?]', 'issue[done_ratio]'
1515 assert_select 'select[name=?]', 'issue[done_ratio]'
1516 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1516 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1517 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1517 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1518 end
1518 end
1519 end
1519 end
1520
1520
1521 def test_get_new_with_list_custom_field
1521 def test_get_new_with_list_custom_field
1522 @request.session[:user_id] = 2
1522 @request.session[:user_id] = 2
1523 get :new, :project_id => 1, :tracker_id => 1
1523 get :new, :project_id => 1, :tracker_id => 1
1524 assert_response :success
1524 assert_response :success
1525 assert_template 'new'
1525 assert_template 'new'
1526
1526
1527 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1527 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1528 assert_select 'option', 4
1528 assert_select 'option', 4
1529 assert_select 'option[value=MySQL]', :text => 'MySQL'
1529 assert_select 'option[value=MySQL]', :text => 'MySQL'
1530 end
1530 end
1531 end
1531 end
1532
1532
1533 def test_get_new_with_multi_custom_field
1533 def test_get_new_with_multi_custom_field
1534 field = IssueCustomField.find(1)
1534 field = IssueCustomField.find(1)
1535 field.update_attribute :multiple, true
1535 field.update_attribute :multiple, true
1536
1536
1537 @request.session[:user_id] = 2
1537 @request.session[:user_id] = 2
1538 get :new, :project_id => 1, :tracker_id => 1
1538 get :new, :project_id => 1, :tracker_id => 1
1539 assert_response :success
1539 assert_response :success
1540 assert_template 'new'
1540 assert_template 'new'
1541
1541
1542 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1542 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1543 assert_select 'option', 3
1543 assert_select 'option', 3
1544 assert_select 'option[value=MySQL]', :text => 'MySQL'
1544 assert_select 'option[value=MySQL]', :text => 'MySQL'
1545 end
1545 end
1546 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1546 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1547 end
1547 end
1548
1548
1549 def test_get_new_with_multi_user_custom_field
1549 def test_get_new_with_multi_user_custom_field
1550 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1550 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1551 :tracker_ids => [1], :is_for_all => true)
1551 :tracker_ids => [1], :is_for_all => true)
1552
1552
1553 @request.session[:user_id] = 2
1553 @request.session[:user_id] = 2
1554 get :new, :project_id => 1, :tracker_id => 1
1554 get :new, :project_id => 1, :tracker_id => 1
1555 assert_response :success
1555 assert_response :success
1556 assert_template 'new'
1556 assert_template 'new'
1557
1557
1558 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1558 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1559 assert_select 'option', Project.find(1).users.count
1559 assert_select 'option', Project.find(1).users.count
1560 assert_select 'option[value=2]', :text => 'John Smith'
1560 assert_select 'option[value=2]', :text => 'John Smith'
1561 end
1561 end
1562 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1562 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1563 end
1563 end
1564
1564
1565 def test_get_new_with_date_custom_field
1565 def test_get_new_with_date_custom_field
1566 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1566 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1567
1567
1568 @request.session[:user_id] = 2
1568 @request.session[:user_id] = 2
1569 get :new, :project_id => 1, :tracker_id => 1
1569 get :new, :project_id => 1, :tracker_id => 1
1570 assert_response :success
1570 assert_response :success
1571
1571
1572 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1572 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1573 end
1573 end
1574
1574
1575 def test_get_new_with_text_custom_field
1575 def test_get_new_with_text_custom_field
1576 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1576 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1577
1577
1578 @request.session[:user_id] = 2
1578 @request.session[:user_id] = 2
1579 get :new, :project_id => 1, :tracker_id => 1
1579 get :new, :project_id => 1, :tracker_id => 1
1580 assert_response :success
1580 assert_response :success
1581
1581
1582 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1582 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1583 end
1583 end
1584
1584
1585 def test_get_new_without_default_start_date_is_creation_date
1585 def test_get_new_without_default_start_date_is_creation_date
1586 with_settings :default_issue_start_date_to_creation_date => 0 do
1586 with_settings :default_issue_start_date_to_creation_date => 0 do
1587 @request.session[:user_id] = 2
1587 @request.session[:user_id] = 2
1588 get :new, :project_id => 1, :tracker_id => 1
1588 get :new, :project_id => 1, :tracker_id => 1
1589 assert_response :success
1589 assert_response :success
1590 assert_template 'new'
1590 assert_template 'new'
1591 assert_select 'input[name=?]', 'issue[start_date]'
1591 assert_select 'input[name=?]', 'issue[start_date]'
1592 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1592 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1593 end
1593 end
1594 end
1594 end
1595
1595
1596 def test_get_new_with_default_start_date_is_creation_date
1596 def test_get_new_with_default_start_date_is_creation_date
1597 with_settings :default_issue_start_date_to_creation_date => 1 do
1597 with_settings :default_issue_start_date_to_creation_date => 1 do
1598 @request.session[:user_id] = 2
1598 @request.session[:user_id] = 2
1599 get :new, :project_id => 1, :tracker_id => 1
1599 get :new, :project_id => 1, :tracker_id => 1
1600 assert_response :success
1600 assert_response :success
1601 assert_template 'new'
1601 assert_template 'new'
1602 assert_select 'input[name=?][value=?]', 'issue[start_date]',
1602 assert_select 'input[name=?][value=?]', 'issue[start_date]',
1603 Date.today.to_s
1603 Date.today.to_s
1604 end
1604 end
1605 end
1605 end
1606
1606
1607 def test_get_new_form_should_allow_attachment_upload
1607 def test_get_new_form_should_allow_attachment_upload
1608 @request.session[:user_id] = 2
1608 @request.session[:user_id] = 2
1609 get :new, :project_id => 1, :tracker_id => 1
1609 get :new, :project_id => 1, :tracker_id => 1
1610
1610
1611 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1611 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1612 assert_select 'input[name=?][type=file]', 'attachments[dummy][file]'
1612 assert_select 'input[name=?][type=file]', 'attachments[dummy][file]'
1613 end
1613 end
1614 end
1614 end
1615
1615
1616 def test_get_new_should_prefill_the_form_from_params
1616 def test_get_new_should_prefill_the_form_from_params
1617 @request.session[:user_id] = 2
1617 @request.session[:user_id] = 2
1618 get :new, :project_id => 1,
1618 get :new, :project_id => 1,
1619 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1619 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1620
1620
1621 issue = assigns(:issue)
1621 issue = assigns(:issue)
1622 assert_equal 3, issue.tracker_id
1622 assert_equal 3, issue.tracker_id
1623 assert_equal 'Prefilled', issue.description
1623 assert_equal 'Prefilled', issue.description
1624 assert_equal 'Custom field value', issue.custom_field_value(2)
1624 assert_equal 'Custom field value', issue.custom_field_value(2)
1625
1625
1626 assert_select 'select[name=?]', 'issue[tracker_id]' do
1626 assert_select 'select[name=?]', 'issue[tracker_id]' do
1627 assert_select 'option[value=3][selected=selected]'
1627 assert_select 'option[value=3][selected=selected]'
1628 end
1628 end
1629 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1629 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1630 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1630 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1631 end
1631 end
1632
1632
1633 def test_get_new_should_mark_required_fields
1633 def test_get_new_should_mark_required_fields
1634 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1634 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1635 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1635 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1636 WorkflowPermission.delete_all
1636 WorkflowPermission.delete_all
1637 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1637 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1638 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1638 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1639 @request.session[:user_id] = 2
1639 @request.session[:user_id] = 2
1640
1640
1641 get :new, :project_id => 1
1641 get :new, :project_id => 1
1642 assert_response :success
1642 assert_response :success
1643 assert_template 'new'
1643 assert_template 'new'
1644
1644
1645 assert_select 'label[for=issue_start_date]' do
1645 assert_select 'label[for=issue_start_date]' do
1646 assert_select 'span[class=required]', 0
1646 assert_select 'span[class=required]', 0
1647 end
1647 end
1648 assert_select 'label[for=issue_due_date]' do
1648 assert_select 'label[for=issue_due_date]' do
1649 assert_select 'span[class=required]'
1649 assert_select 'span[class=required]'
1650 end
1650 end
1651 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1651 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1652 assert_select 'span[class=required]', 0
1652 assert_select 'span[class=required]', 0
1653 end
1653 end
1654 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1654 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1655 assert_select 'span[class=required]'
1655 assert_select 'span[class=required]'
1656 end
1656 end
1657 end
1657 end
1658
1658
1659 def test_get_new_should_not_display_readonly_fields
1659 def test_get_new_should_not_display_readonly_fields
1660 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1660 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1661 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1661 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1662 WorkflowPermission.delete_all
1662 WorkflowPermission.delete_all
1663 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1663 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1664 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1664 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1665 @request.session[:user_id] = 2
1665 @request.session[:user_id] = 2
1666
1666
1667 get :new, :project_id => 1
1667 get :new, :project_id => 1
1668 assert_response :success
1668 assert_response :success
1669 assert_template 'new'
1669 assert_template 'new'
1670
1670
1671 assert_select 'input[name=?]', 'issue[start_date]'
1671 assert_select 'input[name=?]', 'issue[start_date]'
1672 assert_select 'input[name=?]', 'issue[due_date]', 0
1672 assert_select 'input[name=?]', 'issue[due_date]', 0
1673 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1673 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1674 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1674 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1675 end
1675 end
1676
1676
1677 def test_get_new_without_tracker_id
1677 def test_get_new_without_tracker_id
1678 @request.session[:user_id] = 2
1678 @request.session[:user_id] = 2
1679 get :new, :project_id => 1
1679 get :new, :project_id => 1
1680 assert_response :success
1680 assert_response :success
1681 assert_template 'new'
1681 assert_template 'new'
1682
1682
1683 issue = assigns(:issue)
1683 issue = assigns(:issue)
1684 assert_not_nil issue
1684 assert_not_nil issue
1685 assert_equal Project.find(1).trackers.first, issue.tracker
1685 assert_equal Project.find(1).trackers.first, issue.tracker
1686 end
1686 end
1687
1687
1688 def test_get_new_with_no_default_status_should_display_an_error
1688 def test_get_new_with_no_default_status_should_display_an_error
1689 @request.session[:user_id] = 2
1689 @request.session[:user_id] = 2
1690 IssueStatus.delete_all
1690 IssueStatus.delete_all
1691
1691
1692 get :new, :project_id => 1
1692 get :new, :project_id => 1
1693 assert_response 500
1693 assert_response 500
1694 assert_error_tag :content => /No default issue/
1694 assert_error_tag :content => /No default issue/
1695 end
1695 end
1696
1696
1697 def test_get_new_with_no_tracker_should_display_an_error
1697 def test_get_new_with_no_tracker_should_display_an_error
1698 @request.session[:user_id] = 2
1698 @request.session[:user_id] = 2
1699 Tracker.delete_all
1699 Tracker.delete_all
1700
1700
1701 get :new, :project_id => 1
1701 get :new, :project_id => 1
1702 assert_response 500
1702 assert_response 500
1703 assert_error_tag :content => /No tracker/
1703 assert_error_tag :content => /No tracker/
1704 end
1704 end
1705
1705
1706 def test_update_form_for_new_issue
1706 def test_update_form_for_new_issue
1707 @request.session[:user_id] = 2
1707 @request.session[:user_id] = 2
1708 xhr :post, :update_form, :project_id => 1,
1708 xhr :post, :update_form, :project_id => 1,
1709 :issue => {:tracker_id => 2,
1709 :issue => {:tracker_id => 2,
1710 :subject => 'This is the test_new issue',
1710 :subject => 'This is the test_new issue',
1711 :description => 'This is the description',
1711 :description => 'This is the description',
1712 :priority_id => 5}
1712 :priority_id => 5}
1713 assert_response :success
1713 assert_response :success
1714 assert_template 'update_form'
1714 assert_template 'update_form'
1715 assert_template :partial => '_form'
1715 assert_template :partial => '_form'
1716 assert_equal 'text/javascript', response.content_type
1716 assert_equal 'text/javascript', response.content_type
1717
1717
1718 issue = assigns(:issue)
1718 issue = assigns(:issue)
1719 assert_kind_of Issue, issue
1719 assert_kind_of Issue, issue
1720 assert_equal 1, issue.project_id
1720 assert_equal 1, issue.project_id
1721 assert_equal 2, issue.tracker_id
1721 assert_equal 2, issue.tracker_id
1722 assert_equal 'This is the test_new issue', issue.subject
1722 assert_equal 'This is the test_new issue', issue.subject
1723 end
1723 end
1724
1724
1725 def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status
1725 def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status
1726 @request.session[:user_id] = 2
1726 @request.session[:user_id] = 2
1727 WorkflowTransition.delete_all
1727 WorkflowTransition.delete_all
1728 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1728 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1729 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1729 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1730 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1730 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1731
1731
1732 xhr :post, :update_form, :project_id => 1,
1732 xhr :post, :update_form, :project_id => 1,
1733 :issue => {:tracker_id => 1,
1733 :issue => {:tracker_id => 1,
1734 :status_id => 5,
1734 :status_id => 5,
1735 :subject => 'This is an issue'}
1735 :subject => 'This is an issue'}
1736
1736
1737 assert_equal 5, assigns(:issue).status_id
1737 assert_equal 5, assigns(:issue).status_id
1738 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1738 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1739 end
1739 end
1740
1740
1741 def test_post_create
1741 def test_post_create
1742 @request.session[:user_id] = 2
1742 @request.session[:user_id] = 2
1743 assert_difference 'Issue.count' do
1743 assert_difference 'Issue.count' do
1744 post :create, :project_id => 1,
1744 post :create, :project_id => 1,
1745 :issue => {:tracker_id => 3,
1745 :issue => {:tracker_id => 3,
1746 :status_id => 2,
1746 :status_id => 2,
1747 :subject => 'This is the test_new issue',
1747 :subject => 'This is the test_new issue',
1748 :description => 'This is the description',
1748 :description => 'This is the description',
1749 :priority_id => 5,
1749 :priority_id => 5,
1750 :start_date => '2010-11-07',
1750 :start_date => '2010-11-07',
1751 :estimated_hours => '',
1751 :estimated_hours => '',
1752 :custom_field_values => {'2' => 'Value for field 2'}}
1752 :custom_field_values => {'2' => 'Value for field 2'}}
1753 end
1753 end
1754 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1754 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1755
1755
1756 issue = Issue.find_by_subject('This is the test_new issue')
1756 issue = Issue.find_by_subject('This is the test_new issue')
1757 assert_not_nil issue
1757 assert_not_nil issue
1758 assert_equal 2, issue.author_id
1758 assert_equal 2, issue.author_id
1759 assert_equal 3, issue.tracker_id
1759 assert_equal 3, issue.tracker_id
1760 assert_equal 2, issue.status_id
1760 assert_equal 2, issue.status_id
1761 assert_equal Date.parse('2010-11-07'), issue.start_date
1761 assert_equal Date.parse('2010-11-07'), issue.start_date
1762 assert_nil issue.estimated_hours
1762 assert_nil issue.estimated_hours
1763 v = issue.custom_values.where(:custom_field_id => 2).first
1763 v = issue.custom_values.where(:custom_field_id => 2).first
1764 assert_not_nil v
1764 assert_not_nil v
1765 assert_equal 'Value for field 2', v.value
1765 assert_equal 'Value for field 2', v.value
1766 end
1766 end
1767
1767
1768 def test_post_new_with_group_assignment
1768 def test_post_new_with_group_assignment
1769 group = Group.find(11)
1769 group = Group.find(11)
1770 project = Project.find(1)
1770 project = Project.find(1)
1771 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1771 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1772
1772
1773 with_settings :issue_group_assignment => '1' do
1773 with_settings :issue_group_assignment => '1' do
1774 @request.session[:user_id] = 2
1774 @request.session[:user_id] = 2
1775 assert_difference 'Issue.count' do
1775 assert_difference 'Issue.count' do
1776 post :create, :project_id => project.id,
1776 post :create, :project_id => project.id,
1777 :issue => {:tracker_id => 3,
1777 :issue => {:tracker_id => 3,
1778 :status_id => 1,
1778 :status_id => 1,
1779 :subject => 'This is the test_new_with_group_assignment issue',
1779 :subject => 'This is the test_new_with_group_assignment issue',
1780 :assigned_to_id => group.id}
1780 :assigned_to_id => group.id}
1781 end
1781 end
1782 end
1782 end
1783 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1783 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1784
1784
1785 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1785 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1786 assert_not_nil issue
1786 assert_not_nil issue
1787 assert_equal group, issue.assigned_to
1787 assert_equal group, issue.assigned_to
1788 end
1788 end
1789
1789
1790 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1790 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1791 with_settings :default_issue_start_date_to_creation_date => 0 do
1791 with_settings :default_issue_start_date_to_creation_date => 0 do
1792 @request.session[:user_id] = 2
1792 @request.session[:user_id] = 2
1793 assert_difference 'Issue.count' do
1793 assert_difference 'Issue.count' do
1794 post :create, :project_id => 1,
1794 post :create, :project_id => 1,
1795 :issue => {:tracker_id => 3,
1795 :issue => {:tracker_id => 3,
1796 :status_id => 2,
1796 :status_id => 2,
1797 :subject => 'This is the test_new issue',
1797 :subject => 'This is the test_new issue',
1798 :description => 'This is the description',
1798 :description => 'This is the description',
1799 :priority_id => 5,
1799 :priority_id => 5,
1800 :estimated_hours => '',
1800 :estimated_hours => '',
1801 :custom_field_values => {'2' => 'Value for field 2'}}
1801 :custom_field_values => {'2' => 'Value for field 2'}}
1802 end
1802 end
1803 assert_redirected_to :controller => 'issues', :action => 'show',
1803 assert_redirected_to :controller => 'issues', :action => 'show',
1804 :id => Issue.last.id
1804 :id => Issue.last.id
1805 issue = Issue.find_by_subject('This is the test_new issue')
1805 issue = Issue.find_by_subject('This is the test_new issue')
1806 assert_not_nil issue
1806 assert_not_nil issue
1807 assert_nil issue.start_date
1807 assert_nil issue.start_date
1808 end
1808 end
1809 end
1809 end
1810
1810
1811 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1811 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1812 with_settings :default_issue_start_date_to_creation_date => 1 do
1812 with_settings :default_issue_start_date_to_creation_date => 1 do
1813 @request.session[:user_id] = 2
1813 @request.session[:user_id] = 2
1814 assert_difference 'Issue.count' do
1814 assert_difference 'Issue.count' do
1815 post :create, :project_id => 1,
1815 post :create, :project_id => 1,
1816 :issue => {:tracker_id => 3,
1816 :issue => {:tracker_id => 3,
1817 :status_id => 2,
1817 :status_id => 2,
1818 :subject => 'This is the test_new issue',
1818 :subject => 'This is the test_new issue',
1819 :description => 'This is the description',
1819 :description => 'This is the description',
1820 :priority_id => 5,
1820 :priority_id => 5,
1821 :estimated_hours => '',
1821 :estimated_hours => '',
1822 :custom_field_values => {'2' => 'Value for field 2'}}
1822 :custom_field_values => {'2' => 'Value for field 2'}}
1823 end
1823 end
1824 assert_redirected_to :controller => 'issues', :action => 'show',
1824 assert_redirected_to :controller => 'issues', :action => 'show',
1825 :id => Issue.last.id
1825 :id => Issue.last.id
1826 issue = Issue.find_by_subject('This is the test_new issue')
1826 issue = Issue.find_by_subject('This is the test_new issue')
1827 assert_not_nil issue
1827 assert_not_nil issue
1828 assert_equal Date.today, issue.start_date
1828 assert_equal Date.today, issue.start_date
1829 end
1829 end
1830 end
1830 end
1831
1831
1832 def test_post_create_and_continue
1832 def test_post_create_and_continue
1833 @request.session[:user_id] = 2
1833 @request.session[:user_id] = 2
1834 assert_difference 'Issue.count' do
1834 assert_difference 'Issue.count' do
1835 post :create, :project_id => 1,
1835 post :create, :project_id => 1,
1836 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1836 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1837 :continue => ''
1837 :continue => ''
1838 end
1838 end
1839
1839
1840 issue = Issue.order('id DESC').first
1840 issue = Issue.order('id DESC').first
1841 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1841 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1842 assert_not_nil flash[:notice], "flash was not set"
1842 assert_not_nil flash[:notice], "flash was not set"
1843 assert_include %|<a href="/issues/#{issue.id}" title="This is first issue">##{issue.id}</a>|, flash[:notice], "issue link not found in the flash message"
1843 assert_include %|<a href="/issues/#{issue.id}" title="This is first issue">##{issue.id}</a>|, flash[:notice], "issue link not found in the flash message"
1844 end
1844 end
1845
1845
1846 def test_post_create_without_custom_fields_param
1846 def test_post_create_without_custom_fields_param
1847 @request.session[:user_id] = 2
1847 @request.session[:user_id] = 2
1848 assert_difference 'Issue.count' do
1848 assert_difference 'Issue.count' do
1849 post :create, :project_id => 1,
1849 post :create, :project_id => 1,
1850 :issue => {:tracker_id => 1,
1850 :issue => {:tracker_id => 1,
1851 :subject => 'This is the test_new issue',
1851 :subject => 'This is the test_new issue',
1852 :description => 'This is the description',
1852 :description => 'This is the description',
1853 :priority_id => 5}
1853 :priority_id => 5}
1854 end
1854 end
1855 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1855 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1856 end
1856 end
1857
1857
1858 def test_post_create_with_multi_custom_field
1858 def test_post_create_with_multi_custom_field
1859 field = IssueCustomField.find_by_name('Database')
1859 field = IssueCustomField.find_by_name('Database')
1860 field.update_attribute(:multiple, true)
1860 field.update_attribute(:multiple, true)
1861
1861
1862 @request.session[:user_id] = 2
1862 @request.session[:user_id] = 2
1863 assert_difference 'Issue.count' do
1863 assert_difference 'Issue.count' do
1864 post :create, :project_id => 1,
1864 post :create, :project_id => 1,
1865 :issue => {:tracker_id => 1,
1865 :issue => {:tracker_id => 1,
1866 :subject => 'This is the test_new issue',
1866 :subject => 'This is the test_new issue',
1867 :description => 'This is the description',
1867 :description => 'This is the description',
1868 :priority_id => 5,
1868 :priority_id => 5,
1869 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1869 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1870 end
1870 end
1871 assert_response 302
1871 assert_response 302
1872 issue = Issue.order('id DESC').first
1872 issue = Issue.order('id DESC').first
1873 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1873 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1874 end
1874 end
1875
1875
1876 def test_post_create_with_empty_multi_custom_field
1876 def test_post_create_with_empty_multi_custom_field
1877 field = IssueCustomField.find_by_name('Database')
1877 field = IssueCustomField.find_by_name('Database')
1878 field.update_attribute(:multiple, true)
1878 field.update_attribute(:multiple, true)
1879
1879
1880 @request.session[:user_id] = 2
1880 @request.session[:user_id] = 2
1881 assert_difference 'Issue.count' do
1881 assert_difference 'Issue.count' do
1882 post :create, :project_id => 1,
1882 post :create, :project_id => 1,
1883 :issue => {:tracker_id => 1,
1883 :issue => {:tracker_id => 1,
1884 :subject => 'This is the test_new issue',
1884 :subject => 'This is the test_new issue',
1885 :description => 'This is the description',
1885 :description => 'This is the description',
1886 :priority_id => 5,
1886 :priority_id => 5,
1887 :custom_field_values => {'1' => ['']}}
1887 :custom_field_values => {'1' => ['']}}
1888 end
1888 end
1889 assert_response 302
1889 assert_response 302
1890 issue = Issue.order('id DESC').first
1890 issue = Issue.order('id DESC').first
1891 assert_equal [''], issue.custom_field_value(1).sort
1891 assert_equal [''], issue.custom_field_value(1).sort
1892 end
1892 end
1893
1893
1894 def test_post_create_with_multi_user_custom_field
1894 def test_post_create_with_multi_user_custom_field
1895 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1895 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1896 :tracker_ids => [1], :is_for_all => true)
1896 :tracker_ids => [1], :is_for_all => true)
1897
1897
1898 @request.session[:user_id] = 2
1898 @request.session[:user_id] = 2
1899 assert_difference 'Issue.count' do
1899 assert_difference 'Issue.count' do
1900 post :create, :project_id => 1,
1900 post :create, :project_id => 1,
1901 :issue => {:tracker_id => 1,
1901 :issue => {:tracker_id => 1,
1902 :subject => 'This is the test_new issue',
1902 :subject => 'This is the test_new issue',
1903 :description => 'This is the description',
1903 :description => 'This is the description',
1904 :priority_id => 5,
1904 :priority_id => 5,
1905 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1905 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1906 end
1906 end
1907 assert_response 302
1907 assert_response 302
1908 issue = Issue.order('id DESC').first
1908 issue = Issue.order('id DESC').first
1909 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1909 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1910 end
1910 end
1911
1911
1912 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1912 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1913 field = IssueCustomField.find_by_name('Database')
1913 field = IssueCustomField.find_by_name('Database')
1914 field.update_attribute(:is_required, true)
1914 field.update_attribute(:is_required, true)
1915
1915
1916 @request.session[:user_id] = 2
1916 @request.session[:user_id] = 2
1917 assert_no_difference 'Issue.count' do
1917 assert_no_difference 'Issue.count' do
1918 post :create, :project_id => 1,
1918 post :create, :project_id => 1,
1919 :issue => {:tracker_id => 1,
1919 :issue => {:tracker_id => 1,
1920 :subject => 'This is the test_new issue',
1920 :subject => 'This is the test_new issue',
1921 :description => 'This is the description',
1921 :description => 'This is the description',
1922 :priority_id => 5}
1922 :priority_id => 5}
1923 end
1923 end
1924 assert_response :success
1924 assert_response :success
1925 assert_template 'new'
1925 assert_template 'new'
1926 issue = assigns(:issue)
1926 issue = assigns(:issue)
1927 assert_not_nil issue
1927 assert_not_nil issue
1928 assert_error_tag :content => /Database #{ESCAPED_CANT} be blank/
1928 assert_error_tag :content => /Database #{ESCAPED_CANT} be blank/
1929 end
1929 end
1930
1930
1931 def test_create_should_validate_required_fields
1931 def test_create_should_validate_required_fields
1932 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1932 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1933 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1933 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1934 WorkflowPermission.delete_all
1934 WorkflowPermission.delete_all
1935 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1935 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1936 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1936 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1937 @request.session[:user_id] = 2
1937 @request.session[:user_id] = 2
1938
1938
1939 assert_no_difference 'Issue.count' do
1939 assert_no_difference 'Issue.count' do
1940 post :create, :project_id => 1, :issue => {
1940 post :create, :project_id => 1, :issue => {
1941 :tracker_id => 2,
1941 :tracker_id => 2,
1942 :status_id => 1,
1942 :status_id => 1,
1943 :subject => 'Test',
1943 :subject => 'Test',
1944 :start_date => '',
1944 :start_date => '',
1945 :due_date => '',
1945 :due_date => '',
1946 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
1946 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
1947 }
1947 }
1948 assert_response :success
1948 assert_response :success
1949 assert_template 'new'
1949 assert_template 'new'
1950 end
1950 end
1951
1951
1952 assert_error_tag :content => /Due date #{ESCAPED_CANT} be blank/i
1952 assert_error_tag :content => /Due date #{ESCAPED_CANT} be blank/i
1953 assert_error_tag :content => /Bar #{ESCAPED_CANT} be blank/i
1953 assert_error_tag :content => /Bar #{ESCAPED_CANT} be blank/i
1954 end
1954 end
1955
1955
1956 def test_create_should_ignore_readonly_fields
1956 def test_create_should_ignore_readonly_fields
1957 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1957 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1958 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1958 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1959 WorkflowPermission.delete_all
1959 WorkflowPermission.delete_all
1960 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1960 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1961 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1961 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1962 @request.session[:user_id] = 2
1962 @request.session[:user_id] = 2
1963
1963
1964 assert_difference 'Issue.count' do
1964 assert_difference 'Issue.count' do
1965 post :create, :project_id => 1, :issue => {
1965 post :create, :project_id => 1, :issue => {
1966 :tracker_id => 2,
1966 :tracker_id => 2,
1967 :status_id => 1,
1967 :status_id => 1,
1968 :subject => 'Test',
1968 :subject => 'Test',
1969 :start_date => '2012-07-14',
1969 :start_date => '2012-07-14',
1970 :due_date => '2012-07-16',
1970 :due_date => '2012-07-16',
1971 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
1971 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
1972 }
1972 }
1973 assert_response 302
1973 assert_response 302
1974 end
1974 end
1975
1975
1976 issue = Issue.order('id DESC').first
1976 issue = Issue.order('id DESC').first
1977 assert_equal Date.parse('2012-07-14'), issue.start_date
1977 assert_equal Date.parse('2012-07-14'), issue.start_date
1978 assert_nil issue.due_date
1978 assert_nil issue.due_date
1979 assert_equal 'value1', issue.custom_field_value(cf1)
1979 assert_equal 'value1', issue.custom_field_value(cf1)
1980 assert_nil issue.custom_field_value(cf2)
1980 assert_nil issue.custom_field_value(cf2)
1981 end
1981 end
1982
1982
1983 def test_post_create_with_watchers
1983 def test_post_create_with_watchers
1984 @request.session[:user_id] = 2
1984 @request.session[:user_id] = 2
1985 ActionMailer::Base.deliveries.clear
1985 ActionMailer::Base.deliveries.clear
1986
1986
1987 assert_difference 'Watcher.count', 2 do
1987 assert_difference 'Watcher.count', 2 do
1988 post :create, :project_id => 1,
1988 post :create, :project_id => 1,
1989 :issue => {:tracker_id => 1,
1989 :issue => {:tracker_id => 1,
1990 :subject => 'This is a new issue with watchers',
1990 :subject => 'This is a new issue with watchers',
1991 :description => 'This is the description',
1991 :description => 'This is the description',
1992 :priority_id => 5,
1992 :priority_id => 5,
1993 :watcher_user_ids => ['2', '3']}
1993 :watcher_user_ids => ['2', '3']}
1994 end
1994 end
1995 issue = Issue.find_by_subject('This is a new issue with watchers')
1995 issue = Issue.find_by_subject('This is a new issue with watchers')
1996 assert_not_nil issue
1996 assert_not_nil issue
1997 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1997 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1998
1998
1999 # Watchers added
1999 # Watchers added
2000 assert_equal [2, 3], issue.watcher_user_ids.sort
2000 assert_equal [2, 3], issue.watcher_user_ids.sort
2001 assert issue.watched_by?(User.find(3))
2001 assert issue.watched_by?(User.find(3))
2002 # Watchers notified
2002 # Watchers notified
2003 mail = ActionMailer::Base.deliveries.last
2003 mail = ActionMailer::Base.deliveries.last
2004 assert_not_nil mail
2004 assert_not_nil mail
2005 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
2005 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
2006 end
2006 end
2007
2007
2008 def test_post_create_subissue
2008 def test_post_create_subissue
2009 @request.session[:user_id] = 2
2009 @request.session[:user_id] = 2
2010
2010
2011 assert_difference 'Issue.count' do
2011 assert_difference 'Issue.count' do
2012 post :create, :project_id => 1,
2012 post :create, :project_id => 1,
2013 :issue => {:tracker_id => 1,
2013 :issue => {:tracker_id => 1,
2014 :subject => 'This is a child issue',
2014 :subject => 'This is a child issue',
2015 :parent_issue_id => '2'}
2015 :parent_issue_id => '2'}
2016 assert_response 302
2016 assert_response 302
2017 end
2017 end
2018 issue = Issue.order('id DESC').first
2018 issue = Issue.order('id DESC').first
2019 assert_equal Issue.find(2), issue.parent
2019 assert_equal Issue.find(2), issue.parent
2020 end
2020 end
2021
2021
2022 def test_post_create_subissue_with_sharp_parent_id
2022 def test_post_create_subissue_with_sharp_parent_id
2023 @request.session[:user_id] = 2
2023 @request.session[:user_id] = 2
2024
2024
2025 assert_difference 'Issue.count' do
2025 assert_difference 'Issue.count' do
2026 post :create, :project_id => 1,
2026 post :create, :project_id => 1,
2027 :issue => {:tracker_id => 1,
2027 :issue => {:tracker_id => 1,
2028 :subject => 'This is a child issue',
2028 :subject => 'This is a child issue',
2029 :parent_issue_id => '#2'}
2029 :parent_issue_id => '#2'}
2030 assert_response 302
2030 assert_response 302
2031 end
2031 end
2032 issue = Issue.order('id DESC').first
2032 issue = Issue.order('id DESC').first
2033 assert_equal Issue.find(2), issue.parent
2033 assert_equal Issue.find(2), issue.parent
2034 end
2034 end
2035
2035
2036 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
2036 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
2037 @request.session[:user_id] = 2
2037 @request.session[:user_id] = 2
2038
2038
2039 assert_no_difference 'Issue.count' do
2039 assert_no_difference 'Issue.count' do
2040 post :create, :project_id => 1,
2040 post :create, :project_id => 1,
2041 :issue => {:tracker_id => 1,
2041 :issue => {:tracker_id => 1,
2042 :subject => 'This is a child issue',
2042 :subject => 'This is a child issue',
2043 :parent_issue_id => '4'}
2043 :parent_issue_id => '4'}
2044
2044
2045 assert_response :success
2045 assert_response :success
2046 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
2046 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
2047 assert_error_tag :content => /Parent task is invalid/i
2047 assert_error_tag :content => /Parent task is invalid/i
2048 end
2048 end
2049 end
2049 end
2050
2050
2051 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
2051 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
2052 @request.session[:user_id] = 2
2052 @request.session[:user_id] = 2
2053
2053
2054 assert_no_difference 'Issue.count' do
2054 assert_no_difference 'Issue.count' do
2055 post :create, :project_id => 1,
2055 post :create, :project_id => 1,
2056 :issue => {:tracker_id => 1,
2056 :issue => {:tracker_id => 1,
2057 :subject => 'This is a child issue',
2057 :subject => 'This is a child issue',
2058 :parent_issue_id => '01ABC'}
2058 :parent_issue_id => '01ABC'}
2059
2059
2060 assert_response :success
2060 assert_response :success
2061 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
2061 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
2062 assert_error_tag :content => /Parent task is invalid/i
2062 assert_error_tag :content => /Parent task is invalid/i
2063 end
2063 end
2064 end
2064 end
2065
2065
2066 def test_post_create_private
2066 def test_post_create_private
2067 @request.session[:user_id] = 2
2067 @request.session[:user_id] = 2
2068
2068
2069 assert_difference 'Issue.count' do
2069 assert_difference 'Issue.count' do
2070 post :create, :project_id => 1,
2070 post :create, :project_id => 1,
2071 :issue => {:tracker_id => 1,
2071 :issue => {:tracker_id => 1,
2072 :subject => 'This is a private issue',
2072 :subject => 'This is a private issue',
2073 :is_private => '1'}
2073 :is_private => '1'}
2074 end
2074 end
2075 issue = Issue.order('id DESC').first
2075 issue = Issue.order('id DESC').first
2076 assert issue.is_private?
2076 assert issue.is_private?
2077 end
2077 end
2078
2078
2079 def test_post_create_private_with_set_own_issues_private_permission
2079 def test_post_create_private_with_set_own_issues_private_permission
2080 role = Role.find(1)
2080 role = Role.find(1)
2081 role.remove_permission! :set_issues_private
2081 role.remove_permission! :set_issues_private
2082 role.add_permission! :set_own_issues_private
2082 role.add_permission! :set_own_issues_private
2083
2083
2084 @request.session[:user_id] = 2
2084 @request.session[:user_id] = 2
2085
2085
2086 assert_difference 'Issue.count' do
2086 assert_difference 'Issue.count' do
2087 post :create, :project_id => 1,
2087 post :create, :project_id => 1,
2088 :issue => {:tracker_id => 1,
2088 :issue => {:tracker_id => 1,
2089 :subject => 'This is a private issue',
2089 :subject => 'This is a private issue',
2090 :is_private => '1'}
2090 :is_private => '1'}
2091 end
2091 end
2092 issue = Issue.order('id DESC').first
2092 issue = Issue.order('id DESC').first
2093 assert issue.is_private?
2093 assert issue.is_private?
2094 end
2094 end
2095
2095
2096 def test_post_create_should_send_a_notification
2096 def test_post_create_should_send_a_notification
2097 ActionMailer::Base.deliveries.clear
2097 ActionMailer::Base.deliveries.clear
2098 @request.session[:user_id] = 2
2098 @request.session[:user_id] = 2
2099 assert_difference 'Issue.count' do
2099 assert_difference 'Issue.count' do
2100 post :create, :project_id => 1,
2100 post :create, :project_id => 1,
2101 :issue => {:tracker_id => 3,
2101 :issue => {:tracker_id => 3,
2102 :subject => 'This is the test_new issue',
2102 :subject => 'This is the test_new issue',
2103 :description => 'This is the description',
2103 :description => 'This is the description',
2104 :priority_id => 5,
2104 :priority_id => 5,
2105 :estimated_hours => '',
2105 :estimated_hours => '',
2106 :custom_field_values => {'2' => 'Value for field 2'}}
2106 :custom_field_values => {'2' => 'Value for field 2'}}
2107 end
2107 end
2108 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2108 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2109
2109
2110 assert_equal 1, ActionMailer::Base.deliveries.size
2110 assert_equal 1, ActionMailer::Base.deliveries.size
2111 end
2111 end
2112
2112
2113 def test_post_create_should_preserve_fields_values_on_validation_failure
2113 def test_post_create_should_preserve_fields_values_on_validation_failure
2114 @request.session[:user_id] = 2
2114 @request.session[:user_id] = 2
2115 post :create, :project_id => 1,
2115 post :create, :project_id => 1,
2116 :issue => {:tracker_id => 1,
2116 :issue => {:tracker_id => 1,
2117 # empty subject
2117 # empty subject
2118 :subject => '',
2118 :subject => '',
2119 :description => 'This is a description',
2119 :description => 'This is a description',
2120 :priority_id => 6,
2120 :priority_id => 6,
2121 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2121 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2122 assert_response :success
2122 assert_response :success
2123 assert_template 'new'
2123 assert_template 'new'
2124
2124
2125 assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description'
2125 assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description'
2126 assert_select 'select[name=?]', 'issue[priority_id]' do
2126 assert_select 'select[name=?]', 'issue[priority_id]' do
2127 assert_select 'option[value=6][selected=selected]', :text => 'High'
2127 assert_select 'option[value=6][selected=selected]', :text => 'High'
2128 end
2128 end
2129 # Custom fields
2129 # Custom fields
2130 assert_select 'select[name=?]', 'issue[custom_field_values][1]' do
2130 assert_select 'select[name=?]', 'issue[custom_field_values][1]' do
2131 assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle'
2131 assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle'
2132 end
2132 end
2133 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2'
2133 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2'
2134 end
2134 end
2135
2135
2136 def test_post_create_with_failure_should_preserve_watchers
2136 def test_post_create_with_failure_should_preserve_watchers
2137 assert !User.find(8).member_of?(Project.find(1))
2137 assert !User.find(8).member_of?(Project.find(1))
2138
2138
2139 @request.session[:user_id] = 2
2139 @request.session[:user_id] = 2
2140 post :create, :project_id => 1,
2140 post :create, :project_id => 1,
2141 :issue => {:tracker_id => 1,
2141 :issue => {:tracker_id => 1,
2142 :watcher_user_ids => ['3', '8']}
2142 :watcher_user_ids => ['3', '8']}
2143 assert_response :success
2143 assert_response :success
2144 assert_template 'new'
2144 assert_template 'new'
2145
2145
2146 assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]'
2146 assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]'
2147 assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]'
2147 assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]'
2148 assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]'
2148 assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]'
2149 end
2149 end
2150
2150
2151 def test_post_create_should_ignore_non_safe_attributes
2151 def test_post_create_should_ignore_non_safe_attributes
2152 @request.session[:user_id] = 2
2152 @request.session[:user_id] = 2
2153 assert_nothing_raised do
2153 assert_nothing_raised do
2154 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2154 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2155 end
2155 end
2156 end
2156 end
2157
2157
2158 def test_post_create_with_attachment
2158 def test_post_create_with_attachment
2159 set_tmp_attachments_directory
2159 set_tmp_attachments_directory
2160 @request.session[:user_id] = 2
2160 @request.session[:user_id] = 2
2161
2161
2162 assert_difference 'Issue.count' do
2162 assert_difference 'Issue.count' do
2163 assert_difference 'Attachment.count' do
2163 assert_difference 'Attachment.count' do
2164 post :create, :project_id => 1,
2164 post :create, :project_id => 1,
2165 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2165 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2166 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2166 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2167 end
2167 end
2168 end
2168 end
2169
2169
2170 issue = Issue.order('id DESC').first
2170 issue = Issue.order('id DESC').first
2171 attachment = Attachment.order('id DESC').first
2171 attachment = Attachment.order('id DESC').first
2172
2172
2173 assert_equal issue, attachment.container
2173 assert_equal issue, attachment.container
2174 assert_equal 2, attachment.author_id
2174 assert_equal 2, attachment.author_id
2175 assert_equal 'testfile.txt', attachment.filename
2175 assert_equal 'testfile.txt', attachment.filename
2176 assert_equal 'text/plain', attachment.content_type
2176 assert_equal 'text/plain', attachment.content_type
2177 assert_equal 'test file', attachment.description
2177 assert_equal 'test file', attachment.description
2178 assert_equal 59, attachment.filesize
2178 assert_equal 59, attachment.filesize
2179 assert File.exists?(attachment.diskfile)
2179 assert File.exists?(attachment.diskfile)
2180 assert_equal 59, File.size(attachment.diskfile)
2180 assert_equal 59, File.size(attachment.diskfile)
2181 end
2181 end
2182
2182
2183 def test_post_create_with_attachment_should_notify_with_attachments
2183 def test_post_create_with_attachment_should_notify_with_attachments
2184 ActionMailer::Base.deliveries.clear
2184 ActionMailer::Base.deliveries.clear
2185 set_tmp_attachments_directory
2185 set_tmp_attachments_directory
2186 @request.session[:user_id] = 2
2186 @request.session[:user_id] = 2
2187
2187
2188 with_settings :host_name => 'mydomain.foo', :protocol => 'http' do
2188 with_settings :host_name => 'mydomain.foo', :protocol => 'http' do
2189 assert_difference 'Issue.count' do
2189 assert_difference 'Issue.count' do
2190 post :create, :project_id => 1,
2190 post :create, :project_id => 1,
2191 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2191 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2192 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2192 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2193 end
2193 end
2194 end
2194 end
2195
2195
2196 assert_not_nil ActionMailer::Base.deliveries.last
2196 assert_not_nil ActionMailer::Base.deliveries.last
2197 assert_select_email do
2197 assert_select_email do
2198 assert_select 'a[href^=?]', 'http://mydomain.foo/attachments/download', 'testfile.txt'
2198 assert_select 'a[href^=?]', 'http://mydomain.foo/attachments/download', 'testfile.txt'
2199 end
2199 end
2200 end
2200 end
2201
2201
2202 def test_post_create_with_failure_should_save_attachments
2202 def test_post_create_with_failure_should_save_attachments
2203 set_tmp_attachments_directory
2203 set_tmp_attachments_directory
2204 @request.session[:user_id] = 2
2204 @request.session[:user_id] = 2
2205
2205
2206 assert_no_difference 'Issue.count' do
2206 assert_no_difference 'Issue.count' do
2207 assert_difference 'Attachment.count' do
2207 assert_difference 'Attachment.count' do
2208 post :create, :project_id => 1,
2208 post :create, :project_id => 1,
2209 :issue => { :tracker_id => '1', :subject => '' },
2209 :issue => { :tracker_id => '1', :subject => '' },
2210 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2210 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2211 assert_response :success
2211 assert_response :success
2212 assert_template 'new'
2212 assert_template 'new'
2213 end
2213 end
2214 end
2214 end
2215
2215
2216 attachment = Attachment.order('id DESC').first
2216 attachment = Attachment.order('id DESC').first
2217 assert_equal 'testfile.txt', attachment.filename
2217 assert_equal 'testfile.txt', attachment.filename
2218 assert File.exists?(attachment.diskfile)
2218 assert File.exists?(attachment.diskfile)
2219 assert_nil attachment.container
2219 assert_nil attachment.container
2220
2220
2221 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2221 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2222 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2222 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2223 end
2223 end
2224
2224
2225 def test_post_create_with_failure_should_keep_saved_attachments
2225 def test_post_create_with_failure_should_keep_saved_attachments
2226 set_tmp_attachments_directory
2226 set_tmp_attachments_directory
2227 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2227 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2228 @request.session[:user_id] = 2
2228 @request.session[:user_id] = 2
2229
2229
2230 assert_no_difference 'Issue.count' do
2230 assert_no_difference 'Issue.count' do
2231 assert_no_difference 'Attachment.count' do
2231 assert_no_difference 'Attachment.count' do
2232 post :create, :project_id => 1,
2232 post :create, :project_id => 1,
2233 :issue => { :tracker_id => '1', :subject => '' },
2233 :issue => { :tracker_id => '1', :subject => '' },
2234 :attachments => {'p0' => {'token' => attachment.token}}
2234 :attachments => {'p0' => {'token' => attachment.token}}
2235 assert_response :success
2235 assert_response :success
2236 assert_template 'new'
2236 assert_template 'new'
2237 end
2237 end
2238 end
2238 end
2239
2239
2240 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2240 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2241 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2241 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2242 end
2242 end
2243
2243
2244 def test_post_create_should_attach_saved_attachments
2244 def test_post_create_should_attach_saved_attachments
2245 set_tmp_attachments_directory
2245 set_tmp_attachments_directory
2246 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2246 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2247 @request.session[:user_id] = 2
2247 @request.session[:user_id] = 2
2248
2248
2249 assert_difference 'Issue.count' do
2249 assert_difference 'Issue.count' do
2250 assert_no_difference 'Attachment.count' do
2250 assert_no_difference 'Attachment.count' do
2251 post :create, :project_id => 1,
2251 post :create, :project_id => 1,
2252 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2252 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2253 :attachments => {'p0' => {'token' => attachment.token}}
2253 :attachments => {'p0' => {'token' => attachment.token}}
2254 assert_response 302
2254 assert_response 302
2255 end
2255 end
2256 end
2256 end
2257
2257
2258 issue = Issue.order('id DESC').first
2258 issue = Issue.order('id DESC').first
2259 assert_equal 1, issue.attachments.count
2259 assert_equal 1, issue.attachments.count
2260
2260
2261 attachment.reload
2261 attachment.reload
2262 assert_equal issue, attachment.container
2262 assert_equal issue, attachment.container
2263 end
2263 end
2264
2264
2265 def setup_without_workflow_privilege
2265 def setup_without_workflow_privilege
2266 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2266 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2267 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2267 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2268 end
2268 end
2269 private :setup_without_workflow_privilege
2269 private :setup_without_workflow_privilege
2270
2270
2271 test "without workflow privilege #new should propose default status only" do
2271 test "without workflow privilege #new should propose default status only" do
2272 setup_without_workflow_privilege
2272 setup_without_workflow_privilege
2273 get :new, :project_id => 1
2273 get :new, :project_id => 1
2274 assert_response :success
2274 assert_response :success
2275 assert_template 'new'
2275 assert_template 'new'
2276 assert_select 'select[name=?]', 'issue[status_id]' do
2276 assert_select 'select[name=?]', 'issue[status_id]' do
2277 assert_select 'option', 1
2277 assert_select 'option', 1
2278 assert_select 'option[value=?]', IssueStatus.default.id.to_s
2278 assert_select 'option[value=?]', IssueStatus.default.id.to_s
2279 end
2279 end
2280 end
2280 end
2281
2281
2282 test "without workflow privilege #new should accept default status" do
2282 test "without workflow privilege #new should accept default status" do
2283 setup_without_workflow_privilege
2283 setup_without_workflow_privilege
2284 assert_difference 'Issue.count' do
2284 assert_difference 'Issue.count' do
2285 post :create, :project_id => 1,
2285 post :create, :project_id => 1,
2286 :issue => {:tracker_id => 1,
2286 :issue => {:tracker_id => 1,
2287 :subject => 'This is an issue',
2287 :subject => 'This is an issue',
2288 :status_id => 1}
2288 :status_id => 1}
2289 end
2289 end
2290 issue = Issue.order('id').last
2290 issue = Issue.order('id').last
2291 assert_equal IssueStatus.default, issue.status
2291 assert_equal IssueStatus.default, issue.status
2292 end
2292 end
2293
2293
2294 test "without workflow privilege #new should ignore unauthorized status" do
2294 test "without workflow privilege #new should ignore unauthorized status" do
2295 setup_without_workflow_privilege
2295 setup_without_workflow_privilege
2296 assert_difference 'Issue.count' do
2296 assert_difference 'Issue.count' do
2297 post :create, :project_id => 1,
2297 post :create, :project_id => 1,
2298 :issue => {:tracker_id => 1,
2298 :issue => {:tracker_id => 1,
2299 :subject => 'This is an issue',
2299 :subject => 'This is an issue',
2300 :status_id => 3}
2300 :status_id => 3}
2301 end
2301 end
2302 issue = Issue.order('id').last
2302 issue = Issue.order('id').last
2303 assert_equal IssueStatus.default, issue.status
2303 assert_equal IssueStatus.default, issue.status
2304 end
2304 end
2305
2305
2306 test "without workflow privilege #update should ignore status change" do
2306 test "without workflow privilege #update should ignore status change" do
2307 setup_without_workflow_privilege
2307 setup_without_workflow_privilege
2308 assert_difference 'Journal.count' do
2308 assert_difference 'Journal.count' do
2309 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2309 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2310 end
2310 end
2311 assert_equal 1, Issue.find(1).status_id
2311 assert_equal 1, Issue.find(1).status_id
2312 end
2312 end
2313
2313
2314 test "without workflow privilege #update ignore attributes changes" do
2314 test "without workflow privilege #update ignore attributes changes" do
2315 setup_without_workflow_privilege
2315 setup_without_workflow_privilege
2316 assert_difference 'Journal.count' do
2316 assert_difference 'Journal.count' do
2317 put :update, :id => 1,
2317 put :update, :id => 1,
2318 :issue => {:subject => 'changed', :assigned_to_id => 2,
2318 :issue => {:subject => 'changed', :assigned_to_id => 2,
2319 :notes => 'just trying'}
2319 :notes => 'just trying'}
2320 end
2320 end
2321 issue = Issue.find(1)
2321 issue = Issue.find(1)
2322 assert_equal "Can't print recipes", issue.subject
2322 assert_equal "Can't print recipes", issue.subject
2323 assert_nil issue.assigned_to
2323 assert_nil issue.assigned_to
2324 end
2324 end
2325
2325
2326 def setup_with_workflow_privilege
2326 def setup_with_workflow_privilege
2327 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2327 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2328 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2328 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2329 :old_status_id => 1, :new_status_id => 3)
2329 :old_status_id => 1, :new_status_id => 3)
2330 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2330 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2331 :old_status_id => 1, :new_status_id => 4)
2331 :old_status_id => 1, :new_status_id => 4)
2332 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2332 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2333 end
2333 end
2334 private :setup_with_workflow_privilege
2334 private :setup_with_workflow_privilege
2335
2335
2336 test "with workflow privilege #update should accept authorized status" do
2336 test "with workflow privilege #update should accept authorized status" do
2337 setup_with_workflow_privilege
2337 setup_with_workflow_privilege
2338 assert_difference 'Journal.count' do
2338 assert_difference 'Journal.count' do
2339 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2339 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2340 end
2340 end
2341 assert_equal 3, Issue.find(1).status_id
2341 assert_equal 3, Issue.find(1).status_id
2342 end
2342 end
2343
2343
2344 test "with workflow privilege #update should ignore unauthorized status" do
2344 test "with workflow privilege #update should ignore unauthorized status" do
2345 setup_with_workflow_privilege
2345 setup_with_workflow_privilege
2346 assert_difference 'Journal.count' do
2346 assert_difference 'Journal.count' do
2347 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2347 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2348 end
2348 end
2349 assert_equal 1, Issue.find(1).status_id
2349 assert_equal 1, Issue.find(1).status_id
2350 end
2350 end
2351
2351
2352 test "with workflow privilege #update should accept authorized attributes changes" do
2352 test "with workflow privilege #update should accept authorized attributes changes" do
2353 setup_with_workflow_privilege
2353 setup_with_workflow_privilege
2354 assert_difference 'Journal.count' do
2354 assert_difference 'Journal.count' do
2355 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2355 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2356 end
2356 end
2357 issue = Issue.find(1)
2357 issue = Issue.find(1)
2358 assert_equal 2, issue.assigned_to_id
2358 assert_equal 2, issue.assigned_to_id
2359 end
2359 end
2360
2360
2361 test "with workflow privilege #update should ignore unauthorized attributes changes" do
2361 test "with workflow privilege #update should ignore unauthorized attributes changes" do
2362 setup_with_workflow_privilege
2362 setup_with_workflow_privilege
2363 assert_difference 'Journal.count' do
2363 assert_difference 'Journal.count' do
2364 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2364 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2365 end
2365 end
2366 issue = Issue.find(1)
2366 issue = Issue.find(1)
2367 assert_equal "Can't print recipes", issue.subject
2367 assert_equal "Can't print recipes", issue.subject
2368 end
2368 end
2369
2369
2370 def setup_with_workflow_privilege_and_edit_issues_permission
2370 def setup_with_workflow_privilege_and_edit_issues_permission
2371 setup_with_workflow_privilege
2371 setup_with_workflow_privilege
2372 Role.anonymous.add_permission! :add_issues, :edit_issues
2372 Role.anonymous.add_permission! :add_issues, :edit_issues
2373 end
2373 end
2374 private :setup_with_workflow_privilege_and_edit_issues_permission
2374 private :setup_with_workflow_privilege_and_edit_issues_permission
2375
2375
2376 test "with workflow privilege and :edit_issues permission should accept authorized status" do
2376 test "with workflow privilege and :edit_issues permission should accept authorized status" do
2377 setup_with_workflow_privilege_and_edit_issues_permission
2377 setup_with_workflow_privilege_and_edit_issues_permission
2378 assert_difference 'Journal.count' do
2378 assert_difference 'Journal.count' do
2379 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2379 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2380 end
2380 end
2381 assert_equal 3, Issue.find(1).status_id
2381 assert_equal 3, Issue.find(1).status_id
2382 end
2382 end
2383
2383
2384 test "with workflow privilege and :edit_issues permission should ignore unauthorized status" do
2384 test "with workflow privilege and :edit_issues permission should ignore unauthorized status" do
2385 setup_with_workflow_privilege_and_edit_issues_permission
2385 setup_with_workflow_privilege_and_edit_issues_permission
2386 assert_difference 'Journal.count' do
2386 assert_difference 'Journal.count' do
2387 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2387 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2388 end
2388 end
2389 assert_equal 1, Issue.find(1).status_id
2389 assert_equal 1, Issue.find(1).status_id
2390 end
2390 end
2391
2391
2392 test "with workflow privilege and :edit_issues permission should accept authorized attributes changes" do
2392 test "with workflow privilege and :edit_issues permission should accept authorized attributes changes" do
2393 setup_with_workflow_privilege_and_edit_issues_permission
2393 setup_with_workflow_privilege_and_edit_issues_permission
2394 assert_difference 'Journal.count' do
2394 assert_difference 'Journal.count' do
2395 put :update, :id => 1,
2395 put :update, :id => 1,
2396 :issue => {:subject => 'changed', :assigned_to_id => 2,
2396 :issue => {:subject => 'changed', :assigned_to_id => 2,
2397 :notes => 'just trying'}
2397 :notes => 'just trying'}
2398 end
2398 end
2399 issue = Issue.find(1)
2399 issue = Issue.find(1)
2400 assert_equal "changed", issue.subject
2400 assert_equal "changed", issue.subject
2401 assert_equal 2, issue.assigned_to_id
2401 assert_equal 2, issue.assigned_to_id
2402 end
2402 end
2403
2403
2404 def test_new_as_copy
2404 def test_new_as_copy
2405 @request.session[:user_id] = 2
2405 @request.session[:user_id] = 2
2406 get :new, :project_id => 1, :copy_from => 1
2406 get :new, :project_id => 1, :copy_from => 1
2407
2407
2408 assert_response :success
2408 assert_response :success
2409 assert_template 'new'
2409 assert_template 'new'
2410
2410
2411 assert_not_nil assigns(:issue)
2411 assert_not_nil assigns(:issue)
2412 orig = Issue.find(1)
2412 orig = Issue.find(1)
2413 assert_equal 1, assigns(:issue).project_id
2413 assert_equal 1, assigns(:issue).project_id
2414 assert_equal orig.subject, assigns(:issue).subject
2414 assert_equal orig.subject, assigns(:issue).subject
2415 assert assigns(:issue).copy?
2415 assert assigns(:issue).copy?
2416
2416
2417 assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do
2417 assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do
2418 assert_select 'select[name=?]', 'issue[project_id]' do
2418 assert_select 'select[name=?]', 'issue[project_id]' do
2419 assert_select 'option[value=1][selected=selected]', :text => 'eCookbook'
2419 assert_select 'option[value=1][selected=selected]', :text => 'eCookbook'
2420 assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore'
2420 assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore'
2421 end
2421 end
2422 assert_select 'input[name=copy_from][value=1]'
2422 assert_select 'input[name=copy_from][value=1]'
2423 end
2423 end
2424
2424
2425 # "New issue" menu item should not link to copy
2425 # "New issue" menu item should not link to copy
2426 assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]'
2426 assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]'
2427 end
2427 end
2428
2428
2429 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2429 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2430 @request.session[:user_id] = 2
2430 @request.session[:user_id] = 2
2431 issue = Issue.find(3)
2431 issue = Issue.find(3)
2432 assert issue.attachments.count > 0
2432 assert issue.attachments.count > 0
2433 get :new, :project_id => 1, :copy_from => 3
2433 get :new, :project_id => 1, :copy_from => 3
2434
2434
2435 assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]'
2435 assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]'
2436 end
2436 end
2437
2437
2438 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2438 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2439 @request.session[:user_id] = 2
2439 @request.session[:user_id] = 2
2440 issue = Issue.find(3)
2440 issue = Issue.find(3)
2441 issue.attachments.delete_all
2441 issue.attachments.delete_all
2442 get :new, :project_id => 1, :copy_from => 3
2442 get :new, :project_id => 1, :copy_from => 3
2443
2443
2444 assert_select 'input[name=copy_attachments]', 0
2444 assert_select 'input[name=copy_attachments]', 0
2445 end
2445 end
2446
2446
2447 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2447 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2448 @request.session[:user_id] = 2
2448 @request.session[:user_id] = 2
2449 issue = Issue.generate_with_descendants!
2449 issue = Issue.generate_with_descendants!
2450 get :new, :project_id => 1, :copy_from => issue.id
2450 get :new, :project_id => 1, :copy_from => issue.id
2451
2451
2452 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2452 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2453 end
2453 end
2454
2454
2455 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2455 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2456 @request.session[:user_id] = 2
2456 @request.session[:user_id] = 2
2457 get :new, :project_id => 1, :copy_from => 99999
2457 get :new, :project_id => 1, :copy_from => 99999
2458 assert_response 404
2458 assert_response 404
2459 end
2459 end
2460
2460
2461 def test_create_as_copy_on_different_project
2461 def test_create_as_copy_on_different_project
2462 @request.session[:user_id] = 2
2462 @request.session[:user_id] = 2
2463 assert_difference 'Issue.count' do
2463 assert_difference 'Issue.count' do
2464 post :create, :project_id => 1, :copy_from => 1,
2464 post :create, :project_id => 1, :copy_from => 1,
2465 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2465 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2466
2466
2467 assert_not_nil assigns(:issue)
2467 assert_not_nil assigns(:issue)
2468 assert assigns(:issue).copy?
2468 assert assigns(:issue).copy?
2469 end
2469 end
2470 issue = Issue.order('id DESC').first
2470 issue = Issue.order('id DESC').first
2471 assert_redirected_to "/issues/#{issue.id}"
2471 assert_redirected_to "/issues/#{issue.id}"
2472
2472
2473 assert_equal 2, issue.project_id
2473 assert_equal 2, issue.project_id
2474 assert_equal 3, issue.tracker_id
2474 assert_equal 3, issue.tracker_id
2475 assert_equal 'Copy', issue.subject
2475 assert_equal 'Copy', issue.subject
2476 end
2476 end
2477
2477
2478 def test_create_as_copy_should_copy_attachments
2478 def test_create_as_copy_should_copy_attachments
2479 @request.session[:user_id] = 2
2479 @request.session[:user_id] = 2
2480 issue = Issue.find(3)
2480 issue = Issue.find(3)
2481 count = issue.attachments.count
2481 count = issue.attachments.count
2482 assert count > 0
2482 assert count > 0
2483 assert_difference 'Issue.count' do
2483 assert_difference 'Issue.count' do
2484 assert_difference 'Attachment.count', count do
2484 assert_difference 'Attachment.count', count do
2485 assert_difference 'Journal.count', 2 do
2485 assert_difference 'Journal.count', 2 do
2486 post :create, :project_id => 1, :copy_from => 3,
2486 post :create, :project_id => 1, :copy_from => 3,
2487 :issue => {:project_id => '1', :tracker_id => '3',
2487 :issue => {:project_id => '1', :tracker_id => '3',
2488 :status_id => '1', :subject => 'Copy with attachments'},
2488 :status_id => '1', :subject => 'Copy with attachments'},
2489 :copy_attachments => '1'
2489 :copy_attachments => '1'
2490 end
2490 end
2491 end
2491 end
2492 end
2492 end
2493 copy = Issue.order('id DESC').first
2493 copy = Issue.order('id DESC').first
2494 assert_equal count, copy.attachments.count
2494 assert_equal count, copy.attachments.count
2495 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2495 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2496 end
2496 end
2497
2497
2498 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2498 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2499 @request.session[:user_id] = 2
2499 @request.session[:user_id] = 2
2500 issue = Issue.find(3)
2500 issue = Issue.find(3)
2501 count = issue.attachments.count
2501 count = issue.attachments.count
2502 assert count > 0
2502 assert count > 0
2503 assert_difference 'Issue.count' do
2503 assert_difference 'Issue.count' do
2504 assert_no_difference 'Attachment.count' do
2504 assert_no_difference 'Attachment.count' do
2505 assert_difference 'Journal.count', 2 do
2505 assert_difference 'Journal.count', 2 do
2506 post :create, :project_id => 1, :copy_from => 3,
2506 post :create, :project_id => 1, :copy_from => 3,
2507 :issue => {:project_id => '1', :tracker_id => '3',
2507 :issue => {:project_id => '1', :tracker_id => '3',
2508 :status_id => '1', :subject => 'Copy with attachments'}
2508 :status_id => '1', :subject => 'Copy with attachments'}
2509 end
2509 end
2510 end
2510 end
2511 end
2511 end
2512 copy = Issue.order('id DESC').first
2512 copy = Issue.order('id DESC').first
2513 assert_equal 0, copy.attachments.count
2513 assert_equal 0, copy.attachments.count
2514 end
2514 end
2515
2515
2516 def test_create_as_copy_with_attachments_should_add_new_files
2516 def test_create_as_copy_with_attachments_should_add_new_files
2517 @request.session[:user_id] = 2
2517 @request.session[:user_id] = 2
2518 issue = Issue.find(3)
2518 issue = Issue.find(3)
2519 count = issue.attachments.count
2519 count = issue.attachments.count
2520 assert count > 0
2520 assert count > 0
2521 assert_difference 'Issue.count' do
2521 assert_difference 'Issue.count' do
2522 assert_difference 'Attachment.count', count + 1 do
2522 assert_difference 'Attachment.count', count + 1 do
2523 assert_difference 'Journal.count', 2 do
2523 assert_difference 'Journal.count', 2 do
2524 post :create, :project_id => 1, :copy_from => 3,
2524 post :create, :project_id => 1, :copy_from => 3,
2525 :issue => {:project_id => '1', :tracker_id => '3',
2525 :issue => {:project_id => '1', :tracker_id => '3',
2526 :status_id => '1', :subject => 'Copy with attachments'},
2526 :status_id => '1', :subject => 'Copy with attachments'},
2527 :copy_attachments => '1',
2527 :copy_attachments => '1',
2528 :attachments => {'1' =>
2528 :attachments => {'1' =>
2529 {'file' => uploaded_test_file('testfile.txt', 'text/plain'),
2529 {'file' => uploaded_test_file('testfile.txt', 'text/plain'),
2530 'description' => 'test file'}}
2530 'description' => 'test file'}}
2531 end
2531 end
2532 end
2532 end
2533 end
2533 end
2534 copy = Issue.order('id DESC').first
2534 copy = Issue.order('id DESC').first
2535 assert_equal count + 1, copy.attachments.count
2535 assert_equal count + 1, copy.attachments.count
2536 end
2536 end
2537
2537
2538 def test_create_as_copy_should_add_relation_with_copied_issue
2538 def test_create_as_copy_should_add_relation_with_copied_issue
2539 @request.session[:user_id] = 2
2539 @request.session[:user_id] = 2
2540 assert_difference 'Issue.count' do
2540 assert_difference 'Issue.count' do
2541 assert_difference 'IssueRelation.count' do
2541 assert_difference 'IssueRelation.count' do
2542 post :create, :project_id => 1, :copy_from => 1,
2542 post :create, :project_id => 1, :copy_from => 1,
2543 :issue => {:project_id => '1', :tracker_id => '3',
2543 :issue => {:project_id => '1', :tracker_id => '3',
2544 :status_id => '1', :subject => 'Copy'}
2544 :status_id => '1', :subject => 'Copy'}
2545 end
2545 end
2546 end
2546 end
2547 copy = Issue.order('id DESC').first
2547 copy = Issue.order('id DESC').first
2548 assert_equal 1, copy.relations.size
2548 assert_equal 1, copy.relations.size
2549 end
2549 end
2550
2550
2551 def test_create_as_copy_should_copy_subtasks
2551 def test_create_as_copy_should_copy_subtasks
2552 @request.session[:user_id] = 2
2552 @request.session[:user_id] = 2
2553 issue = Issue.generate_with_descendants!
2553 issue = Issue.generate_with_descendants!
2554 count = issue.descendants.count
2554 count = issue.descendants.count
2555 assert_difference 'Issue.count', count + 1 do
2555 assert_difference 'Issue.count', count + 1 do
2556 assert_difference 'Journal.count', (count + 1) * 2 do
2556 assert_difference 'Journal.count', (count + 1) * 2 do
2557 post :create, :project_id => 1, :copy_from => issue.id,
2557 post :create, :project_id => 1, :copy_from => issue.id,
2558 :issue => {:project_id => '1', :tracker_id => '3',
2558 :issue => {:project_id => '1', :tracker_id => '3',
2559 :status_id => '1', :subject => 'Copy with subtasks'},
2559 :status_id => '1', :subject => 'Copy with subtasks'},
2560 :copy_subtasks => '1'
2560 :copy_subtasks => '1'
2561 end
2561 end
2562 end
2562 end
2563 copy = Issue.where(:parent_id => nil).order('id DESC').first
2563 copy = Issue.where(:parent_id => nil).order('id DESC').first
2564 assert_equal count, copy.descendants.count
2564 assert_equal count, copy.descendants.count
2565 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2565 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2566 end
2566 end
2567
2567
2568 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2568 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2569 @request.session[:user_id] = 2
2569 @request.session[:user_id] = 2
2570 issue = Issue.generate_with_descendants!
2570 issue = Issue.generate_with_descendants!
2571 assert_difference 'Issue.count', 1 do
2571 assert_difference 'Issue.count', 1 do
2572 assert_difference 'Journal.count', 2 do
2572 assert_difference 'Journal.count', 2 do
2573 post :create, :project_id => 1, :copy_from => 3,
2573 post :create, :project_id => 1, :copy_from => 3,
2574 :issue => {:project_id => '1', :tracker_id => '3',
2574 :issue => {:project_id => '1', :tracker_id => '3',
2575 :status_id => '1', :subject => 'Copy with subtasks'}
2575 :status_id => '1', :subject => 'Copy with subtasks'}
2576 end
2576 end
2577 end
2577 end
2578 copy = Issue.where(:parent_id => nil).order('id DESC').first
2578 copy = Issue.where(:parent_id => nil).order('id DESC').first
2579 assert_equal 0, copy.descendants.count
2579 assert_equal 0, copy.descendants.count
2580 end
2580 end
2581
2581
2582 def test_create_as_copy_with_failure
2582 def test_create_as_copy_with_failure
2583 @request.session[:user_id] = 2
2583 @request.session[:user_id] = 2
2584 post :create, :project_id => 1, :copy_from => 1,
2584 post :create, :project_id => 1, :copy_from => 1,
2585 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2585 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2586
2586
2587 assert_response :success
2587 assert_response :success
2588 assert_template 'new'
2588 assert_template 'new'
2589
2589
2590 assert_not_nil assigns(:issue)
2590 assert_not_nil assigns(:issue)
2591 assert assigns(:issue).copy?
2591 assert assigns(:issue).copy?
2592
2592
2593 assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do
2593 assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do
2594 assert_select 'select[name=?]', 'issue[project_id]' do
2594 assert_select 'select[name=?]', 'issue[project_id]' do
2595 assert_select 'option[value=1]:not([selected])', :text => 'eCookbook'
2595 assert_select 'option[value=1]:not([selected])', :text => 'eCookbook'
2596 assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore'
2596 assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore'
2597 end
2597 end
2598 assert_select 'input[name=copy_from][value=1]'
2598 assert_select 'input[name=copy_from][value=1]'
2599 end
2599 end
2600 end
2600 end
2601
2601
2602 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2602 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2603 @request.session[:user_id] = 2
2603 @request.session[:user_id] = 2
2604 assert !User.find(2).member_of?(Project.find(4))
2604 assert !User.find(2).member_of?(Project.find(4))
2605
2605
2606 assert_difference 'Issue.count' do
2606 assert_difference 'Issue.count' do
2607 post :create, :project_id => 1, :copy_from => 1,
2607 post :create, :project_id => 1, :copy_from => 1,
2608 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2608 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2609 end
2609 end
2610 issue = Issue.order('id DESC').first
2610 issue = Issue.order('id DESC').first
2611 assert_equal 1, issue.project_id
2611 assert_equal 1, issue.project_id
2612 end
2612 end
2613
2613
2614 def test_get_edit
2614 def test_get_edit
2615 @request.session[:user_id] = 2
2615 @request.session[:user_id] = 2
2616 get :edit, :id => 1
2616 get :edit, :id => 1
2617 assert_response :success
2617 assert_response :success
2618 assert_template 'edit'
2618 assert_template 'edit'
2619 assert_not_nil assigns(:issue)
2619 assert_not_nil assigns(:issue)
2620 assert_equal Issue.find(1), assigns(:issue)
2620 assert_equal Issue.find(1), assigns(:issue)
2621
2621
2622 # Be sure we don't display inactive IssuePriorities
2622 # Be sure we don't display inactive IssuePriorities
2623 assert ! IssuePriority.find(15).active?
2623 assert ! IssuePriority.find(15).active?
2624 assert_select 'select[name=?]', 'issue[priority_id]' do
2624 assert_select 'select[name=?]', 'issue[priority_id]' do
2625 assert_select 'option[value=15]', 0
2625 assert_select 'option[value=15]', 0
2626 end
2626 end
2627 end
2627 end
2628
2628
2629 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2629 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2630 @request.session[:user_id] = 2
2630 @request.session[:user_id] = 2
2631 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2631 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2632
2632
2633 get :edit, :id => 1
2633 get :edit, :id => 1
2634 assert_select 'input[name=?]', 'time_entry[hours]'
2634 assert_select 'input[name=?]', 'time_entry[hours]'
2635 end
2635 end
2636
2636
2637 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2637 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2638 @request.session[:user_id] = 2
2638 @request.session[:user_id] = 2
2639 Role.find_by_name('Manager').remove_permission! :log_time
2639 Role.find_by_name('Manager').remove_permission! :log_time
2640
2640
2641 get :edit, :id => 1
2641 get :edit, :id => 1
2642 assert_select 'input[name=?]', 'time_entry[hours]', 0
2642 assert_select 'input[name=?]', 'time_entry[hours]', 0
2643 end
2643 end
2644
2644
2645 def test_get_edit_with_params
2645 def test_get_edit_with_params
2646 @request.session[:user_id] = 2
2646 @request.session[:user_id] = 2
2647 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2647 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2648 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 }
2648 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 }
2649 assert_response :success
2649 assert_response :success
2650 assert_template 'edit'
2650 assert_template 'edit'
2651
2651
2652 issue = assigns(:issue)
2652 issue = assigns(:issue)
2653 assert_not_nil issue
2653 assert_not_nil issue
2654
2654
2655 assert_equal 5, issue.status_id
2655 assert_equal 5, issue.status_id
2656 assert_select 'select[name=?]', 'issue[status_id]' do
2656 assert_select 'select[name=?]', 'issue[status_id]' do
2657 assert_select 'option[value=5][selected=selected]', :text => 'Closed'
2657 assert_select 'option[value=5][selected=selected]', :text => 'Closed'
2658 end
2658 end
2659
2659
2660 assert_equal 7, issue.priority_id
2660 assert_equal 7, issue.priority_id
2661 assert_select 'select[name=?]', 'issue[priority_id]' do
2661 assert_select 'select[name=?]', 'issue[priority_id]' do
2662 assert_select 'option[value=7][selected=selected]', :text => 'Urgent'
2662 assert_select 'option[value=7][selected=selected]', :text => 'Urgent'
2663 end
2663 end
2664
2664
2665 assert_select 'input[name=?][value=2.5]', 'time_entry[hours]'
2665 assert_select 'input[name=?][value=2.5]', 'time_entry[hours]'
2666 assert_select 'select[name=?]', 'time_entry[activity_id]' do
2666 assert_select 'select[name=?]', 'time_entry[activity_id]' do
2667 assert_select 'option[value=10][selected=selected]', :text => 'Development'
2667 assert_select 'option[value=10][selected=selected]', :text => 'Development'
2668 end
2668 end
2669 assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]'
2669 assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]'
2670 end
2670 end
2671
2671
2672 def test_get_edit_with_multi_custom_field
2672 def test_get_edit_with_multi_custom_field
2673 field = CustomField.find(1)
2673 field = CustomField.find(1)
2674 field.update_attribute :multiple, true
2674 field.update_attribute :multiple, true
2675 issue = Issue.find(1)
2675 issue = Issue.find(1)
2676 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2676 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2677 issue.save!
2677 issue.save!
2678
2678
2679 @request.session[:user_id] = 2
2679 @request.session[:user_id] = 2
2680 get :edit, :id => 1
2680 get :edit, :id => 1
2681 assert_response :success
2681 assert_response :success
2682 assert_template 'edit'
2682 assert_template 'edit'
2683
2683
2684 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
2684 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
2685 assert_select 'option', 3
2685 assert_select 'option', 3
2686 assert_select 'option[value=MySQL][selected=selected]'
2686 assert_select 'option[value=MySQL][selected=selected]'
2687 assert_select 'option[value=Oracle][selected=selected]'
2687 assert_select 'option[value=Oracle][selected=selected]'
2688 assert_select 'option[value=PostgreSQL]:not([selected])'
2688 assert_select 'option[value=PostgreSQL]:not([selected])'
2689 end
2689 end
2690 end
2690 end
2691
2691
2692 def test_update_form_for_existing_issue
2692 def test_update_form_for_existing_issue
2693 @request.session[:user_id] = 2
2693 @request.session[:user_id] = 2
2694 xhr :put, :update_form, :project_id => 1,
2694 xhr :put, :update_form, :project_id => 1,
2695 :id => 1,
2695 :id => 1,
2696 :issue => {:tracker_id => 2,
2696 :issue => {:tracker_id => 2,
2697 :subject => 'This is the test_new issue',
2697 :subject => 'This is the test_new issue',
2698 :description => 'This is the description',
2698 :description => 'This is the description',
2699 :priority_id => 5}
2699 :priority_id => 5}
2700 assert_response :success
2700 assert_response :success
2701 assert_equal 'text/javascript', response.content_type
2701 assert_equal 'text/javascript', response.content_type
2702 assert_template 'update_form'
2702 assert_template 'update_form'
2703 assert_template :partial => '_form'
2703 assert_template :partial => '_form'
2704
2704
2705 issue = assigns(:issue)
2705 issue = assigns(:issue)
2706 assert_kind_of Issue, issue
2706 assert_kind_of Issue, issue
2707 assert_equal 1, issue.id
2707 assert_equal 1, issue.id
2708 assert_equal 1, issue.project_id
2708 assert_equal 1, issue.project_id
2709 assert_equal 2, issue.tracker_id
2709 assert_equal 2, issue.tracker_id
2710 assert_equal 'This is the test_new issue', issue.subject
2710 assert_equal 'This is the test_new issue', issue.subject
2711 end
2711 end
2712
2712
2713 def test_update_form_for_existing_issue_should_keep_issue_author
2713 def test_update_form_for_existing_issue_should_keep_issue_author
2714 @request.session[:user_id] = 3
2714 @request.session[:user_id] = 3
2715 xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2715 xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2716 assert_response :success
2716 assert_response :success
2717 assert_equal 'text/javascript', response.content_type
2717 assert_equal 'text/javascript', response.content_type
2718
2718
2719 issue = assigns(:issue)
2719 issue = assigns(:issue)
2720 assert_equal User.find(2), issue.author
2720 assert_equal User.find(2), issue.author
2721 assert_equal 2, issue.author_id
2721 assert_equal 2, issue.author_id
2722 assert_not_equal User.current, issue.author
2722 assert_not_equal User.current, issue.author
2723 end
2723 end
2724
2724
2725 def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status
2725 def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status
2726 @request.session[:user_id] = 2
2726 @request.session[:user_id] = 2
2727 WorkflowTransition.delete_all
2727 WorkflowTransition.delete_all
2728 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2728 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2729 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2729 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2730 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2730 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2731
2731
2732 xhr :put, :update_form, :project_id => 1,
2732 xhr :put, :update_form, :project_id => 1,
2733 :id => 2,
2733 :id => 2,
2734 :issue => {:tracker_id => 2,
2734 :issue => {:tracker_id => 2,
2735 :status_id => 5,
2735 :status_id => 5,
2736 :subject => 'This is an issue'}
2736 :subject => 'This is an issue'}
2737
2737
2738 assert_equal 5, assigns(:issue).status_id
2738 assert_equal 5, assigns(:issue).status_id
2739 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2739 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2740 end
2740 end
2741
2741
2742 def test_update_form_for_existing_issue_with_project_change
2742 def test_update_form_for_existing_issue_with_project_change
2743 @request.session[:user_id] = 2
2743 @request.session[:user_id] = 2
2744 xhr :put, :update_form, :project_id => 1,
2744 xhr :put, :update_form, :project_id => 1,
2745 :id => 1,
2745 :id => 1,
2746 :issue => {:project_id => 2,
2746 :issue => {:project_id => 2,
2747 :tracker_id => 2,
2747 :tracker_id => 2,
2748 :subject => 'This is the test_new issue',
2748 :subject => 'This is the test_new issue',
2749 :description => 'This is the description',
2749 :description => 'This is the description',
2750 :priority_id => 5}
2750 :priority_id => 5}
2751 assert_response :success
2751 assert_response :success
2752 assert_template :partial => '_form'
2752 assert_template :partial => '_form'
2753
2753
2754 issue = assigns(:issue)
2754 issue = assigns(:issue)
2755 assert_kind_of Issue, issue
2755 assert_kind_of Issue, issue
2756 assert_equal 1, issue.id
2756 assert_equal 1, issue.id
2757 assert_equal 2, issue.project_id
2757 assert_equal 2, issue.project_id
2758 assert_equal 2, issue.tracker_id
2758 assert_equal 2, issue.tracker_id
2759 assert_equal 'This is the test_new issue', issue.subject
2759 assert_equal 'This is the test_new issue', issue.subject
2760 end
2760 end
2761
2761
2762 def test_update_form_should_propose_default_status_for_existing_issue
2762 def test_update_form_should_propose_default_status_for_existing_issue
2763 @request.session[:user_id] = 2
2763 @request.session[:user_id] = 2
2764 WorkflowTransition.delete_all
2764 WorkflowTransition.delete_all
2765 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2765 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2766
2766
2767 xhr :put, :update_form, :project_id => 1, :id => 2
2767 xhr :put, :update_form, :project_id => 1, :id => 2
2768 assert_response :success
2768 assert_response :success
2769 assert_equal [2,3], assigns(:allowed_statuses).map(&:id).sort
2769 assert_equal [2,3], assigns(:allowed_statuses).map(&:id).sort
2770 end
2770 end
2771
2771
2772 def test_put_update_without_custom_fields_param
2772 def test_put_update_without_custom_fields_param
2773 @request.session[:user_id] = 2
2773 @request.session[:user_id] = 2
2774 ActionMailer::Base.deliveries.clear
2774 ActionMailer::Base.deliveries.clear
2775
2775
2776 issue = Issue.find(1)
2776 issue = Issue.find(1)
2777 assert_equal '125', issue.custom_value_for(2).value
2777 assert_equal '125', issue.custom_value_for(2).value
2778 old_subject = issue.subject
2778 old_subject = issue.subject
2779 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2779 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2780
2780
2781 assert_difference('Journal.count') do
2781 assert_difference('Journal.count') do
2782 assert_difference('JournalDetail.count', 2) do
2782 assert_difference('JournalDetail.count', 2) do
2783 put :update, :id => 1, :issue => {:subject => new_subject,
2783 put :update, :id => 1, :issue => {:subject => new_subject,
2784 :priority_id => '6',
2784 :priority_id => '6',
2785 :category_id => '1' # no change
2785 :category_id => '1' # no change
2786 }
2786 }
2787 end
2787 end
2788 end
2788 end
2789 assert_redirected_to :action => 'show', :id => '1'
2789 assert_redirected_to :action => 'show', :id => '1'
2790 issue.reload
2790 issue.reload
2791 assert_equal new_subject, issue.subject
2791 assert_equal new_subject, issue.subject
2792 # Make sure custom fields were not cleared
2792 # Make sure custom fields were not cleared
2793 assert_equal '125', issue.custom_value_for(2).value
2793 assert_equal '125', issue.custom_value_for(2).value
2794
2794
2795 mail = ActionMailer::Base.deliveries.last
2795 mail = ActionMailer::Base.deliveries.last
2796 assert_not_nil mail
2796 assert_not_nil mail
2797 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2797 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2798 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2798 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2799 end
2799 end
2800
2800
2801 def test_put_update_with_project_change
2801 def test_put_update_with_project_change
2802 @request.session[:user_id] = 2
2802 @request.session[:user_id] = 2
2803 ActionMailer::Base.deliveries.clear
2803 ActionMailer::Base.deliveries.clear
2804
2804
2805 assert_difference('Journal.count') do
2805 assert_difference('Journal.count') do
2806 assert_difference('JournalDetail.count', 3) do
2806 assert_difference('JournalDetail.count', 3) do
2807 put :update, :id => 1, :issue => {:project_id => '2',
2807 put :update, :id => 1, :issue => {:project_id => '2',
2808 :tracker_id => '1', # no change
2808 :tracker_id => '1', # no change
2809 :priority_id => '6',
2809 :priority_id => '6',
2810 :category_id => '3'
2810 :category_id => '3'
2811 }
2811 }
2812 end
2812 end
2813 end
2813 end
2814 assert_redirected_to :action => 'show', :id => '1'
2814 assert_redirected_to :action => 'show', :id => '1'
2815 issue = Issue.find(1)
2815 issue = Issue.find(1)
2816 assert_equal 2, issue.project_id
2816 assert_equal 2, issue.project_id
2817 assert_equal 1, issue.tracker_id
2817 assert_equal 1, issue.tracker_id
2818 assert_equal 6, issue.priority_id
2818 assert_equal 6, issue.priority_id
2819 assert_equal 3, issue.category_id
2819 assert_equal 3, issue.category_id
2820
2820
2821 mail = ActionMailer::Base.deliveries.last
2821 mail = ActionMailer::Base.deliveries.last
2822 assert_not_nil mail
2822 assert_not_nil mail
2823 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2823 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2824 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2824 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2825 end
2825 end
2826
2826
2827 def test_put_update_with_tracker_change
2827 def test_put_update_with_tracker_change
2828 @request.session[:user_id] = 2
2828 @request.session[:user_id] = 2
2829 ActionMailer::Base.deliveries.clear
2829 ActionMailer::Base.deliveries.clear
2830
2830
2831 assert_difference('Journal.count') do
2831 assert_difference('Journal.count') do
2832 assert_difference('JournalDetail.count', 2) do
2832 assert_difference('JournalDetail.count', 2) do
2833 put :update, :id => 1, :issue => {:project_id => '1',
2833 put :update, :id => 1, :issue => {:project_id => '1',
2834 :tracker_id => '2',
2834 :tracker_id => '2',
2835 :priority_id => '6'
2835 :priority_id => '6'
2836 }
2836 }
2837 end
2837 end
2838 end
2838 end
2839 assert_redirected_to :action => 'show', :id => '1'
2839 assert_redirected_to :action => 'show', :id => '1'
2840 issue = Issue.find(1)
2840 issue = Issue.find(1)
2841 assert_equal 1, issue.project_id
2841 assert_equal 1, issue.project_id
2842 assert_equal 2, issue.tracker_id
2842 assert_equal 2, issue.tracker_id
2843 assert_equal 6, issue.priority_id
2843 assert_equal 6, issue.priority_id
2844 assert_equal 1, issue.category_id
2844 assert_equal 1, issue.category_id
2845
2845
2846 mail = ActionMailer::Base.deliveries.last
2846 mail = ActionMailer::Base.deliveries.last
2847 assert_not_nil mail
2847 assert_not_nil mail
2848 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2848 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2849 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2849 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2850 end
2850 end
2851
2851
2852 def test_put_update_with_custom_field_change
2852 def test_put_update_with_custom_field_change
2853 @request.session[:user_id] = 2
2853 @request.session[:user_id] = 2
2854 issue = Issue.find(1)
2854 issue = Issue.find(1)
2855 assert_equal '125', issue.custom_value_for(2).value
2855 assert_equal '125', issue.custom_value_for(2).value
2856
2856
2857 assert_difference('Journal.count') do
2857 assert_difference('Journal.count') do
2858 assert_difference('JournalDetail.count', 3) do
2858 assert_difference('JournalDetail.count', 3) do
2859 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2859 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2860 :priority_id => '6',
2860 :priority_id => '6',
2861 :category_id => '1', # no change
2861 :category_id => '1', # no change
2862 :custom_field_values => { '2' => 'New custom value' }
2862 :custom_field_values => { '2' => 'New custom value' }
2863 }
2863 }
2864 end
2864 end
2865 end
2865 end
2866 assert_redirected_to :action => 'show', :id => '1'
2866 assert_redirected_to :action => 'show', :id => '1'
2867 issue.reload
2867 issue.reload
2868 assert_equal 'New custom value', issue.custom_value_for(2).value
2868 assert_equal 'New custom value', issue.custom_value_for(2).value
2869
2869
2870 mail = ActionMailer::Base.deliveries.last
2870 mail = ActionMailer::Base.deliveries.last
2871 assert_not_nil mail
2871 assert_not_nil mail
2872 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2872 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2873 end
2873 end
2874
2874
2875 def test_put_update_with_multi_custom_field_change
2875 def test_put_update_with_multi_custom_field_change
2876 field = CustomField.find(1)
2876 field = CustomField.find(1)
2877 field.update_attribute :multiple, true
2877 field.update_attribute :multiple, true
2878 issue = Issue.find(1)
2878 issue = Issue.find(1)
2879 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2879 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2880 issue.save!
2880 issue.save!
2881
2881
2882 @request.session[:user_id] = 2
2882 @request.session[:user_id] = 2
2883 assert_difference('Journal.count') do
2883 assert_difference('Journal.count') do
2884 assert_difference('JournalDetail.count', 3) do
2884 assert_difference('JournalDetail.count', 3) do
2885 put :update, :id => 1,
2885 put :update, :id => 1,
2886 :issue => {
2886 :issue => {
2887 :subject => 'Custom field change',
2887 :subject => 'Custom field change',
2888 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2888 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2889 }
2889 }
2890 end
2890 end
2891 end
2891 end
2892 assert_redirected_to :action => 'show', :id => '1'
2892 assert_redirected_to :action => 'show', :id => '1'
2893 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2893 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2894 end
2894 end
2895
2895
2896 def test_put_update_with_status_and_assignee_change
2896 def test_put_update_with_status_and_assignee_change
2897 issue = Issue.find(1)
2897 issue = Issue.find(1)
2898 assert_equal 1, issue.status_id
2898 assert_equal 1, issue.status_id
2899 @request.session[:user_id] = 2
2899 @request.session[:user_id] = 2
2900 assert_difference('TimeEntry.count', 0) do
2900 assert_difference('TimeEntry.count', 0) do
2901 put :update,
2901 put :update,
2902 :id => 1,
2902 :id => 1,
2903 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2903 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2904 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2904 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2905 end
2905 end
2906 assert_redirected_to :action => 'show', :id => '1'
2906 assert_redirected_to :action => 'show', :id => '1'
2907 issue.reload
2907 issue.reload
2908 assert_equal 2, issue.status_id
2908 assert_equal 2, issue.status_id
2909 j = Journal.order('id DESC').first
2909 j = Journal.order('id DESC').first
2910 assert_equal 'Assigned to dlopper', j.notes
2910 assert_equal 'Assigned to dlopper', j.notes
2911 assert_equal 2, j.details.size
2911 assert_equal 2, j.details.size
2912
2912
2913 mail = ActionMailer::Base.deliveries.last
2913 mail = ActionMailer::Base.deliveries.last
2914 assert_mail_body_match "Status changed from New to Assigned", mail
2914 assert_mail_body_match "Status changed from New to Assigned", mail
2915 # subject should contain the new status
2915 # subject should contain the new status
2916 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2916 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2917 end
2917 end
2918
2918
2919 def test_put_update_with_note_only
2919 def test_put_update_with_note_only
2920 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2920 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2921 # anonymous user
2921 # anonymous user
2922 put :update,
2922 put :update,
2923 :id => 1,
2923 :id => 1,
2924 :issue => { :notes => notes }
2924 :issue => { :notes => notes }
2925 assert_redirected_to :action => 'show', :id => '1'
2925 assert_redirected_to :action => 'show', :id => '1'
2926 j = Journal.order('id DESC').first
2926 j = Journal.order('id DESC').first
2927 assert_equal notes, j.notes
2927 assert_equal notes, j.notes
2928 assert_equal 0, j.details.size
2928 assert_equal 0, j.details.size
2929 assert_equal User.anonymous, j.user
2929 assert_equal User.anonymous, j.user
2930
2930
2931 mail = ActionMailer::Base.deliveries.last
2931 mail = ActionMailer::Base.deliveries.last
2932 assert_mail_body_match notes, mail
2932 assert_mail_body_match notes, mail
2933 end
2933 end
2934
2934
2935 def test_put_update_with_private_note_only
2935 def test_put_update_with_private_note_only
2936 notes = 'Private note'
2936 notes = 'Private note'
2937 @request.session[:user_id] = 2
2937 @request.session[:user_id] = 2
2938
2938
2939 assert_difference 'Journal.count' do
2939 assert_difference 'Journal.count' do
2940 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
2940 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
2941 assert_redirected_to :action => 'show', :id => '1'
2941 assert_redirected_to :action => 'show', :id => '1'
2942 end
2942 end
2943
2943
2944 j = Journal.order('id DESC').first
2944 j = Journal.order('id DESC').first
2945 assert_equal notes, j.notes
2945 assert_equal notes, j.notes
2946 assert_equal true, j.private_notes
2946 assert_equal true, j.private_notes
2947 end
2947 end
2948
2948
2949 def test_put_update_with_private_note_and_changes
2949 def test_put_update_with_private_note_and_changes
2950 notes = 'Private note'
2950 notes = 'Private note'
2951 @request.session[:user_id] = 2
2951 @request.session[:user_id] = 2
2952
2952
2953 assert_difference 'Journal.count', 2 do
2953 assert_difference 'Journal.count', 2 do
2954 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
2954 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
2955 assert_redirected_to :action => 'show', :id => '1'
2955 assert_redirected_to :action => 'show', :id => '1'
2956 end
2956 end
2957
2957
2958 j = Journal.order('id DESC').first
2958 j = Journal.order('id DESC').first
2959 assert_equal notes, j.notes
2959 assert_equal notes, j.notes
2960 assert_equal true, j.private_notes
2960 assert_equal true, j.private_notes
2961 assert_equal 0, j.details.count
2961 assert_equal 0, j.details.count
2962
2962
2963 j = Journal.order('id DESC').offset(1).first
2963 j = Journal.order('id DESC').offset(1).first
2964 assert_nil j.notes
2964 assert_nil j.notes
2965 assert_equal false, j.private_notes
2965 assert_equal false, j.private_notes
2966 assert_equal 1, j.details.count
2966 assert_equal 1, j.details.count
2967 end
2967 end
2968
2968
2969 def test_put_update_with_note_and_spent_time
2969 def test_put_update_with_note_and_spent_time
2970 @request.session[:user_id] = 2
2970 @request.session[:user_id] = 2
2971 spent_hours_before = Issue.find(1).spent_hours
2971 spent_hours_before = Issue.find(1).spent_hours
2972 assert_difference('TimeEntry.count') do
2972 assert_difference('TimeEntry.count') do
2973 put :update,
2973 put :update,
2974 :id => 1,
2974 :id => 1,
2975 :issue => { :notes => '2.5 hours added' },
2975 :issue => { :notes => '2.5 hours added' },
2976 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2976 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2977 end
2977 end
2978 assert_redirected_to :action => 'show', :id => '1'
2978 assert_redirected_to :action => 'show', :id => '1'
2979
2979
2980 issue = Issue.find(1)
2980 issue = Issue.find(1)
2981
2981
2982 j = Journal.order('id DESC').first
2982 j = Journal.order('id DESC').first
2983 assert_equal '2.5 hours added', j.notes
2983 assert_equal '2.5 hours added', j.notes
2984 assert_equal 0, j.details.size
2984 assert_equal 0, j.details.size
2985
2985
2986 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2986 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2987 assert_not_nil t
2987 assert_not_nil t
2988 assert_equal 2.5, t.hours
2988 assert_equal 2.5, t.hours
2989 assert_equal spent_hours_before + 2.5, issue.spent_hours
2989 assert_equal spent_hours_before + 2.5, issue.spent_hours
2990 end
2990 end
2991
2991
2992 def test_put_update_should_preserve_parent_issue_even_if_not_visible
2992 def test_put_update_should_preserve_parent_issue_even_if_not_visible
2993 parent = Issue.generate!(:project_id => 1, :is_private => true)
2993 parent = Issue.generate!(:project_id => 1, :is_private => true)
2994 issue = Issue.generate!(:parent_issue_id => parent.id)
2994 issue = Issue.generate!(:parent_issue_id => parent.id)
2995 assert !parent.visible?(User.find(3))
2995 assert !parent.visible?(User.find(3))
2996 @request.session[:user_id] = 3
2996 @request.session[:user_id] = 3
2997
2997
2998 get :edit, :id => issue.id
2998 get :edit, :id => issue.id
2999 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', parent.id.to_s
2999 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', parent.id.to_s
3000
3000
3001 put :update, :id => issue.id, :issue => {:subject => 'New subject', :parent_issue_id => parent.id.to_s}
3001 put :update, :id => issue.id, :issue => {:subject => 'New subject', :parent_issue_id => parent.id.to_s}
3002 assert_response 302
3002 assert_response 302
3003 assert_equal parent, issue.parent
3003 assert_equal parent, issue.parent
3004 end
3004 end
3005
3005
3006 def test_put_update_with_attachment_only
3006 def test_put_update_with_attachment_only
3007 set_tmp_attachments_directory
3007 set_tmp_attachments_directory
3008
3008
3009 # Delete all fixtured journals, a race condition can occur causing the wrong
3009 # Delete all fixtured journals, a race condition can occur causing the wrong
3010 # journal to get fetched in the next find.
3010 # journal to get fetched in the next find.
3011 Journal.delete_all
3011 Journal.delete_all
3012
3012
3013 # anonymous user
3013 # anonymous user
3014 assert_difference 'Attachment.count' do
3014 assert_difference 'Attachment.count' do
3015 put :update, :id => 1,
3015 put :update, :id => 1,
3016 :issue => {:notes => ''},
3016 :issue => {:notes => ''},
3017 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3017 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3018 end
3018 end
3019
3019
3020 assert_redirected_to :action => 'show', :id => '1'
3020 assert_redirected_to :action => 'show', :id => '1'
3021 j = Issue.find(1).journals.reorder('id DESC').first
3021 j = Issue.find(1).journals.reorder('id DESC').first
3022 assert j.notes.blank?
3022 assert j.notes.blank?
3023 assert_equal 1, j.details.size
3023 assert_equal 1, j.details.size
3024 assert_equal 'testfile.txt', j.details.first.value
3024 assert_equal 'testfile.txt', j.details.first.value
3025 assert_equal User.anonymous, j.user
3025 assert_equal User.anonymous, j.user
3026
3026
3027 attachment = Attachment.order('id DESC').first
3027 attachment = Attachment.order('id DESC').first
3028 assert_equal Issue.find(1), attachment.container
3028 assert_equal Issue.find(1), attachment.container
3029 assert_equal User.anonymous, attachment.author
3029 assert_equal User.anonymous, attachment.author
3030 assert_equal 'testfile.txt', attachment.filename
3030 assert_equal 'testfile.txt', attachment.filename
3031 assert_equal 'text/plain', attachment.content_type
3031 assert_equal 'text/plain', attachment.content_type
3032 assert_equal 'test file', attachment.description
3032 assert_equal 'test file', attachment.description
3033 assert_equal 59, attachment.filesize
3033 assert_equal 59, attachment.filesize
3034 assert File.exists?(attachment.diskfile)
3034 assert File.exists?(attachment.diskfile)
3035 assert_equal 59, File.size(attachment.diskfile)
3035 assert_equal 59, File.size(attachment.diskfile)
3036
3036
3037 mail = ActionMailer::Base.deliveries.last
3037 mail = ActionMailer::Base.deliveries.last
3038 assert_mail_body_match 'testfile.txt', mail
3038 assert_mail_body_match 'testfile.txt', mail
3039 end
3039 end
3040
3040
3041 def test_put_update_with_failure_should_save_attachments
3041 def test_put_update_with_failure_should_save_attachments
3042 set_tmp_attachments_directory
3042 set_tmp_attachments_directory
3043 @request.session[:user_id] = 2
3043 @request.session[:user_id] = 2
3044
3044
3045 assert_no_difference 'Journal.count' do
3045 assert_no_difference 'Journal.count' do
3046 assert_difference 'Attachment.count' do
3046 assert_difference 'Attachment.count' do
3047 put :update, :id => 1,
3047 put :update, :id => 1,
3048 :issue => { :subject => '' },
3048 :issue => { :subject => '' },
3049 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3049 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3050 assert_response :success
3050 assert_response :success
3051 assert_template 'edit'
3051 assert_template 'edit'
3052 end
3052 end
3053 end
3053 end
3054
3054
3055 attachment = Attachment.order('id DESC').first
3055 attachment = Attachment.order('id DESC').first
3056 assert_equal 'testfile.txt', attachment.filename
3056 assert_equal 'testfile.txt', attachment.filename
3057 assert File.exists?(attachment.diskfile)
3057 assert File.exists?(attachment.diskfile)
3058 assert_nil attachment.container
3058 assert_nil attachment.container
3059
3059
3060 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3060 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3061 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3061 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3062 end
3062 end
3063
3063
3064 def test_put_update_with_failure_should_keep_saved_attachments
3064 def test_put_update_with_failure_should_keep_saved_attachments
3065 set_tmp_attachments_directory
3065 set_tmp_attachments_directory
3066 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3066 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3067 @request.session[:user_id] = 2
3067 @request.session[:user_id] = 2
3068
3068
3069 assert_no_difference 'Journal.count' do
3069 assert_no_difference 'Journal.count' do
3070 assert_no_difference 'Attachment.count' do
3070 assert_no_difference 'Attachment.count' do
3071 put :update, :id => 1,
3071 put :update, :id => 1,
3072 :issue => { :subject => '' },
3072 :issue => { :subject => '' },
3073 :attachments => {'p0' => {'token' => attachment.token}}
3073 :attachments => {'p0' => {'token' => attachment.token}}
3074 assert_response :success
3074 assert_response :success
3075 assert_template 'edit'
3075 assert_template 'edit'
3076 end
3076 end
3077 end
3077 end
3078
3078
3079 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3079 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3080 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3080 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3081 end
3081 end
3082
3082
3083 def test_put_update_should_attach_saved_attachments
3083 def test_put_update_should_attach_saved_attachments
3084 set_tmp_attachments_directory
3084 set_tmp_attachments_directory
3085 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3085 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3086 @request.session[:user_id] = 2
3086 @request.session[:user_id] = 2
3087
3087
3088 assert_difference 'Journal.count' do
3088 assert_difference 'Journal.count' do
3089 assert_difference 'JournalDetail.count' do
3089 assert_difference 'JournalDetail.count' do
3090 assert_no_difference 'Attachment.count' do
3090 assert_no_difference 'Attachment.count' do
3091 put :update, :id => 1,
3091 put :update, :id => 1,
3092 :issue => {:notes => 'Attachment added'},
3092 :issue => {:notes => 'Attachment added'},
3093 :attachments => {'p0' => {'token' => attachment.token}}
3093 :attachments => {'p0' => {'token' => attachment.token}}
3094 assert_redirected_to '/issues/1'
3094 assert_redirected_to '/issues/1'
3095 end
3095 end
3096 end
3096 end
3097 end
3097 end
3098
3098
3099 attachment.reload
3099 attachment.reload
3100 assert_equal Issue.find(1), attachment.container
3100 assert_equal Issue.find(1), attachment.container
3101
3101
3102 journal = Journal.order('id DESC').first
3102 journal = Journal.order('id DESC').first
3103 assert_equal 1, journal.details.size
3103 assert_equal 1, journal.details.size
3104 assert_equal 'testfile.txt', journal.details.first.value
3104 assert_equal 'testfile.txt', journal.details.first.value
3105 end
3105 end
3106
3106
3107 def test_put_update_with_attachment_that_fails_to_save
3107 def test_put_update_with_attachment_that_fails_to_save
3108 set_tmp_attachments_directory
3108 set_tmp_attachments_directory
3109
3109
3110 # Delete all fixtured journals, a race condition can occur causing the wrong
3110 # Delete all fixtured journals, a race condition can occur causing the wrong
3111 # journal to get fetched in the next find.
3111 # journal to get fetched in the next find.
3112 Journal.delete_all
3112 Journal.delete_all
3113
3113
3114 # Mock out the unsaved attachment
3114 # Mock out the unsaved attachment
3115 Attachment.any_instance.stubs(:create).returns(Attachment.new)
3115 Attachment.any_instance.stubs(:create).returns(Attachment.new)
3116
3116
3117 # anonymous user
3117 # anonymous user
3118 put :update,
3118 put :update,
3119 :id => 1,
3119 :id => 1,
3120 :issue => {:notes => ''},
3120 :issue => {:notes => ''},
3121 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
3121 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
3122 assert_redirected_to :action => 'show', :id => '1'
3122 assert_redirected_to :action => 'show', :id => '1'
3123 assert_equal '1 file(s) could not be saved.', flash[:warning]
3123 assert_equal '1 file(s) could not be saved.', flash[:warning]
3124 end
3124 end
3125
3125
3126 def test_put_update_with_no_change
3126 def test_put_update_with_no_change
3127 issue = Issue.find(1)
3127 issue = Issue.find(1)
3128 issue.journals.clear
3128 issue.journals.clear
3129 ActionMailer::Base.deliveries.clear
3129 ActionMailer::Base.deliveries.clear
3130
3130
3131 put :update,
3131 put :update,
3132 :id => 1,
3132 :id => 1,
3133 :issue => {:notes => ''}
3133 :issue => {:notes => ''}
3134 assert_redirected_to :action => 'show', :id => '1'
3134 assert_redirected_to :action => 'show', :id => '1'
3135
3135
3136 issue.reload
3136 issue.reload
3137 assert issue.journals.empty?
3137 assert issue.journals.empty?
3138 # No email should be sent
3138 # No email should be sent
3139 assert ActionMailer::Base.deliveries.empty?
3139 assert ActionMailer::Base.deliveries.empty?
3140 end
3140 end
3141
3141
3142 def test_put_update_should_send_a_notification
3142 def test_put_update_should_send_a_notification
3143 @request.session[:user_id] = 2
3143 @request.session[:user_id] = 2
3144 ActionMailer::Base.deliveries.clear
3144 ActionMailer::Base.deliveries.clear
3145 issue = Issue.find(1)
3145 issue = Issue.find(1)
3146 old_subject = issue.subject
3146 old_subject = issue.subject
3147 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3147 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3148
3148
3149 put :update, :id => 1, :issue => {:subject => new_subject,
3149 put :update, :id => 1, :issue => {:subject => new_subject,
3150 :priority_id => '6',
3150 :priority_id => '6',
3151 :category_id => '1' # no change
3151 :category_id => '1' # no change
3152 }
3152 }
3153 assert_equal 1, ActionMailer::Base.deliveries.size
3153 assert_equal 1, ActionMailer::Base.deliveries.size
3154 end
3154 end
3155
3155
3156 def test_put_update_with_invalid_spent_time_hours_only
3156 def test_put_update_with_invalid_spent_time_hours_only
3157 @request.session[:user_id] = 2
3157 @request.session[:user_id] = 2
3158 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3158 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3159
3159
3160 assert_no_difference('Journal.count') do
3160 assert_no_difference('Journal.count') do
3161 put :update,
3161 put :update,
3162 :id => 1,
3162 :id => 1,
3163 :issue => {:notes => notes},
3163 :issue => {:notes => notes},
3164 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3164 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3165 end
3165 end
3166 assert_response :success
3166 assert_response :success
3167 assert_template 'edit'
3167 assert_template 'edit'
3168
3168
3169 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3169 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3170 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3170 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3171 assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z'
3171 assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z'
3172 end
3172 end
3173
3173
3174 def test_put_update_with_invalid_spent_time_comments_only
3174 def test_put_update_with_invalid_spent_time_comments_only
3175 @request.session[:user_id] = 2
3175 @request.session[:user_id] = 2
3176 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3176 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3177
3177
3178 assert_no_difference('Journal.count') do
3178 assert_no_difference('Journal.count') do
3179 put :update,
3179 put :update,
3180 :id => 1,
3180 :id => 1,
3181 :issue => {:notes => notes},
3181 :issue => {:notes => notes},
3182 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3182 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3183 end
3183 end
3184 assert_response :success
3184 assert_response :success
3185 assert_template 'edit'
3185 assert_template 'edit'
3186
3186
3187 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3187 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3188 assert_error_tag :descendant => {:content => /Hours #{ESCAPED_CANT} be blank/}
3188 assert_error_tag :descendant => {:content => /Hours #{ESCAPED_CANT} be blank/}
3189 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3189 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3190 assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment'
3190 assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment'
3191 end
3191 end
3192
3192
3193 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3193 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3194 issue = Issue.find(2)
3194 issue = Issue.find(2)
3195 @request.session[:user_id] = 2
3195 @request.session[:user_id] = 2
3196
3196
3197 put :update,
3197 put :update,
3198 :id => issue.id,
3198 :id => issue.id,
3199 :issue => {
3199 :issue => {
3200 :fixed_version_id => 4
3200 :fixed_version_id => 4
3201 }
3201 }
3202
3202
3203 assert_response :redirect
3203 assert_response :redirect
3204 issue.reload
3204 issue.reload
3205 assert_equal 4, issue.fixed_version_id
3205 assert_equal 4, issue.fixed_version_id
3206 assert_not_equal issue.project_id, issue.fixed_version.project_id
3206 assert_not_equal issue.project_id, issue.fixed_version.project_id
3207 end
3207 end
3208
3208
3209 def test_put_update_should_redirect_back_using_the_back_url_parameter
3209 def test_put_update_should_redirect_back_using_the_back_url_parameter
3210 issue = Issue.find(2)
3210 issue = Issue.find(2)
3211 @request.session[:user_id] = 2
3211 @request.session[:user_id] = 2
3212
3212
3213 put :update,
3213 put :update,
3214 :id => issue.id,
3214 :id => issue.id,
3215 :issue => {
3215 :issue => {
3216 :fixed_version_id => 4
3216 :fixed_version_id => 4
3217 },
3217 },
3218 :back_url => '/issues'
3218 :back_url => '/issues'
3219
3219
3220 assert_response :redirect
3220 assert_response :redirect
3221 assert_redirected_to '/issues'
3221 assert_redirected_to '/issues'
3222 end
3222 end
3223
3223
3224 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3224 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3225 issue = Issue.find(2)
3225 issue = Issue.find(2)
3226 @request.session[:user_id] = 2
3226 @request.session[:user_id] = 2
3227
3227
3228 put :update,
3228 put :update,
3229 :id => issue.id,
3229 :id => issue.id,
3230 :issue => {
3230 :issue => {
3231 :fixed_version_id => 4
3231 :fixed_version_id => 4
3232 },
3232 },
3233 :back_url => 'http://google.com'
3233 :back_url => 'http://google.com'
3234
3234
3235 assert_response :redirect
3235 assert_response :redirect
3236 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3236 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3237 end
3237 end
3238
3238
3239 def test_get_bulk_edit
3239 def test_get_bulk_edit
3240 @request.session[:user_id] = 2
3240 @request.session[:user_id] = 2
3241 get :bulk_edit, :ids => [1, 2]
3241 get :bulk_edit, :ids => [1, 2]
3242 assert_response :success
3242 assert_response :success
3243 assert_template 'bulk_edit'
3243 assert_template 'bulk_edit'
3244
3244
3245 assert_select 'ul#bulk-selection' do
3245 assert_select 'ul#bulk-selection' do
3246 assert_select 'li', 2
3246 assert_select 'li', 2
3247 assert_select 'li a', :text => 'Bug #1'
3247 assert_select 'li a', :text => 'Bug #1'
3248 end
3248 end
3249
3249
3250 assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do
3250 assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do
3251 assert_select 'input[name=?]', 'ids[]', 2
3251 assert_select 'input[name=?]', 'ids[]', 2
3252 assert_select 'input[name=?][value=1][type=hidden]', 'ids[]'
3252 assert_select 'input[name=?][value=1][type=hidden]', 'ids[]'
3253
3253
3254 assert_select 'select[name=?]', 'issue[project_id]'
3254 assert_select 'select[name=?]', 'issue[project_id]'
3255 assert_select 'input[name=?]', 'issue[parent_issue_id]'
3255 assert_select 'input[name=?]', 'issue[parent_issue_id]'
3256
3256
3257 # Project specific custom field, date type
3257 # Project specific custom field, date type
3258 field = CustomField.find(9)
3258 field = CustomField.find(9)
3259 assert !field.is_for_all?
3259 assert !field.is_for_all?
3260 assert_equal 'date', field.field_format
3260 assert_equal 'date', field.field_format
3261 assert_select 'input[name=?]', 'issue[custom_field_values][9]'
3261 assert_select 'input[name=?]', 'issue[custom_field_values][9]'
3262
3262
3263 # System wide custom field
3263 # System wide custom field
3264 assert CustomField.find(1).is_for_all?
3264 assert CustomField.find(1).is_for_all?
3265 assert_select 'select[name=?]', 'issue[custom_field_values][1]'
3265 assert_select 'select[name=?]', 'issue[custom_field_values][1]'
3266
3266
3267 # Be sure we don't display inactive IssuePriorities
3267 # Be sure we don't display inactive IssuePriorities
3268 assert ! IssuePriority.find(15).active?
3268 assert ! IssuePriority.find(15).active?
3269 assert_select 'select[name=?]', 'issue[priority_id]' do
3269 assert_select 'select[name=?]', 'issue[priority_id]' do
3270 assert_select 'option[value=15]', 0
3270 assert_select 'option[value=15]', 0
3271 end
3271 end
3272 end
3272 end
3273 end
3273 end
3274
3274
3275 def test_get_bulk_edit_on_different_projects
3275 def test_get_bulk_edit_on_different_projects
3276 @request.session[:user_id] = 2
3276 @request.session[:user_id] = 2
3277 get :bulk_edit, :ids => [1, 2, 6]
3277 get :bulk_edit, :ids => [1, 2, 6]
3278 assert_response :success
3278 assert_response :success
3279 assert_template 'bulk_edit'
3279 assert_template 'bulk_edit'
3280
3280
3281 # Can not set issues from different projects as children of an issue
3281 # Can not set issues from different projects as children of an issue
3282 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
3282 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
3283
3283
3284 # Project specific custom field, date type
3284 # Project specific custom field, date type
3285 field = CustomField.find(9)
3285 field = CustomField.find(9)
3286 assert !field.is_for_all?
3286 assert !field.is_for_all?
3287 assert !field.project_ids.include?(Issue.find(6).project_id)
3287 assert !field.project_ids.include?(Issue.find(6).project_id)
3288 assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0
3288 assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0
3289 end
3289 end
3290
3290
3291 def test_get_bulk_edit_with_user_custom_field
3291 def test_get_bulk_edit_with_user_custom_field
3292 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3292 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3293
3293
3294 @request.session[:user_id] = 2
3294 @request.session[:user_id] = 2
3295 get :bulk_edit, :ids => [1, 2]
3295 get :bulk_edit, :ids => [1, 2]
3296 assert_response :success
3296 assert_response :success
3297 assert_template 'bulk_edit'
3297 assert_template 'bulk_edit'
3298
3298
3299 assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3299 assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3300 assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options
3300 assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options
3301 end
3301 end
3302 end
3302 end
3303
3303
3304 def test_get_bulk_edit_with_version_custom_field
3304 def test_get_bulk_edit_with_version_custom_field
3305 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3305 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3306
3306
3307 @request.session[:user_id] = 2
3307 @request.session[:user_id] = 2
3308 get :bulk_edit, :ids => [1, 2]
3308 get :bulk_edit, :ids => [1, 2]
3309 assert_response :success
3309 assert_response :success
3310 assert_template 'bulk_edit'
3310 assert_template 'bulk_edit'
3311
3311
3312 assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3312 assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3313 assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3313 assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3314 end
3314 end
3315 end
3315 end
3316
3316
3317 def test_get_bulk_edit_with_multi_custom_field
3317 def test_get_bulk_edit_with_multi_custom_field
3318 field = CustomField.find(1)
3318 field = CustomField.find(1)
3319 field.update_attribute :multiple, true
3319 field.update_attribute :multiple, true
3320
3320
3321 @request.session[:user_id] = 2
3321 @request.session[:user_id] = 2
3322 get :bulk_edit, :ids => [1, 2]
3322 get :bulk_edit, :ids => [1, 2]
3323 assert_response :success
3323 assert_response :success
3324 assert_template 'bulk_edit'
3324 assert_template 'bulk_edit'
3325
3325
3326 assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do
3326 assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do
3327 assert_select 'option', field.possible_values.size + 1 # "none" options
3327 assert_select 'option', field.possible_values.size + 1 # "none" options
3328 end
3328 end
3329 end
3329 end
3330
3330
3331 def test_bulk_edit_should_propose_to_clear_text_custom_fields
3331 def test_bulk_edit_should_propose_to_clear_text_custom_fields
3332 @request.session[:user_id] = 2
3332 @request.session[:user_id] = 2
3333 get :bulk_edit, :ids => [1, 3]
3333 get :bulk_edit, :ids => [1, 3]
3334 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', '__none__'
3334 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', '__none__'
3335 end
3335 end
3336
3336
3337 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3337 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3338 WorkflowTransition.delete_all
3338 WorkflowTransition.delete_all
3339 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3339 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3340 :old_status_id => 1, :new_status_id => 1)
3340 :old_status_id => 1, :new_status_id => 1)
3341 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3341 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3342 :old_status_id => 1, :new_status_id => 3)
3342 :old_status_id => 1, :new_status_id => 3)
3343 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3343 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3344 :old_status_id => 1, :new_status_id => 4)
3344 :old_status_id => 1, :new_status_id => 4)
3345 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3345 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3346 :old_status_id => 2, :new_status_id => 1)
3346 :old_status_id => 2, :new_status_id => 1)
3347 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3347 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3348 :old_status_id => 2, :new_status_id => 3)
3348 :old_status_id => 2, :new_status_id => 3)
3349 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3349 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3350 :old_status_id => 2, :new_status_id => 5)
3350 :old_status_id => 2, :new_status_id => 5)
3351 @request.session[:user_id] = 2
3351 @request.session[:user_id] = 2
3352 get :bulk_edit, :ids => [1, 2]
3352 get :bulk_edit, :ids => [1, 2]
3353
3353
3354 assert_response :success
3354 assert_response :success
3355 statuses = assigns(:available_statuses)
3355 statuses = assigns(:available_statuses)
3356 assert_not_nil statuses
3356 assert_not_nil statuses
3357 assert_equal [1, 3], statuses.map(&:id).sort
3357 assert_equal [1, 3], statuses.map(&:id).sort
3358
3358
3359 assert_select 'select[name=?]', 'issue[status_id]' do
3359 assert_select 'select[name=?]', 'issue[status_id]' do
3360 assert_select 'option', 3 # 2 statuses + "no change" option
3360 assert_select 'option', 3 # 2 statuses + "no change" option
3361 end
3361 end
3362 end
3362 end
3363
3363
3364 def test_bulk_edit_should_propose_target_project_open_shared_versions
3364 def test_bulk_edit_should_propose_target_project_open_shared_versions
3365 @request.session[:user_id] = 2
3365 @request.session[:user_id] = 2
3366 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3366 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3367 assert_response :success
3367 assert_response :success
3368 assert_template 'bulk_edit'
3368 assert_template 'bulk_edit'
3369 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
3369 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
3370
3370
3371 assert_select 'select[name=?]', 'issue[fixed_version_id]' do
3371 assert_select 'select[name=?]', 'issue[fixed_version_id]' do
3372 assert_select 'option', :text => '2.0'
3372 assert_select 'option', :text => '2.0'
3373 end
3373 end
3374 end
3374 end
3375
3375
3376 def test_bulk_edit_should_propose_target_project_categories
3376 def test_bulk_edit_should_propose_target_project_categories
3377 @request.session[:user_id] = 2
3377 @request.session[:user_id] = 2
3378 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3378 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3379 assert_response :success
3379 assert_response :success
3380 assert_template 'bulk_edit'
3380 assert_template 'bulk_edit'
3381 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3381 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3382
3382
3383 assert_select 'select[name=?]', 'issue[category_id]' do
3383 assert_select 'select[name=?]', 'issue[category_id]' do
3384 assert_select 'option', :text => 'Recipes'
3384 assert_select 'option', :text => 'Recipes'
3385 end
3385 end
3386 end
3386 end
3387
3387
3388 def test_bulk_update
3388 def test_bulk_update
3389 @request.session[:user_id] = 2
3389 @request.session[:user_id] = 2
3390 # update issues priority
3390 # update issues priority
3391 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3391 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3392 :issue => {:priority_id => 7,
3392 :issue => {:priority_id => 7,
3393 :assigned_to_id => '',
3393 :assigned_to_id => '',
3394 :custom_field_values => {'2' => ''}}
3394 :custom_field_values => {'2' => ''}}
3395
3395
3396 assert_response 302
3396 assert_response 302
3397 # check that the issues were updated
3397 # check that the issues were updated
3398 assert_equal [7, 7], Issue.where(:id =>[1, 2]).collect {|i| i.priority.id}
3398 assert_equal [7, 7], Issue.where(:id =>[1, 2]).collect {|i| i.priority.id}
3399
3399
3400 issue = Issue.find(1)
3400 issue = Issue.find(1)
3401 journal = issue.journals.reorder('created_on DESC').first
3401 journal = issue.journals.reorder('created_on DESC').first
3402 assert_equal '125', issue.custom_value_for(2).value
3402 assert_equal '125', issue.custom_value_for(2).value
3403 assert_equal 'Bulk editing', journal.notes
3403 assert_equal 'Bulk editing', journal.notes
3404 assert_equal 1, journal.details.size
3404 assert_equal 1, journal.details.size
3405 end
3405 end
3406
3406
3407 def test_bulk_update_with_group_assignee
3407 def test_bulk_update_with_group_assignee
3408 group = Group.find(11)
3408 group = Group.find(11)
3409 project = Project.find(1)
3409 project = Project.find(1)
3410 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3410 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3411
3411
3412 @request.session[:user_id] = 2
3412 @request.session[:user_id] = 2
3413 # update issues assignee
3413 # update issues assignee
3414 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3414 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3415 :issue => {:priority_id => '',
3415 :issue => {:priority_id => '',
3416 :assigned_to_id => group.id,
3416 :assigned_to_id => group.id,
3417 :custom_field_values => {'2' => ''}}
3417 :custom_field_values => {'2' => ''}}
3418
3418
3419 assert_response 302
3419 assert_response 302
3420 assert_equal [group, group], Issue.where(:id => [1, 2]).collect {|i| i.assigned_to}
3420 assert_equal [group, group], Issue.where(:id => [1, 2]).collect {|i| i.assigned_to}
3421 end
3421 end
3422
3422
3423 def test_bulk_update_on_different_projects
3423 def test_bulk_update_on_different_projects
3424 @request.session[:user_id] = 2
3424 @request.session[:user_id] = 2
3425 # update issues priority
3425 # update issues priority
3426 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3426 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3427 :issue => {:priority_id => 7,
3427 :issue => {:priority_id => 7,
3428 :assigned_to_id => '',
3428 :assigned_to_id => '',
3429 :custom_field_values => {'2' => ''}}
3429 :custom_field_values => {'2' => ''}}
3430
3430
3431 assert_response 302
3431 assert_response 302
3432 # check that the issues were updated
3432 # check that the issues were updated
3433 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3433 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3434
3434
3435 issue = Issue.find(1)
3435 issue = Issue.find(1)
3436 journal = issue.journals.reorder('created_on DESC').first
3436 journal = issue.journals.reorder('created_on DESC').first
3437 assert_equal '125', issue.custom_value_for(2).value
3437 assert_equal '125', issue.custom_value_for(2).value
3438 assert_equal 'Bulk editing', journal.notes
3438 assert_equal 'Bulk editing', journal.notes
3439 assert_equal 1, journal.details.size
3439 assert_equal 1, journal.details.size
3440 end
3440 end
3441
3441
3442 def test_bulk_update_on_different_projects_without_rights
3442 def test_bulk_update_on_different_projects_without_rights
3443 @request.session[:user_id] = 3
3443 @request.session[:user_id] = 3
3444 user = User.find(3)
3444 user = User.find(3)
3445 action = { :controller => "issues", :action => "bulk_update" }
3445 action = { :controller => "issues", :action => "bulk_update" }
3446 assert user.allowed_to?(action, Issue.find(1).project)
3446 assert user.allowed_to?(action, Issue.find(1).project)
3447 assert ! user.allowed_to?(action, Issue.find(6).project)
3447 assert ! user.allowed_to?(action, Issue.find(6).project)
3448 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3448 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3449 :issue => {:priority_id => 7,
3449 :issue => {:priority_id => 7,
3450 :assigned_to_id => '',
3450 :assigned_to_id => '',
3451 :custom_field_values => {'2' => ''}}
3451 :custom_field_values => {'2' => ''}}
3452 assert_response 403
3452 assert_response 403
3453 assert_not_equal "Bulk should fail", Journal.last.notes
3453 assert_not_equal "Bulk should fail", Journal.last.notes
3454 end
3454 end
3455
3455
3456 def test_bullk_update_should_send_a_notification
3456 def test_bullk_update_should_send_a_notification
3457 @request.session[:user_id] = 2
3457 @request.session[:user_id] = 2
3458 ActionMailer::Base.deliveries.clear
3458 ActionMailer::Base.deliveries.clear
3459 post(:bulk_update,
3459 post(:bulk_update,
3460 {
3460 {
3461 :ids => [1, 2],
3461 :ids => [1, 2],
3462 :notes => 'Bulk editing',
3462 :notes => 'Bulk editing',
3463 :issue => {
3463 :issue => {
3464 :priority_id => 7,
3464 :priority_id => 7,
3465 :assigned_to_id => '',
3465 :assigned_to_id => '',
3466 :custom_field_values => {'2' => ''}
3466 :custom_field_values => {'2' => ''}
3467 }
3467 }
3468 })
3468 })
3469
3469
3470 assert_response 302
3470 assert_response 302
3471 assert_equal 2, ActionMailer::Base.deliveries.size
3471 assert_equal 2, ActionMailer::Base.deliveries.size
3472 end
3472 end
3473
3473
3474 def test_bulk_update_project
3474 def test_bulk_update_project
3475 @request.session[:user_id] = 2
3475 @request.session[:user_id] = 2
3476 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3476 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3477 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3477 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3478 # Issues moved to project 2
3478 # Issues moved to project 2
3479 assert_equal 2, Issue.find(1).project_id
3479 assert_equal 2, Issue.find(1).project_id
3480 assert_equal 2, Issue.find(2).project_id
3480 assert_equal 2, Issue.find(2).project_id
3481 # No tracker change
3481 # No tracker change
3482 assert_equal 1, Issue.find(1).tracker_id
3482 assert_equal 1, Issue.find(1).tracker_id
3483 assert_equal 2, Issue.find(2).tracker_id
3483 assert_equal 2, Issue.find(2).tracker_id
3484 end
3484 end
3485
3485
3486 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3486 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3487 @request.session[:user_id] = 2
3487 @request.session[:user_id] = 2
3488 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3488 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3489 assert_redirected_to '/issues/1'
3489 assert_redirected_to '/issues/1'
3490 end
3490 end
3491
3491
3492 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3492 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3493 @request.session[:user_id] = 2
3493 @request.session[:user_id] = 2
3494 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3494 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3495 assert_redirected_to '/projects/onlinestore/issues'
3495 assert_redirected_to '/projects/onlinestore/issues'
3496 end
3496 end
3497
3497
3498 def test_bulk_update_tracker
3498 def test_bulk_update_tracker
3499 @request.session[:user_id] = 2
3499 @request.session[:user_id] = 2
3500 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3500 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3501 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3501 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3502 assert_equal 2, Issue.find(1).tracker_id
3502 assert_equal 2, Issue.find(1).tracker_id
3503 assert_equal 2, Issue.find(2).tracker_id
3503 assert_equal 2, Issue.find(2).tracker_id
3504 end
3504 end
3505
3505
3506 def test_bulk_update_status
3506 def test_bulk_update_status
3507 @request.session[:user_id] = 2
3507 @request.session[:user_id] = 2
3508 # update issues priority
3508 # update issues priority
3509 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3509 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3510 :issue => {:priority_id => '',
3510 :issue => {:priority_id => '',
3511 :assigned_to_id => '',
3511 :assigned_to_id => '',
3512 :status_id => '5'}
3512 :status_id => '5'}
3513
3513
3514 assert_response 302
3514 assert_response 302
3515 issue = Issue.find(1)
3515 issue = Issue.find(1)
3516 assert issue.closed?
3516 assert issue.closed?
3517 end
3517 end
3518
3518
3519 def test_bulk_update_priority
3519 def test_bulk_update_priority
3520 @request.session[:user_id] = 2
3520 @request.session[:user_id] = 2
3521 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3521 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3522
3522
3523 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3523 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3524 assert_equal 6, Issue.find(1).priority_id
3524 assert_equal 6, Issue.find(1).priority_id
3525 assert_equal 6, Issue.find(2).priority_id
3525 assert_equal 6, Issue.find(2).priority_id
3526 end
3526 end
3527
3527
3528 def test_bulk_update_with_notes
3528 def test_bulk_update_with_notes
3529 @request.session[:user_id] = 2
3529 @request.session[:user_id] = 2
3530 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3530 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3531
3531
3532 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3532 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3533 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3533 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3534 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3534 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3535 end
3535 end
3536
3536
3537 def test_bulk_update_parent_id
3537 def test_bulk_update_parent_id
3538 IssueRelation.delete_all
3538 IssueRelation.delete_all
3539 @request.session[:user_id] = 2
3539 @request.session[:user_id] = 2
3540 post :bulk_update, :ids => [1, 3],
3540 post :bulk_update, :ids => [1, 3],
3541 :notes => 'Bulk editing parent',
3541 :notes => 'Bulk editing parent',
3542 :issue => {:priority_id => '', :assigned_to_id => '',
3542 :issue => {:priority_id => '', :assigned_to_id => '',
3543 :status_id => '', :parent_issue_id => '2'}
3543 :status_id => '', :parent_issue_id => '2'}
3544 assert_response 302
3544 assert_response 302
3545 parent = Issue.find(2)
3545 parent = Issue.find(2)
3546 assert_equal parent.id, Issue.find(1).parent_id
3546 assert_equal parent.id, Issue.find(1).parent_id
3547 assert_equal parent.id, Issue.find(3).parent_id
3547 assert_equal parent.id, Issue.find(3).parent_id
3548 assert_equal [1, 3], parent.children.collect(&:id).sort
3548 assert_equal [1, 3], parent.children.collect(&:id).sort
3549 end
3549 end
3550
3550
3551 def test_bulk_update_custom_field
3551 def test_bulk_update_custom_field
3552 @request.session[:user_id] = 2
3552 @request.session[:user_id] = 2
3553 # update issues priority
3553 # update issues priority
3554 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3554 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3555 :issue => {:priority_id => '',
3555 :issue => {:priority_id => '',
3556 :assigned_to_id => '',
3556 :assigned_to_id => '',
3557 :custom_field_values => {'2' => '777'}}
3557 :custom_field_values => {'2' => '777'}}
3558
3558
3559 assert_response 302
3559 assert_response 302
3560
3560
3561 issue = Issue.find(1)
3561 issue = Issue.find(1)
3562 journal = issue.journals.reorder('created_on DESC').first
3562 journal = issue.journals.reorder('created_on DESC').first
3563 assert_equal '777', issue.custom_value_for(2).value
3563 assert_equal '777', issue.custom_value_for(2).value
3564 assert_equal 1, journal.details.size
3564 assert_equal 1, journal.details.size
3565 assert_equal '125', journal.details.first.old_value
3565 assert_equal '125', journal.details.first.old_value
3566 assert_equal '777', journal.details.first.value
3566 assert_equal '777', journal.details.first.value
3567 end
3567 end
3568
3568
3569 def test_bulk_update_custom_field_to_blank
3569 def test_bulk_update_custom_field_to_blank
3570 @request.session[:user_id] = 2
3570 @request.session[:user_id] = 2
3571 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3571 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3572 :issue => {:priority_id => '',
3572 :issue => {:priority_id => '',
3573 :assigned_to_id => '',
3573 :assigned_to_id => '',
3574 :custom_field_values => {'1' => '__none__'}}
3574 :custom_field_values => {'1' => '__none__'}}
3575 assert_response 302
3575 assert_response 302
3576 assert_equal '', Issue.find(1).custom_field_value(1)
3576 assert_equal '', Issue.find(1).custom_field_value(1)
3577 assert_equal '', Issue.find(3).custom_field_value(1)
3577 assert_equal '', Issue.find(3).custom_field_value(1)
3578 end
3578 end
3579
3579
3580 def test_bulk_update_multi_custom_field
3580 def test_bulk_update_multi_custom_field
3581 field = CustomField.find(1)
3581 field = CustomField.find(1)
3582 field.update_attribute :multiple, true
3582 field.update_attribute :multiple, true
3583
3583
3584 @request.session[:user_id] = 2
3584 @request.session[:user_id] = 2
3585 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3585 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3586 :issue => {:priority_id => '',
3586 :issue => {:priority_id => '',
3587 :assigned_to_id => '',
3587 :assigned_to_id => '',
3588 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3588 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3589
3589
3590 assert_response 302
3590 assert_response 302
3591
3591
3592 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3592 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3593 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3593 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3594 # the custom field is not associated with the issue tracker
3594 # the custom field is not associated with the issue tracker
3595 assert_nil Issue.find(2).custom_field_value(1)
3595 assert_nil Issue.find(2).custom_field_value(1)
3596 end
3596 end
3597
3597
3598 def test_bulk_update_multi_custom_field_to_blank
3598 def test_bulk_update_multi_custom_field_to_blank
3599 field = CustomField.find(1)
3599 field = CustomField.find(1)
3600 field.update_attribute :multiple, true
3600 field.update_attribute :multiple, true
3601
3601
3602 @request.session[:user_id] = 2
3602 @request.session[:user_id] = 2
3603 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3603 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3604 :issue => {:priority_id => '',
3604 :issue => {:priority_id => '',
3605 :assigned_to_id => '',
3605 :assigned_to_id => '',
3606 :custom_field_values => {'1' => ['__none__']}}
3606 :custom_field_values => {'1' => ['__none__']}}
3607 assert_response 302
3607 assert_response 302
3608 assert_equal [''], Issue.find(1).custom_field_value(1)
3608 assert_equal [''], Issue.find(1).custom_field_value(1)
3609 assert_equal [''], Issue.find(3).custom_field_value(1)
3609 assert_equal [''], Issue.find(3).custom_field_value(1)
3610 end
3610 end
3611
3611
3612 def test_bulk_update_unassign
3612 def test_bulk_update_unassign
3613 assert_not_nil Issue.find(2).assigned_to
3613 assert_not_nil Issue.find(2).assigned_to
3614 @request.session[:user_id] = 2
3614 @request.session[:user_id] = 2
3615 # unassign issues
3615 # unassign issues
3616 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3616 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3617 assert_response 302
3617 assert_response 302
3618 # check that the issues were updated
3618 # check that the issues were updated
3619 assert_nil Issue.find(2).assigned_to
3619 assert_nil Issue.find(2).assigned_to
3620 end
3620 end
3621
3621
3622 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3622 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3623 @request.session[:user_id] = 2
3623 @request.session[:user_id] = 2
3624
3624
3625 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3625 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3626
3626
3627 assert_response :redirect
3627 assert_response :redirect
3628 issues = Issue.find([1,2])
3628 issues = Issue.find([1,2])
3629 issues.each do |issue|
3629 issues.each do |issue|
3630 assert_equal 4, issue.fixed_version_id
3630 assert_equal 4, issue.fixed_version_id
3631 assert_not_equal issue.project_id, issue.fixed_version.project_id
3631 assert_not_equal issue.project_id, issue.fixed_version.project_id
3632 end
3632 end
3633 end
3633 end
3634
3634
3635 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3635 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3636 @request.session[:user_id] = 2
3636 @request.session[:user_id] = 2
3637 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3637 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3638
3638
3639 assert_response :redirect
3639 assert_response :redirect
3640 assert_redirected_to '/issues'
3640 assert_redirected_to '/issues'
3641 end
3641 end
3642
3642
3643 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3643 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3644 @request.session[:user_id] = 2
3644 @request.session[:user_id] = 2
3645 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3645 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3646
3646
3647 assert_response :redirect
3647 assert_response :redirect
3648 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3648 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3649 end
3649 end
3650
3650
3651 def test_bulk_update_with_all_failures_should_show_errors
3651 def test_bulk_update_with_all_failures_should_show_errors
3652 @request.session[:user_id] = 2
3652 @request.session[:user_id] = 2
3653 post :bulk_update, :ids => [1, 2], :issue => {:start_date => 'foo'}
3653 post :bulk_update, :ids => [1, 2], :issue => {:start_date => 'foo'}
3654
3654
3655 assert_response :success
3655 assert_response :success
3656 assert_template 'bulk_edit'
3656 assert_template 'bulk_edit'
3657 assert_select '#errorExplanation span', :text => 'Failed to save 2 issue(s) on 2 selected: #1, #2.'
3657 assert_select '#errorExplanation span', :text => 'Failed to save 2 issue(s) on 2 selected: #1, #2.'
3658 assert_select '#errorExplanation ul li', :text => 'Start date is not a valid date: #1, #2'
3658 assert_select '#errorExplanation ul li', :text => 'Start date is not a valid date: #1, #2'
3659
3659
3660 assert_equal [1, 2], assigns[:issues].map(&:id)
3660 assert_equal [1, 2], assigns[:issues].map(&:id)
3661 end
3661 end
3662
3662
3663 def test_bulk_update_with_some_failures_should_show_errors
3663 def test_bulk_update_with_some_failures_should_show_errors
3664 issue1 = Issue.generate!(:start_date => '2013-05-12')
3664 issue1 = Issue.generate!(:start_date => '2013-05-12')
3665 issue2 = Issue.generate!(:start_date => '2013-05-15')
3665 issue2 = Issue.generate!(:start_date => '2013-05-15')
3666 issue3 = Issue.generate!
3666 issue3 = Issue.generate!
3667 @request.session[:user_id] = 2
3667 @request.session[:user_id] = 2
3668 post :bulk_update, :ids => [issue1.id, issue2.id, issue3.id],
3668 post :bulk_update, :ids => [issue1.id, issue2.id, issue3.id],
3669 :issue => {:due_date => '2013-05-01'}
3669 :issue => {:due_date => '2013-05-01'}
3670 assert_response :success
3670 assert_response :success
3671 assert_template 'bulk_edit'
3671 assert_template 'bulk_edit'
3672 assert_select '#errorExplanation span',
3672 assert_select '#errorExplanation span',
3673 :text => "Failed to save 2 issue(s) on 3 selected: ##{issue1.id}, ##{issue2.id}."
3673 :text => "Failed to save 2 issue(s) on 3 selected: ##{issue1.id}, ##{issue2.id}."
3674 assert_select '#errorExplanation ul li',
3674 assert_select '#errorExplanation ul li',
3675 :text => "Due date must be greater than start date: ##{issue1.id}, ##{issue2.id}"
3675 :text => "Due date must be greater than start date: ##{issue1.id}, ##{issue2.id}"
3676 assert_equal [issue1.id, issue2.id], assigns[:issues].map(&:id)
3676 assert_equal [issue1.id, issue2.id], assigns[:issues].map(&:id)
3677 end
3677 end
3678
3678
3679 def test_bulk_update_with_failure_should_preserved_form_values
3679 def test_bulk_update_with_failure_should_preserved_form_values
3680 @request.session[:user_id] = 2
3680 @request.session[:user_id] = 2
3681 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2', :start_date => 'foo'}
3681 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2', :start_date => 'foo'}
3682
3682
3683 assert_response :success
3683 assert_response :success
3684 assert_template 'bulk_edit'
3684 assert_template 'bulk_edit'
3685 assert_select 'select[name=?]', 'issue[tracker_id]' do
3685 assert_select 'select[name=?]', 'issue[tracker_id]' do
3686 assert_select 'option[value=2][selected=selected]'
3686 assert_select 'option[value=2][selected=selected]'
3687 end
3687 end
3688 assert_select 'input[name=?][value=?]', 'issue[start_date]', 'foo'
3688 assert_select 'input[name=?][value=?]', 'issue[start_date]', 'foo'
3689 end
3689 end
3690
3690
3691 def test_get_bulk_copy
3691 def test_get_bulk_copy
3692 @request.session[:user_id] = 2
3692 @request.session[:user_id] = 2
3693 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3693 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3694 assert_response :success
3694 assert_response :success
3695 assert_template 'bulk_edit'
3695 assert_template 'bulk_edit'
3696
3696
3697 issues = assigns(:issues)
3697 issues = assigns(:issues)
3698 assert_not_nil issues
3698 assert_not_nil issues
3699 assert_equal [1, 2, 3], issues.map(&:id).sort
3699 assert_equal [1, 2, 3], issues.map(&:id).sort
3700
3700
3701 assert_select 'input[name=copy_attachments]'
3701 assert_select 'input[name=copy_attachments]'
3702 end
3702 end
3703
3703
3704 def test_bulk_copy_to_another_project
3704 def test_bulk_copy_to_another_project
3705 @request.session[:user_id] = 2
3705 @request.session[:user_id] = 2
3706 assert_difference 'Issue.count', 2 do
3706 assert_difference 'Issue.count', 2 do
3707 assert_no_difference 'Project.find(1).issues.count' do
3707 assert_no_difference 'Project.find(1).issues.count' do
3708 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3708 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3709 end
3709 end
3710 end
3710 end
3711 assert_redirected_to '/projects/ecookbook/issues'
3711 assert_redirected_to '/projects/ecookbook/issues'
3712
3712
3713 copies = Issue.order('id DESC').limit(issues.size)
3713 copies = Issue.order('id DESC').limit(issues.size)
3714 copies.each do |copy|
3714 copies.each do |copy|
3715 assert_equal 2, copy.project_id
3715 assert_equal 2, copy.project_id
3716 end
3716 end
3717 end
3717 end
3718
3718
3719 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3719 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3720 @request.session[:user_id] = 2
3720 @request.session[:user_id] = 2
3721 issues = [
3721 issues = [
3722 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
3722 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
3723 :priority_id => 2, :subject => 'issue 1', :author_id => 1,
3723 :priority_id => 2, :subject => 'issue 1', :author_id => 1,
3724 :assigned_to_id => nil),
3724 :assigned_to_id => nil),
3725 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2,
3725 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2,
3726 :priority_id => 1, :subject => 'issue 2', :author_id => 2,
3726 :priority_id => 1, :subject => 'issue 2', :author_id => 2,
3727 :assigned_to_id => 3)
3727 :assigned_to_id => 3)
3728 ]
3728 ]
3729 assert_difference 'Issue.count', issues.size do
3729 assert_difference 'Issue.count', issues.size do
3730 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3730 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3731 :issue => {
3731 :issue => {
3732 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3732 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3733 :status_id => '', :start_date => '', :due_date => ''
3733 :status_id => '', :start_date => '', :due_date => ''
3734 }
3734 }
3735 end
3735 end
3736
3736
3737 copies = Issue.order('id DESC').limit(issues.size)
3737 copies = Issue.order('id DESC').limit(issues.size)
3738 issues.each do |orig|
3738 issues.each do |orig|
3739 copy = copies.detect {|c| c.subject == orig.subject}
3739 copy = copies.detect {|c| c.subject == orig.subject}
3740 assert_not_nil copy
3740 assert_not_nil copy
3741 assert_equal orig.project_id, copy.project_id
3741 assert_equal orig.project_id, copy.project_id
3742 assert_equal orig.tracker_id, copy.tracker_id
3742 assert_equal orig.tracker_id, copy.tracker_id
3743 assert_equal orig.status_id, copy.status_id
3743 assert_equal orig.status_id, copy.status_id
3744 assert_equal orig.assigned_to_id, copy.assigned_to_id
3744 assert_equal orig.assigned_to_id, copy.assigned_to_id
3745 assert_equal orig.priority_id, copy.priority_id
3745 assert_equal orig.priority_id, copy.priority_id
3746 end
3746 end
3747 end
3747 end
3748
3748
3749 def test_bulk_copy_should_allow_changing_the_issue_attributes
3749 def test_bulk_copy_should_allow_changing_the_issue_attributes
3750 # Fixes random test failure with Mysql
3750 # Fixes random test failure with Mysql
3751 # where Issue.where(:project_id => 2).limit(2).order('id desc')
3751 # where Issue.where(:project_id => 2).limit(2).order('id desc')
3752 # doesn't return the expected results
3752 # doesn't return the expected results
3753 Issue.delete_all("project_id=2")
3753 Issue.delete_all("project_id=2")
3754
3754
3755 @request.session[:user_id] = 2
3755 @request.session[:user_id] = 2
3756 assert_difference 'Issue.count', 2 do
3756 assert_difference 'Issue.count', 2 do
3757 assert_no_difference 'Project.find(1).issues.count' do
3757 assert_no_difference 'Project.find(1).issues.count' do
3758 post :bulk_update, :ids => [1, 2], :copy => '1',
3758 post :bulk_update, :ids => [1, 2], :copy => '1',
3759 :issue => {
3759 :issue => {
3760 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3760 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3761 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3761 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3762 }
3762 }
3763 end
3763 end
3764 end
3764 end
3765
3765
3766 copied_issues = Issue.where(:project_id => 2).limit(2).order('id desc').to_a
3766 copied_issues = Issue.where(:project_id => 2).limit(2).order('id desc').to_a
3767 assert_equal 2, copied_issues.size
3767 assert_equal 2, copied_issues.size
3768 copied_issues.each do |issue|
3768 copied_issues.each do |issue|
3769 assert_equal 2, issue.project_id, "Project is incorrect"
3769 assert_equal 2, issue.project_id, "Project is incorrect"
3770 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3770 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3771 assert_equal 1, issue.status_id, "Status is incorrect"
3771 assert_equal 1, issue.status_id, "Status is incorrect"
3772 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3772 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3773 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3773 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3774 end
3774 end
3775 end
3775 end
3776
3776
3777 def test_bulk_copy_should_allow_adding_a_note
3777 def test_bulk_copy_should_allow_adding_a_note
3778 @request.session[:user_id] = 2
3778 @request.session[:user_id] = 2
3779 assert_difference 'Issue.count', 1 do
3779 assert_difference 'Issue.count', 1 do
3780 post :bulk_update, :ids => [1], :copy => '1',
3780 post :bulk_update, :ids => [1], :copy => '1',
3781 :notes => 'Copying one issue',
3781 :notes => 'Copying one issue',
3782 :issue => {
3782 :issue => {
3783 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3783 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3784 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3784 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3785 }
3785 }
3786 end
3786 end
3787 issue = Issue.order('id DESC').first
3787 issue = Issue.order('id DESC').first
3788 assert_equal 1, issue.journals.size
3788 assert_equal 1, issue.journals.size
3789 journal = issue.journals.first
3789 journal = issue.journals.first
3790 assert_equal 1, journal.details.size
3790 assert_equal 1, journal.details.size
3791 assert_equal 'Copying one issue', journal.notes
3791 assert_equal 'Copying one issue', journal.notes
3792 end
3792 end
3793
3793
3794 def test_bulk_copy_should_allow_not_copying_the_attachments
3794 def test_bulk_copy_should_allow_not_copying_the_attachments
3795 attachment_count = Issue.find(3).attachments.size
3795 attachment_count = Issue.find(3).attachments.size
3796 assert attachment_count > 0
3796 assert attachment_count > 0
3797 @request.session[:user_id] = 2
3797 @request.session[:user_id] = 2
3798
3798
3799 assert_difference 'Issue.count', 1 do
3799 assert_difference 'Issue.count', 1 do
3800 assert_no_difference 'Attachment.count' do
3800 assert_no_difference 'Attachment.count' do
3801 post :bulk_update, :ids => [3], :copy => '1',
3801 post :bulk_update, :ids => [3], :copy => '1',
3802 :issue => {
3802 :issue => {
3803 :project_id => ''
3803 :project_id => ''
3804 }
3804 }
3805 end
3805 end
3806 end
3806 end
3807 end
3807 end
3808
3808
3809 def test_bulk_copy_should_allow_copying_the_attachments
3809 def test_bulk_copy_should_allow_copying_the_attachments
3810 attachment_count = Issue.find(3).attachments.size
3810 attachment_count = Issue.find(3).attachments.size
3811 assert attachment_count > 0
3811 assert attachment_count > 0
3812 @request.session[:user_id] = 2
3812 @request.session[:user_id] = 2
3813
3813
3814 assert_difference 'Issue.count', 1 do
3814 assert_difference 'Issue.count', 1 do
3815 assert_difference 'Attachment.count', attachment_count do
3815 assert_difference 'Attachment.count', attachment_count do
3816 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3816 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3817 :issue => {
3817 :issue => {
3818 :project_id => ''
3818 :project_id => ''
3819 }
3819 }
3820 end
3820 end
3821 end
3821 end
3822 end
3822 end
3823
3823
3824 def test_bulk_copy_should_add_relations_with_copied_issues
3824 def test_bulk_copy_should_add_relations_with_copied_issues
3825 @request.session[:user_id] = 2
3825 @request.session[:user_id] = 2
3826
3826
3827 assert_difference 'Issue.count', 2 do
3827 assert_difference 'Issue.count', 2 do
3828 assert_difference 'IssueRelation.count', 2 do
3828 assert_difference 'IssueRelation.count', 2 do
3829 post :bulk_update, :ids => [1, 3], :copy => '1',
3829 post :bulk_update, :ids => [1, 3], :copy => '1',
3830 :issue => {
3830 :issue => {
3831 :project_id => '1'
3831 :project_id => '1'
3832 }
3832 }
3833 end
3833 end
3834 end
3834 end
3835 end
3835 end
3836
3836
3837 def test_bulk_copy_should_allow_not_copying_the_subtasks
3837 def test_bulk_copy_should_allow_not_copying_the_subtasks
3838 issue = Issue.generate_with_descendants!
3838 issue = Issue.generate_with_descendants!
3839 @request.session[:user_id] = 2
3839 @request.session[:user_id] = 2
3840
3840
3841 assert_difference 'Issue.count', 1 do
3841 assert_difference 'Issue.count', 1 do
3842 post :bulk_update, :ids => [issue.id], :copy => '1',
3842 post :bulk_update, :ids => [issue.id], :copy => '1',
3843 :issue => {
3843 :issue => {
3844 :project_id => ''
3844 :project_id => ''
3845 }
3845 }
3846 end
3846 end
3847 end
3847 end
3848
3848
3849 def test_bulk_copy_should_allow_copying_the_subtasks
3849 def test_bulk_copy_should_allow_copying_the_subtasks
3850 issue = Issue.generate_with_descendants!
3850 issue = Issue.generate_with_descendants!
3851 count = issue.descendants.count
3851 count = issue.descendants.count
3852 @request.session[:user_id] = 2
3852 @request.session[:user_id] = 2
3853
3853
3854 assert_difference 'Issue.count', count+1 do
3854 assert_difference 'Issue.count', count+1 do
3855 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3855 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3856 :issue => {
3856 :issue => {
3857 :project_id => ''
3857 :project_id => ''
3858 }
3858 }
3859 end
3859 end
3860 copy = Issue.where(:parent_id => nil).order("id DESC").first
3860 copy = Issue.where(:parent_id => nil).order("id DESC").first
3861 assert_equal count, copy.descendants.count
3861 assert_equal count, copy.descendants.count
3862 end
3862 end
3863
3863
3864 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3864 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3865 issue = Issue.generate_with_descendants!
3865 issue = Issue.generate_with_descendants!
3866 count = issue.descendants.count
3866 count = issue.descendants.count
3867 @request.session[:user_id] = 2
3867 @request.session[:user_id] = 2
3868
3868
3869 assert_difference 'Issue.count', count+1 do
3869 assert_difference 'Issue.count', count+1 do
3870 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3870 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3871 :issue => {
3871 :issue => {
3872 :project_id => ''
3872 :project_id => ''
3873 }
3873 }
3874 end
3874 end
3875 copy = Issue.where(:parent_id => nil).order("id DESC").first
3875 copy = Issue.where(:parent_id => nil).order("id DESC").first
3876 assert_equal count, copy.descendants.count
3876 assert_equal count, copy.descendants.count
3877 end
3877 end
3878
3878
3879 def test_bulk_copy_to_another_project_should_follow_when_needed
3879 def test_bulk_copy_to_another_project_should_follow_when_needed
3880 @request.session[:user_id] = 2
3880 @request.session[:user_id] = 2
3881 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3881 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3882 issue = Issue.order('id DESC').first
3882 issue = Issue.order('id DESC').first
3883 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3883 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3884 end
3884 end
3885
3885
3886 def test_bulk_copy_with_all_failures_should_display_errors
3886 def test_bulk_copy_with_all_failures_should_display_errors
3887 @request.session[:user_id] = 2
3887 @request.session[:user_id] = 2
3888 post :bulk_update, :ids => [1, 2], :copy => '1', :issue => {:start_date => 'foo'}
3888 post :bulk_update, :ids => [1, 2], :copy => '1', :issue => {:start_date => 'foo'}
3889
3889
3890 assert_response :success
3890 assert_response :success
3891 end
3891 end
3892
3892
3893 def test_destroy_issue_with_no_time_entries
3893 def test_destroy_issue_with_no_time_entries
3894 assert_nil TimeEntry.find_by_issue_id(2)
3894 assert_nil TimeEntry.find_by_issue_id(2)
3895 @request.session[:user_id] = 2
3895 @request.session[:user_id] = 2
3896
3896
3897 assert_difference 'Issue.count', -1 do
3897 assert_difference 'Issue.count', -1 do
3898 delete :destroy, :id => 2
3898 delete :destroy, :id => 2
3899 end
3899 end
3900 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3900 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3901 assert_nil Issue.find_by_id(2)
3901 assert_nil Issue.find_by_id(2)
3902 end
3902 end
3903
3903
3904 def test_destroy_issues_with_time_entries
3904 def test_destroy_issues_with_time_entries
3905 @request.session[:user_id] = 2
3905 @request.session[:user_id] = 2
3906
3906
3907 assert_no_difference 'Issue.count' do
3907 assert_no_difference 'Issue.count' do
3908 delete :destroy, :ids => [1, 3]
3908 delete :destroy, :ids => [1, 3]
3909 end
3909 end
3910 assert_response :success
3910 assert_response :success
3911 assert_template 'destroy'
3911 assert_template 'destroy'
3912 assert_not_nil assigns(:hours)
3912 assert_not_nil assigns(:hours)
3913 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3913 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3914
3914
3915 assert_select 'form' do
3915 assert_select 'form' do
3916 assert_select 'input[name=_method][value=delete]'
3916 assert_select 'input[name=_method][value=delete]'
3917 end
3917 end
3918 end
3918 end
3919
3919
3920 def test_destroy_issues_and_destroy_time_entries
3920 def test_destroy_issues_and_destroy_time_entries
3921 @request.session[:user_id] = 2
3921 @request.session[:user_id] = 2
3922
3922
3923 assert_difference 'Issue.count', -2 do
3923 assert_difference 'Issue.count', -2 do
3924 assert_difference 'TimeEntry.count', -3 do
3924 assert_difference 'TimeEntry.count', -3 do
3925 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3925 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3926 end
3926 end
3927 end
3927 end
3928 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3928 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3929 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3929 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3930 assert_nil TimeEntry.find_by_id([1, 2])
3930 assert_nil TimeEntry.find_by_id([1, 2])
3931 end
3931 end
3932
3932
3933 def test_destroy_issues_and_assign_time_entries_to_project
3933 def test_destroy_issues_and_assign_time_entries_to_project
3934 @request.session[:user_id] = 2
3934 @request.session[:user_id] = 2
3935
3935
3936 assert_difference 'Issue.count', -2 do
3936 assert_difference 'Issue.count', -2 do
3937 assert_no_difference 'TimeEntry.count' do
3937 assert_no_difference 'TimeEntry.count' do
3938 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3938 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3939 end
3939 end
3940 end
3940 end
3941 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3941 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3942 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3942 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3943 assert_nil TimeEntry.find(1).issue_id
3943 assert_nil TimeEntry.find(1).issue_id
3944 assert_nil TimeEntry.find(2).issue_id
3944 assert_nil TimeEntry.find(2).issue_id
3945 end
3945 end
3946
3946
3947 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3947 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3948 @request.session[:user_id] = 2
3948 @request.session[:user_id] = 2
3949
3949
3950 assert_difference 'Issue.count', -2 do
3950 assert_difference 'Issue.count', -2 do
3951 assert_no_difference 'TimeEntry.count' do
3951 assert_no_difference 'TimeEntry.count' do
3952 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3952 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3953 end
3953 end
3954 end
3954 end
3955 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3955 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3956 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3956 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3957 assert_equal 2, TimeEntry.find(1).issue_id
3957 assert_equal 2, TimeEntry.find(1).issue_id
3958 assert_equal 2, TimeEntry.find(2).issue_id
3958 assert_equal 2, TimeEntry.find(2).issue_id
3959 end
3959 end
3960
3960
3961 def test_destroy_issues_from_different_projects
3961 def test_destroy_issues_from_different_projects
3962 @request.session[:user_id] = 2
3962 @request.session[:user_id] = 2
3963
3963
3964 assert_difference 'Issue.count', -3 do
3964 assert_difference 'Issue.count', -3 do
3965 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3965 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3966 end
3966 end
3967 assert_redirected_to :controller => 'issues', :action => 'index'
3967 assert_redirected_to :controller => 'issues', :action => 'index'
3968 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3968 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3969 end
3969 end
3970
3970
3971 def test_destroy_parent_and_child_issues
3971 def test_destroy_parent_and_child_issues
3972 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3972 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3973 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3973 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3974 assert child.is_descendant_of?(parent.reload)
3974 assert child.is_descendant_of?(parent.reload)
3975
3975
3976 @request.session[:user_id] = 2
3976 @request.session[:user_id] = 2
3977 assert_difference 'Issue.count', -2 do
3977 assert_difference 'Issue.count', -2 do
3978 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3978 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3979 end
3979 end
3980 assert_response 302
3980 assert_response 302
3981 end
3981 end
3982
3982
3983 def test_destroy_invalid_should_respond_with_404
3983 def test_destroy_invalid_should_respond_with_404
3984 @request.session[:user_id] = 2
3984 @request.session[:user_id] = 2
3985 assert_no_difference 'Issue.count' do
3985 assert_no_difference 'Issue.count' do
3986 delete :destroy, :id => 999
3986 delete :destroy, :id => 999
3987 end
3987 end
3988 assert_response 404
3988 assert_response 404
3989 end
3989 end
3990
3990
3991 def test_default_search_scope
3991 def test_default_search_scope
3992 get :index
3992 get :index
3993
3993
3994 assert_select 'div#quick-search form' do
3994 assert_select 'div#quick-search form' do
3995 assert_select 'input[name=issues][value=1][type=hidden]'
3995 assert_select 'input[name=issues][value=1][type=hidden]'
3996 end
3996 end
3997 end
3997 end
3998 end
3998 end
@@ -1,85 +1,94
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class ProjectsHelperTest < ActionView::TestCase
20 class ProjectsHelperTest < ActionView::TestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ProjectsHelper
22 include ProjectsHelper
23 include Redmine::I18n
23 include Redmine::I18n
24 include ERB::Util
24 include ERB::Util
25 include Rails.application.routes.url_helpers
25 include Rails.application.routes.url_helpers
26
26
27 fixtures :projects, :trackers, :issue_statuses, :issues,
27 fixtures :projects, :trackers, :issue_statuses, :issues,
28 :enumerations, :users, :issue_categories,
28 :enumerations, :users, :issue_categories,
29 :versions,
29 :versions,
30 :projects_trackers,
30 :projects_trackers,
31 :member_roles,
31 :member_roles,
32 :members,
32 :members,
33 :groups_users,
33 :groups_users,
34 :enabled_modules
34 :enabled_modules
35
35
36 def setup
36 def setup
37 super
37 super
38 set_language_if_valid('en')
38 set_language_if_valid('en')
39 User.current = nil
39 User.current = nil
40 end
40 end
41
41
42 def test_link_to_version_within_project
42 def test_link_to_version_within_project
43 @project = Project.find(2)
43 @project = Project.find(2)
44 User.current = User.find(1)
44 User.current = User.find(1)
45 assert_equal '<a href="/versions/5" title="07/01/2006">Alpha</a>', link_to_version(Version.find(5))
45 assert_equal '<a href="/versions/5" title="07/01/2006">Alpha</a>', link_to_version(Version.find(5))
46 end
46 end
47
47
48 def test_link_to_version
48 def test_link_to_version
49 User.current = User.find(1)
49 User.current = User.find(1)
50 assert_equal '<a href="/versions/5" title="07/01/2006">OnlineStore - Alpha</a>', link_to_version(Version.find(5))
50 assert_equal '<a href="/versions/5" title="07/01/2006">Alpha</a>', link_to_version(Version.find(5))
51 end
51 end
52
52
53 def test_link_to_version_without_effective_date
53 def test_link_to_version_without_effective_date
54 User.current = User.find(1)
54 User.current = User.find(1)
55 version = Version.find(5)
55 version = Version.find(5)
56 version.effective_date = nil
56 version.effective_date = nil
57 assert_equal '<a href="/versions/5">OnlineStore - Alpha</a>', link_to_version(version)
57 assert_equal '<a href="/versions/5">Alpha</a>', link_to_version(version)
58 end
58 end
59
59
60 def test_link_to_private_version
60 def test_link_to_private_version
61 assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5))
61 assert_equal 'Alpha', link_to_version(Version.find(5))
62 end
62 end
63
63
64 def test_link_to_version_invalid_version
64 def test_link_to_version_invalid_version
65 assert_equal '', link_to_version(Object)
65 assert_equal '', link_to_version(Object)
66 end
66 end
67
67
68 def test_format_version_name_within_project
68 def test_format_version_name_within_project
69 @project = Project.find(1)
69 @project = Project.find(1)
70 assert_equal "0.1", format_version_name(Version.find(1))
70 assert_equal "0.1", format_version_name(Version.find(1))
71 end
71 end
72
72
73 def test_format_version_name
73 def test_format_version_name
74 assert_equal "eCookbook - 0.1", format_version_name(Version.find(1))
74 assert_equal "0.1", format_version_name(Version.find(1))
75 end
76
77 def test_format_version_name_for_shared_version_within_project_should_not_display_project_name
78 @project = Project.find(1)
79 version = Version.find(1)
80 version.sharing = 'system'
81 assert_equal "0.1", format_version_name(version)
75 end
82 end
76
83
77 def test_format_version_name_for_system_version
84 def test_format_version_name_for_shared_version_should_display_project_name
78 assert_equal "OnlineStore - Systemwide visible version", format_version_name(Version.find(7))
85 version = Version.find(1)
86 version.sharing = 'system'
87 assert_equal "eCookbook - 0.1", format_version_name(version)
79 end
88 end
80
89
81 def test_version_options_for_select_with_no_versions
90 def test_version_options_for_select_with_no_versions
82 assert_equal '', version_options_for_select([])
91 assert_equal '', version_options_for_select([])
83 assert_equal '', version_options_for_select([], Version.find(1))
92 assert_equal '', version_options_for_select([], Version.find(1))
84 end
93 end
85 end
94 end
General Comments 0
You need to be logged in to leave comments. Login now