##// END OF EJS Templates
Merged r14108 (#19348)....
Jean-Philippe Lang -
r13728:7783d7557fdb
parent child
Show More
@@ -1,1363 +1,1363
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 # Copyright (C) 2006-2015 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.shared? || version.project == @project
250 if 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 = ''.html_safe
352 s = ''.html_safe
353 if options[:include_blank]
353 if options[:include_blank]
354 s << content_tag('option', '&nbsp;'.html_safe, :value => '')
354 s << content_tag('option', '&nbsp;'.html_safe, :value => '')
355 end
355 end
356 project_tree(projects) do |project, level|
356 project_tree(projects) do |project, level|
357 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
357 name_prefix = (level > 0 ? '&nbsp;' * 2 * level + '&#187; ' : '').html_safe
358 tag_options = {:value => project.id}
358 tag_options = {:value => project.id}
359 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
359 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
360 tag_options[:selected] = 'selected'
360 tag_options[:selected] = 'selected'
361 else
361 else
362 tag_options[:selected] = nil
362 tag_options[:selected] = nil
363 end
363 end
364 tag_options.merge!(yield(project)) if block_given?
364 tag_options.merge!(yield(project)) if block_given?
365 s << content_tag('option', name_prefix + h(project), tag_options)
365 s << content_tag('option', name_prefix + h(project), tag_options)
366 end
366 end
367 s.html_safe
367 s.html_safe
368 end
368 end
369
369
370 # Yields the given block for each project with its level in the tree
370 # Yields the given block for each project with its level in the tree
371 #
371 #
372 # Wrapper for Project#project_tree
372 # Wrapper for Project#project_tree
373 def project_tree(projects, &block)
373 def project_tree(projects, &block)
374 Project.project_tree(projects, &block)
374 Project.project_tree(projects, &block)
375 end
375 end
376
376
377 def principals_check_box_tags(name, principals)
377 def principals_check_box_tags(name, principals)
378 s = ''
378 s = ''
379 principals.each do |principal|
379 principals.each do |principal|
380 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
380 s << "<label>#{ check_box_tag name, principal.id, false, :id => nil } #{h principal}</label>\n"
381 end
381 end
382 s.html_safe
382 s.html_safe
383 end
383 end
384
384
385 # Returns a string for users/groups option tags
385 # Returns a string for users/groups option tags
386 def principals_options_for_select(collection, selected=nil)
386 def principals_options_for_select(collection, selected=nil)
387 s = ''
387 s = ''
388 if collection.include?(User.current)
388 if collection.include?(User.current)
389 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
389 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
390 end
390 end
391 groups = ''
391 groups = ''
392 collection.sort.each do |element|
392 collection.sort.each do |element|
393 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
393 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected) || element.id.to_s == selected
394 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
394 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
395 end
395 end
396 unless groups.empty?
396 unless groups.empty?
397 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
397 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
398 end
398 end
399 s.html_safe
399 s.html_safe
400 end
400 end
401
401
402 # Options for the new membership projects combo-box
402 # Options for the new membership projects combo-box
403 def options_for_membership_project_select(principal, projects)
403 def options_for_membership_project_select(principal, projects)
404 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
404 options = content_tag('option', "--- #{l(:actionview_instancetag_blank_option)} ---")
405 options << project_tree_options_for_select(projects) do |p|
405 options << project_tree_options_for_select(projects) do |p|
406 {:disabled => principal.projects.to_a.include?(p)}
406 {:disabled => principal.projects.to_a.include?(p)}
407 end
407 end
408 options
408 options
409 end
409 end
410
410
411 def option_tag(name, text, value, selected=nil, options={})
411 def option_tag(name, text, value, selected=nil, options={})
412 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
412 content_tag 'option', value, options.merge(:value => value, :selected => (value == selected))
413 end
413 end
414
414
415 # Truncates and returns the string as a single line
415 # Truncates and returns the string as a single line
416 def truncate_single_line(string, *args)
416 def truncate_single_line(string, *args)
417 ActiveSupport::Deprecation.warn(
417 ActiveSupport::Deprecation.warn(
418 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
418 "ApplicationHelper#truncate_single_line is deprecated and will be removed in Rails 4 poring")
419 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
419 # Rails 4 ActionView::Helpers::TextHelper#truncate escapes.
420 # So, result is broken.
420 # So, result is broken.
421 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
421 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
422 end
422 end
423
423
424 def truncate_single_line_raw(string, length)
424 def truncate_single_line_raw(string, length)
425 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
425 string.truncate(length).gsub(%r{[\r\n]+}m, ' ')
426 end
426 end
427
427
428 # Truncates at line break after 250 characters or options[:length]
428 # Truncates at line break after 250 characters or options[:length]
429 def truncate_lines(string, options={})
429 def truncate_lines(string, options={})
430 length = options[:length] || 250
430 length = options[:length] || 250
431 if string.to_s =~ /\A(.{#{length}}.*?)$/m
431 if string.to_s =~ /\A(.{#{length}}.*?)$/m
432 "#{$1}..."
432 "#{$1}..."
433 else
433 else
434 string
434 string
435 end
435 end
436 end
436 end
437
437
438 def anchor(text)
438 def anchor(text)
439 text.to_s.gsub(' ', '_')
439 text.to_s.gsub(' ', '_')
440 end
440 end
441
441
442 def html_hours(text)
442 def html_hours(text)
443 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
443 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
444 end
444 end
445
445
446 def authoring(created, author, options={})
446 def authoring(created, author, options={})
447 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
447 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
448 end
448 end
449
449
450 def time_tag(time)
450 def time_tag(time)
451 text = distance_of_time_in_words(Time.now, time)
451 text = distance_of_time_in_words(Time.now, time)
452 if @project
452 if @project
453 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
453 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => User.current.time_to_date(time)}, :title => format_time(time))
454 else
454 else
455 content_tag('abbr', text, :title => format_time(time))
455 content_tag('abbr', text, :title => format_time(time))
456 end
456 end
457 end
457 end
458
458
459 def syntax_highlight_lines(name, content)
459 def syntax_highlight_lines(name, content)
460 lines = []
460 lines = []
461 syntax_highlight(name, content).each_line { |line| lines << line }
461 syntax_highlight(name, content).each_line { |line| lines << line }
462 lines
462 lines
463 end
463 end
464
464
465 def syntax_highlight(name, content)
465 def syntax_highlight(name, content)
466 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
466 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
467 end
467 end
468
468
469 def to_path_param(path)
469 def to_path_param(path)
470 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
470 str = path.to_s.split(%r{[/\\]}).select{|p| !p.blank?}.join("/")
471 str.blank? ? nil : str
471 str.blank? ? nil : str
472 end
472 end
473
473
474 def reorder_links(name, url, method = :post)
474 def reorder_links(name, url, method = :post)
475 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
475 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
476 url.merge({"#{name}[move_to]" => 'highest'}),
476 url.merge({"#{name}[move_to]" => 'highest'}),
477 :method => method, :title => l(:label_sort_highest)) +
477 :method => method, :title => l(:label_sort_highest)) +
478 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
478 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
479 url.merge({"#{name}[move_to]" => 'higher'}),
479 url.merge({"#{name}[move_to]" => 'higher'}),
480 :method => method, :title => l(:label_sort_higher)) +
480 :method => method, :title => l(:label_sort_higher)) +
481 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
481 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
482 url.merge({"#{name}[move_to]" => 'lower'}),
482 url.merge({"#{name}[move_to]" => 'lower'}),
483 :method => method, :title => l(:label_sort_lower)) +
483 :method => method, :title => l(:label_sort_lower)) +
484 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
484 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
485 url.merge({"#{name}[move_to]" => 'lowest'}),
485 url.merge({"#{name}[move_to]" => 'lowest'}),
486 :method => method, :title => l(:label_sort_lowest))
486 :method => method, :title => l(:label_sort_lowest))
487 end
487 end
488
488
489 def breadcrumb(*args)
489 def breadcrumb(*args)
490 elements = args.flatten
490 elements = args.flatten
491 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
491 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
492 end
492 end
493
493
494 def other_formats_links(&block)
494 def other_formats_links(&block)
495 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
495 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
496 yield Redmine::Views::OtherFormatsBuilder.new(self)
496 yield Redmine::Views::OtherFormatsBuilder.new(self)
497 concat('</p>'.html_safe)
497 concat('</p>'.html_safe)
498 end
498 end
499
499
500 def page_header_title
500 def page_header_title
501 if @project.nil? || @project.new_record?
501 if @project.nil? || @project.new_record?
502 h(Setting.app_title)
502 h(Setting.app_title)
503 else
503 else
504 b = []
504 b = []
505 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
505 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
506 if ancestors.any?
506 if ancestors.any?
507 root = ancestors.shift
507 root = ancestors.shift
508 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
508 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
509 if ancestors.size > 2
509 if ancestors.size > 2
510 b << "\xe2\x80\xa6"
510 b << "\xe2\x80\xa6"
511 ancestors = ancestors[-2, 2]
511 ancestors = ancestors[-2, 2]
512 end
512 end
513 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
513 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
514 end
514 end
515 b << h(@project)
515 b << h(@project)
516 b.join(" \xc2\xbb ").html_safe
516 b.join(" \xc2\xbb ").html_safe
517 end
517 end
518 end
518 end
519
519
520 # Returns a h2 tag and sets the html title with the given arguments
520 # Returns a h2 tag and sets the html title with the given arguments
521 def title(*args)
521 def title(*args)
522 strings = args.map do |arg|
522 strings = args.map do |arg|
523 if arg.is_a?(Array) && arg.size >= 2
523 if arg.is_a?(Array) && arg.size >= 2
524 link_to(*arg)
524 link_to(*arg)
525 else
525 else
526 h(arg.to_s)
526 h(arg.to_s)
527 end
527 end
528 end
528 end
529 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
529 html_title args.reverse.map {|s| (s.is_a?(Array) ? s.first : s).to_s}
530 content_tag('h2', strings.join(' &#187; ').html_safe)
530 content_tag('h2', strings.join(' &#187; ').html_safe)
531 end
531 end
532
532
533 # Sets the html title
533 # Sets the html title
534 # Returns the html title when called without arguments
534 # Returns the html title when called without arguments
535 # Current project name and app_title and automatically appended
535 # Current project name and app_title and automatically appended
536 # Exemples:
536 # Exemples:
537 # html_title 'Foo', 'Bar'
537 # html_title 'Foo', 'Bar'
538 # html_title # => 'Foo - Bar - My Project - Redmine'
538 # html_title # => 'Foo - Bar - My Project - Redmine'
539 def html_title(*args)
539 def html_title(*args)
540 if args.empty?
540 if args.empty?
541 title = @html_title || []
541 title = @html_title || []
542 title << @project.name if @project
542 title << @project.name if @project
543 title << Setting.app_title unless Setting.app_title == title.last
543 title << Setting.app_title unless Setting.app_title == title.last
544 title.reject(&:blank?).join(' - ')
544 title.reject(&:blank?).join(' - ')
545 else
545 else
546 @html_title ||= []
546 @html_title ||= []
547 @html_title += args
547 @html_title += args
548 end
548 end
549 end
549 end
550
550
551 # Returns the theme, controller name, and action as css classes for the
551 # Returns the theme, controller name, and action as css classes for the
552 # HTML body.
552 # HTML body.
553 def body_css_classes
553 def body_css_classes
554 css = []
554 css = []
555 if theme = Redmine::Themes.theme(Setting.ui_theme)
555 if theme = Redmine::Themes.theme(Setting.ui_theme)
556 css << 'theme-' + theme.name
556 css << 'theme-' + theme.name
557 end
557 end
558
558
559 css << 'project-' + @project.identifier if @project && @project.identifier.present?
559 css << 'project-' + @project.identifier if @project && @project.identifier.present?
560 css << 'controller-' + controller_name
560 css << 'controller-' + controller_name
561 css << 'action-' + action_name
561 css << 'action-' + action_name
562 css.join(' ')
562 css.join(' ')
563 end
563 end
564
564
565 def accesskey(s)
565 def accesskey(s)
566 @used_accesskeys ||= []
566 @used_accesskeys ||= []
567 key = Redmine::AccessKeys.key_for(s)
567 key = Redmine::AccessKeys.key_for(s)
568 return nil if @used_accesskeys.include?(key)
568 return nil if @used_accesskeys.include?(key)
569 @used_accesskeys << key
569 @used_accesskeys << key
570 key
570 key
571 end
571 end
572
572
573 # Formats text according to system settings.
573 # Formats text according to system settings.
574 # 2 ways to call this method:
574 # 2 ways to call this method:
575 # * with a String: textilizable(text, options)
575 # * with a String: textilizable(text, options)
576 # * with an object and one of its attribute: textilizable(issue, :description, options)
576 # * with an object and one of its attribute: textilizable(issue, :description, options)
577 def textilizable(*args)
577 def textilizable(*args)
578 options = args.last.is_a?(Hash) ? args.pop : {}
578 options = args.last.is_a?(Hash) ? args.pop : {}
579 case args.size
579 case args.size
580 when 1
580 when 1
581 obj = options[:object]
581 obj = options[:object]
582 text = args.shift
582 text = args.shift
583 when 2
583 when 2
584 obj = args.shift
584 obj = args.shift
585 attr = args.shift
585 attr = args.shift
586 text = obj.send(attr).to_s
586 text = obj.send(attr).to_s
587 else
587 else
588 raise ArgumentError, 'invalid arguments to textilizable'
588 raise ArgumentError, 'invalid arguments to textilizable'
589 end
589 end
590 return '' if text.blank?
590 return '' if text.blank?
591 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
591 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
592 @only_path = only_path = options.delete(:only_path) == false ? false : true
592 @only_path = only_path = options.delete(:only_path) == false ? false : true
593
593
594 text = text.dup
594 text = text.dup
595 macros = catch_macros(text)
595 macros = catch_macros(text)
596 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
596 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
597
597
598 @parsed_headings = []
598 @parsed_headings = []
599 @heading_anchors = {}
599 @heading_anchors = {}
600 @current_section = 0 if options[:edit_section_links]
600 @current_section = 0 if options[:edit_section_links]
601
601
602 parse_sections(text, project, obj, attr, only_path, options)
602 parse_sections(text, project, obj, attr, only_path, options)
603 text = parse_non_pre_blocks(text, obj, macros) do |text|
603 text = parse_non_pre_blocks(text, obj, macros) do |text|
604 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
604 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
605 send method_name, text, project, obj, attr, only_path, options
605 send method_name, text, project, obj, attr, only_path, options
606 end
606 end
607 end
607 end
608 parse_headings(text, project, obj, attr, only_path, options)
608 parse_headings(text, project, obj, attr, only_path, options)
609
609
610 if @parsed_headings.any?
610 if @parsed_headings.any?
611 replace_toc(text, @parsed_headings)
611 replace_toc(text, @parsed_headings)
612 end
612 end
613
613
614 text.html_safe
614 text.html_safe
615 end
615 end
616
616
617 def parse_non_pre_blocks(text, obj, macros)
617 def parse_non_pre_blocks(text, obj, macros)
618 s = StringScanner.new(text)
618 s = StringScanner.new(text)
619 tags = []
619 tags = []
620 parsed = ''
620 parsed = ''
621 while !s.eos?
621 while !s.eos?
622 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
622 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
623 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
623 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
624 if tags.empty?
624 if tags.empty?
625 yield text
625 yield text
626 inject_macros(text, obj, macros) if macros.any?
626 inject_macros(text, obj, macros) if macros.any?
627 else
627 else
628 inject_macros(text, obj, macros, false) if macros.any?
628 inject_macros(text, obj, macros, false) if macros.any?
629 end
629 end
630 parsed << text
630 parsed << text
631 if tag
631 if tag
632 if closing
632 if closing
633 if tags.last == tag.downcase
633 if tags.last == tag.downcase
634 tags.pop
634 tags.pop
635 end
635 end
636 else
636 else
637 tags << tag.downcase
637 tags << tag.downcase
638 end
638 end
639 parsed << full_tag
639 parsed << full_tag
640 end
640 end
641 end
641 end
642 # Close any non closing tags
642 # Close any non closing tags
643 while tag = tags.pop
643 while tag = tags.pop
644 parsed << "</#{tag}>"
644 parsed << "</#{tag}>"
645 end
645 end
646 parsed
646 parsed
647 end
647 end
648
648
649 def parse_inline_attachments(text, project, obj, attr, only_path, options)
649 def parse_inline_attachments(text, project, obj, attr, only_path, options)
650 # when using an image link, try to use an attachment, if possible
650 # when using an image link, try to use an attachment, if possible
651 attachments = options[:attachments] || []
651 attachments = options[:attachments] || []
652 attachments += obj.attachments if obj.respond_to?(:attachments)
652 attachments += obj.attachments if obj.respond_to?(:attachments)
653 if attachments.present?
653 if attachments.present?
654 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
654 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
655 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
655 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
656 # search for the picture in attachments
656 # search for the picture in attachments
657 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
657 if found = Attachment.latest_attach(attachments, CGI.unescape(filename))
658 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
658 image_url = download_named_attachment_path(found, found.filename, :only_path => only_path)
659 desc = found.description.to_s.gsub('"', '')
659 desc = found.description.to_s.gsub('"', '')
660 if !desc.blank? && alttext.blank?
660 if !desc.blank? && alttext.blank?
661 alt = " title=\"#{desc}\" alt=\"#{desc}\""
661 alt = " title=\"#{desc}\" alt=\"#{desc}\""
662 end
662 end
663 "src=\"#{image_url}\"#{alt}"
663 "src=\"#{image_url}\"#{alt}"
664 else
664 else
665 m
665 m
666 end
666 end
667 end
667 end
668 end
668 end
669 end
669 end
670
670
671 # Wiki links
671 # Wiki links
672 #
672 #
673 # Examples:
673 # Examples:
674 # [[mypage]]
674 # [[mypage]]
675 # [[mypage|mytext]]
675 # [[mypage|mytext]]
676 # wiki links can refer other project wikis, using project name or identifier:
676 # wiki links can refer other project wikis, using project name or identifier:
677 # [[project:]] -> wiki starting page
677 # [[project:]] -> wiki starting page
678 # [[project:|mytext]]
678 # [[project:|mytext]]
679 # [[project:mypage]]
679 # [[project:mypage]]
680 # [[project:mypage|mytext]]
680 # [[project:mypage|mytext]]
681 def parse_wiki_links(text, project, obj, attr, only_path, options)
681 def parse_wiki_links(text, project, obj, attr, only_path, options)
682 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
682 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
683 link_project = project
683 link_project = project
684 esc, all, page, title = $1, $2, $3, $5
684 esc, all, page, title = $1, $2, $3, $5
685 if esc.nil?
685 if esc.nil?
686 if page =~ /^([^\:]+)\:(.*)$/
686 if page =~ /^([^\:]+)\:(.*)$/
687 identifier, page = $1, $2
687 identifier, page = $1, $2
688 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
688 link_project = Project.find_by_identifier(identifier) || Project.find_by_name(identifier)
689 title ||= identifier if page.blank?
689 title ||= identifier if page.blank?
690 end
690 end
691
691
692 if link_project && link_project.wiki
692 if link_project && link_project.wiki
693 # extract anchor
693 # extract anchor
694 anchor = nil
694 anchor = nil
695 if page =~ /^(.+?)\#(.+)$/
695 if page =~ /^(.+?)\#(.+)$/
696 page, anchor = $1, $2
696 page, anchor = $1, $2
697 end
697 end
698 anchor = sanitize_anchor_name(anchor) if anchor.present?
698 anchor = sanitize_anchor_name(anchor) if anchor.present?
699 # check if page exists
699 # check if page exists
700 wiki_page = link_project.wiki.find_page(page)
700 wiki_page = link_project.wiki.find_page(page)
701 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
701 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
702 "##{anchor}"
702 "##{anchor}"
703 else
703 else
704 case options[:wiki_links]
704 case options[:wiki_links]
705 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
705 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
706 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
706 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
707 else
707 else
708 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
708 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
709 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
709 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
710 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
710 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
711 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
711 :id => wiki_page_id, :version => nil, :anchor => anchor, :parent => parent)
712 end
712 end
713 end
713 end
714 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
714 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
715 else
715 else
716 # project or wiki doesn't exist
716 # project or wiki doesn't exist
717 all
717 all
718 end
718 end
719 else
719 else
720 all
720 all
721 end
721 end
722 end
722 end
723 end
723 end
724
724
725 # Redmine links
725 # Redmine links
726 #
726 #
727 # Examples:
727 # Examples:
728 # Issues:
728 # Issues:
729 # #52 -> Link to issue #52
729 # #52 -> Link to issue #52
730 # Changesets:
730 # Changesets:
731 # r52 -> Link to revision 52
731 # r52 -> Link to revision 52
732 # commit:a85130f -> Link to scmid starting with a85130f
732 # commit:a85130f -> Link to scmid starting with a85130f
733 # Documents:
733 # Documents:
734 # document#17 -> Link to document with id 17
734 # document#17 -> Link to document with id 17
735 # document:Greetings -> Link to the document with title "Greetings"
735 # document:Greetings -> Link to the document with title "Greetings"
736 # document:"Some document" -> Link to the document with title "Some document"
736 # document:"Some document" -> Link to the document with title "Some document"
737 # Versions:
737 # Versions:
738 # version#3 -> Link to version with id 3
738 # version#3 -> Link to version with id 3
739 # version:1.0.0 -> Link to version named "1.0.0"
739 # version:1.0.0 -> Link to version named "1.0.0"
740 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
740 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
741 # Attachments:
741 # Attachments:
742 # attachment:file.zip -> Link to the attachment of the current object named file.zip
742 # attachment:file.zip -> Link to the attachment of the current object named file.zip
743 # Source files:
743 # Source files:
744 # source:some/file -> Link to the file located at /some/file in the project's repository
744 # source:some/file -> Link to the file located at /some/file in the project's repository
745 # source:some/file@52 -> Link to the file's revision 52
745 # source:some/file@52 -> Link to the file's revision 52
746 # source:some/file#L120 -> Link to line 120 of the file
746 # source:some/file#L120 -> Link to line 120 of the file
747 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
747 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
748 # export:some/file -> Force the download of the file
748 # export:some/file -> Force the download of the file
749 # Forum messages:
749 # Forum messages:
750 # message#1218 -> Link to message with id 1218
750 # message#1218 -> Link to message with id 1218
751 # Projects:
751 # Projects:
752 # project:someproject -> Link to project named "someproject"
752 # project:someproject -> Link to project named "someproject"
753 # project#3 -> Link to project with id 3
753 # project#3 -> Link to project with id 3
754 #
754 #
755 # Links can refer other objects from other projects, using project identifier:
755 # Links can refer other objects from other projects, using project identifier:
756 # identifier:r52
756 # identifier:r52
757 # identifier:document:"Some document"
757 # identifier:document:"Some document"
758 # identifier:version:1.0.0
758 # identifier:version:1.0.0
759 # identifier:source:some/file
759 # identifier:source:some/file
760 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
760 def parse_redmine_links(text, default_project, obj, attr, only_path, options)
761 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|
761 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|
762 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
762 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
763 link = nil
763 link = nil
764 project = default_project
764 project = default_project
765 if project_identifier
765 if project_identifier
766 project = Project.visible.find_by_identifier(project_identifier)
766 project = Project.visible.find_by_identifier(project_identifier)
767 end
767 end
768 if esc.nil?
768 if esc.nil?
769 if prefix.nil? && sep == 'r'
769 if prefix.nil? && sep == 'r'
770 if project
770 if project
771 repository = nil
771 repository = nil
772 if repo_identifier
772 if repo_identifier
773 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
773 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
774 else
774 else
775 repository = project.repository
775 repository = project.repository
776 end
776 end
777 # project.changesets.visible raises an SQL error because of a double join on repositories
777 # project.changesets.visible raises an SQL error because of a double join on repositories
778 if repository &&
778 if repository &&
779 (changeset = Changeset.visible.
779 (changeset = Changeset.visible.
780 find_by_repository_id_and_revision(repository.id, identifier))
780 find_by_repository_id_and_revision(repository.id, identifier))
781 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
781 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"),
782 {:only_path => only_path, :controller => 'repositories',
782 {:only_path => only_path, :controller => 'repositories',
783 :action => 'revision', :id => project,
783 :action => 'revision', :id => project,
784 :repository_id => repository.identifier_param,
784 :repository_id => repository.identifier_param,
785 :rev => changeset.revision},
785 :rev => changeset.revision},
786 :class => 'changeset',
786 :class => 'changeset',
787 :title => truncate_single_line_raw(changeset.comments, 100))
787 :title => truncate_single_line_raw(changeset.comments, 100))
788 end
788 end
789 end
789 end
790 elsif sep == '#'
790 elsif sep == '#'
791 oid = identifier.to_i
791 oid = identifier.to_i
792 case prefix
792 case prefix
793 when nil
793 when nil
794 if oid.to_s == identifier &&
794 if oid.to_s == identifier &&
795 issue = Issue.visible.includes(:status).find_by_id(oid)
795 issue = Issue.visible.includes(:status).find_by_id(oid)
796 anchor = comment_id ? "note-#{comment_id}" : nil
796 anchor = comment_id ? "note-#{comment_id}" : nil
797 link = link_to(h("##{oid}#{comment_suffix}"),
797 link = link_to(h("##{oid}#{comment_suffix}"),
798 {:only_path => only_path, :controller => 'issues',
798 {:only_path => only_path, :controller => 'issues',
799 :action => 'show', :id => oid, :anchor => anchor},
799 :action => 'show', :id => oid, :anchor => anchor},
800 :class => issue.css_classes,
800 :class => issue.css_classes,
801 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
801 :title => "#{issue.subject.truncate(100)} (#{issue.status.name})")
802 end
802 end
803 when 'document'
803 when 'document'
804 if document = Document.visible.find_by_id(oid)
804 if document = Document.visible.find_by_id(oid)
805 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
805 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
806 :class => 'document'
806 :class => 'document'
807 end
807 end
808 when 'version'
808 when 'version'
809 if version = Version.visible.find_by_id(oid)
809 if version = Version.visible.find_by_id(oid)
810 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
810 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
811 :class => 'version'
811 :class => 'version'
812 end
812 end
813 when 'message'
813 when 'message'
814 if message = Message.visible.includes(:parent).find_by_id(oid)
814 if message = Message.visible.includes(:parent).find_by_id(oid)
815 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
815 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
816 end
816 end
817 when 'forum'
817 when 'forum'
818 if board = Board.visible.find_by_id(oid)
818 if board = Board.visible.find_by_id(oid)
819 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
819 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
820 :class => 'board'
820 :class => 'board'
821 end
821 end
822 when 'news'
822 when 'news'
823 if news = News.visible.find_by_id(oid)
823 if news = News.visible.find_by_id(oid)
824 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
824 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
825 :class => 'news'
825 :class => 'news'
826 end
826 end
827 when 'project'
827 when 'project'
828 if p = Project.visible.find_by_id(oid)
828 if p = Project.visible.find_by_id(oid)
829 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
829 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
830 end
830 end
831 end
831 end
832 elsif sep == ':'
832 elsif sep == ':'
833 # removes the double quotes if any
833 # removes the double quotes if any
834 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
834 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
835 name = CGI.unescapeHTML(name)
835 name = CGI.unescapeHTML(name)
836 case prefix
836 case prefix
837 when 'document'
837 when 'document'
838 if project && document = project.documents.visible.find_by_title(name)
838 if project && document = project.documents.visible.find_by_title(name)
839 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
839 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
840 :class => 'document'
840 :class => 'document'
841 end
841 end
842 when 'version'
842 when 'version'
843 if project && version = project.versions.visible.find_by_name(name)
843 if project && version = project.versions.visible.find_by_name(name)
844 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
844 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
845 :class => 'version'
845 :class => 'version'
846 end
846 end
847 when 'forum'
847 when 'forum'
848 if project && board = project.boards.visible.find_by_name(name)
848 if project && board = project.boards.visible.find_by_name(name)
849 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
849 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
850 :class => 'board'
850 :class => 'board'
851 end
851 end
852 when 'news'
852 when 'news'
853 if project && news = project.news.visible.find_by_title(name)
853 if project && news = project.news.visible.find_by_title(name)
854 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
854 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
855 :class => 'news'
855 :class => 'news'
856 end
856 end
857 when 'commit', 'source', 'export'
857 when 'commit', 'source', 'export'
858 if project
858 if project
859 repository = nil
859 repository = nil
860 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
860 if name =~ %r{^(([a-z0-9\-_]+)\|)(.+)$}
861 repo_prefix, repo_identifier, name = $1, $2, $3
861 repo_prefix, repo_identifier, name = $1, $2, $3
862 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
862 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
863 else
863 else
864 repository = project.repository
864 repository = project.repository
865 end
865 end
866 if prefix == 'commit'
866 if prefix == 'commit'
867 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
867 if repository && (changeset = Changeset.visible.where("repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%").first)
868 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},
868 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},
869 :class => 'changeset',
869 :class => 'changeset',
870 :title => truncate_single_line_raw(changeset.comments, 100)
870 :title => truncate_single_line_raw(changeset.comments, 100)
871 end
871 end
872 else
872 else
873 if repository && User.current.allowed_to?(:browse_repository, project)
873 if repository && User.current.allowed_to?(:browse_repository, project)
874 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
874 name =~ %r{^[/\\]*(.*?)(@([^/\\@]+?))?(#(L\d+))?$}
875 path, rev, anchor = $1, $3, $5
875 path, rev, anchor = $1, $3, $5
876 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,
876 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,
877 :path => to_path_param(path),
877 :path => to_path_param(path),
878 :rev => rev,
878 :rev => rev,
879 :anchor => anchor},
879 :anchor => anchor},
880 :class => (prefix == 'export' ? 'source download' : 'source')
880 :class => (prefix == 'export' ? 'source download' : 'source')
881 end
881 end
882 end
882 end
883 repo_prefix = nil
883 repo_prefix = nil
884 end
884 end
885 when 'attachment'
885 when 'attachment'
886 attachments = options[:attachments] || []
886 attachments = options[:attachments] || []
887 attachments += obj.attachments if obj.respond_to?(:attachments)
887 attachments += obj.attachments if obj.respond_to?(:attachments)
888 if attachments && attachment = Attachment.latest_attach(attachments, name)
888 if attachments && attachment = Attachment.latest_attach(attachments, name)
889 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
889 link = link_to_attachment(attachment, :only_path => only_path, :download => true, :class => 'attachment')
890 end
890 end
891 when 'project'
891 when 'project'
892 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
892 if p = Project.visible.where("identifier = :s OR LOWER(name) = :s", :s => name.downcase).first
893 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
893 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
894 end
894 end
895 end
895 end
896 end
896 end
897 end
897 end
898 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
898 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}#{comment_suffix}"))
899 end
899 end
900 end
900 end
901
901
902 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
902 HEADING_RE = /(<h(\d)( [^>]+)?>(.+?)<\/h(\d)>)/i unless const_defined?(:HEADING_RE)
903
903
904 def parse_sections(text, project, obj, attr, only_path, options)
904 def parse_sections(text, project, obj, attr, only_path, options)
905 return unless options[:edit_section_links]
905 return unless options[:edit_section_links]
906 text.gsub!(HEADING_RE) do
906 text.gsub!(HEADING_RE) do
907 heading = $1
907 heading = $1
908 @current_section += 1
908 @current_section += 1
909 if @current_section > 1
909 if @current_section > 1
910 content_tag('div',
910 content_tag('div',
911 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
911 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
912 :class => 'contextual',
912 :class => 'contextual',
913 :title => l(:button_edit_section),
913 :title => l(:button_edit_section),
914 :id => "section-#{@current_section}") + heading.html_safe
914 :id => "section-#{@current_section}") + heading.html_safe
915 else
915 else
916 heading
916 heading
917 end
917 end
918 end
918 end
919 end
919 end
920
920
921 # Headings and TOC
921 # Headings and TOC
922 # Adds ids and links to headings unless options[:headings] is set to false
922 # Adds ids and links to headings unless options[:headings] is set to false
923 def parse_headings(text, project, obj, attr, only_path, options)
923 def parse_headings(text, project, obj, attr, only_path, options)
924 return if options[:headings] == false
924 return if options[:headings] == false
925
925
926 text.gsub!(HEADING_RE) do
926 text.gsub!(HEADING_RE) do
927 level, attrs, content = $2.to_i, $3, $4
927 level, attrs, content = $2.to_i, $3, $4
928 item = strip_tags(content).strip
928 item = strip_tags(content).strip
929 anchor = sanitize_anchor_name(item)
929 anchor = sanitize_anchor_name(item)
930 # used for single-file wiki export
930 # used for single-file wiki export
931 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
931 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
932 @heading_anchors[anchor] ||= 0
932 @heading_anchors[anchor] ||= 0
933 idx = (@heading_anchors[anchor] += 1)
933 idx = (@heading_anchors[anchor] += 1)
934 if idx > 1
934 if idx > 1
935 anchor = "#{anchor}-#{idx}"
935 anchor = "#{anchor}-#{idx}"
936 end
936 end
937 @parsed_headings << [level, anchor, item]
937 @parsed_headings << [level, anchor, item]
938 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
938 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
939 end
939 end
940 end
940 end
941
941
942 MACROS_RE = /(
942 MACROS_RE = /(
943 (!)? # escaping
943 (!)? # escaping
944 (
944 (
945 \{\{ # opening tag
945 \{\{ # opening tag
946 ([\w]+) # macro name
946 ([\w]+) # macro name
947 (\(([^\n\r]*?)\))? # optional arguments
947 (\(([^\n\r]*?)\))? # optional arguments
948 ([\n\r].*?[\n\r])? # optional block of text
948 ([\n\r].*?[\n\r])? # optional block of text
949 \}\} # closing tag
949 \}\} # closing tag
950 )
950 )
951 )/mx unless const_defined?(:MACROS_RE)
951 )/mx unless const_defined?(:MACROS_RE)
952
952
953 MACRO_SUB_RE = /(
953 MACRO_SUB_RE = /(
954 \{\{
954 \{\{
955 macro\((\d+)\)
955 macro\((\d+)\)
956 \}\}
956 \}\}
957 )/x unless const_defined?(:MACRO_SUB_RE)
957 )/x unless const_defined?(:MACRO_SUB_RE)
958
958
959 # Extracts macros from text
959 # Extracts macros from text
960 def catch_macros(text)
960 def catch_macros(text)
961 macros = {}
961 macros = {}
962 text.gsub!(MACROS_RE) do
962 text.gsub!(MACROS_RE) do
963 all, macro = $1, $4.downcase
963 all, macro = $1, $4.downcase
964 if macro_exists?(macro) || all =~ MACRO_SUB_RE
964 if macro_exists?(macro) || all =~ MACRO_SUB_RE
965 index = macros.size
965 index = macros.size
966 macros[index] = all
966 macros[index] = all
967 "{{macro(#{index})}}"
967 "{{macro(#{index})}}"
968 else
968 else
969 all
969 all
970 end
970 end
971 end
971 end
972 macros
972 macros
973 end
973 end
974
974
975 # Executes and replaces macros in text
975 # Executes and replaces macros in text
976 def inject_macros(text, obj, macros, execute=true)
976 def inject_macros(text, obj, macros, execute=true)
977 text.gsub!(MACRO_SUB_RE) do
977 text.gsub!(MACRO_SUB_RE) do
978 all, index = $1, $2.to_i
978 all, index = $1, $2.to_i
979 orig = macros.delete(index)
979 orig = macros.delete(index)
980 if execute && orig && orig =~ MACROS_RE
980 if execute && orig && orig =~ MACROS_RE
981 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
981 esc, all, macro, args, block = $2, $3, $4.downcase, $6.to_s, $7.try(:strip)
982 if esc.nil?
982 if esc.nil?
983 h(exec_macro(macro, obj, args, block) || all)
983 h(exec_macro(macro, obj, args, block) || all)
984 else
984 else
985 h(all)
985 h(all)
986 end
986 end
987 elsif orig
987 elsif orig
988 h(orig)
988 h(orig)
989 else
989 else
990 h(all)
990 h(all)
991 end
991 end
992 end
992 end
993 end
993 end
994
994
995 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
995 TOC_RE = /<p>\{\{((<|&lt;)|(>|&gt;))?toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
996
996
997 # Renders the TOC with given headings
997 # Renders the TOC with given headings
998 def replace_toc(text, headings)
998 def replace_toc(text, headings)
999 text.gsub!(TOC_RE) do
999 text.gsub!(TOC_RE) do
1000 left_align, right_align = $2, $3
1000 left_align, right_align = $2, $3
1001 # Keep only the 4 first levels
1001 # Keep only the 4 first levels
1002 headings = headings.select{|level, anchor, item| level <= 4}
1002 headings = headings.select{|level, anchor, item| level <= 4}
1003 if headings.empty?
1003 if headings.empty?
1004 ''
1004 ''
1005 else
1005 else
1006 div_class = 'toc'
1006 div_class = 'toc'
1007 div_class << ' right' if right_align
1007 div_class << ' right' if right_align
1008 div_class << ' left' if left_align
1008 div_class << ' left' if left_align
1009 out = "<ul class=\"#{div_class}\"><li>"
1009 out = "<ul class=\"#{div_class}\"><li>"
1010 root = headings.map(&:first).min
1010 root = headings.map(&:first).min
1011 current = root
1011 current = root
1012 started = false
1012 started = false
1013 headings.each do |level, anchor, item|
1013 headings.each do |level, anchor, item|
1014 if level > current
1014 if level > current
1015 out << '<ul><li>' * (level - current)
1015 out << '<ul><li>' * (level - current)
1016 elsif level < current
1016 elsif level < current
1017 out << "</li></ul>\n" * (current - level) + "</li><li>"
1017 out << "</li></ul>\n" * (current - level) + "</li><li>"
1018 elsif started
1018 elsif started
1019 out << '</li><li>'
1019 out << '</li><li>'
1020 end
1020 end
1021 out << "<a href=\"##{anchor}\">#{item}</a>"
1021 out << "<a href=\"##{anchor}\">#{item}</a>"
1022 current = level
1022 current = level
1023 started = true
1023 started = true
1024 end
1024 end
1025 out << '</li></ul>' * (current - root)
1025 out << '</li></ul>' * (current - root)
1026 out << '</li></ul>'
1026 out << '</li></ul>'
1027 end
1027 end
1028 end
1028 end
1029 end
1029 end
1030
1030
1031 # Same as Rails' simple_format helper without using paragraphs
1031 # Same as Rails' simple_format helper without using paragraphs
1032 def simple_format_without_paragraph(text)
1032 def simple_format_without_paragraph(text)
1033 text.to_s.
1033 text.to_s.
1034 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1034 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
1035 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1035 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
1036 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1036 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
1037 html_safe
1037 html_safe
1038 end
1038 end
1039
1039
1040 def lang_options_for_select(blank=true)
1040 def lang_options_for_select(blank=true)
1041 (blank ? [["(auto)", ""]] : []) + languages_options
1041 (blank ? [["(auto)", ""]] : []) + languages_options
1042 end
1042 end
1043
1043
1044 def label_tag_for(name, option_tags = nil, options = {})
1044 def label_tag_for(name, option_tags = nil, options = {})
1045 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1045 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
1046 content_tag("label", label_text)
1046 content_tag("label", label_text)
1047 end
1047 end
1048
1048
1049 def labelled_form_for(*args, &proc)
1049 def labelled_form_for(*args, &proc)
1050 args << {} unless args.last.is_a?(Hash)
1050 args << {} unless args.last.is_a?(Hash)
1051 options = args.last
1051 options = args.last
1052 if args.first.is_a?(Symbol)
1052 if args.first.is_a?(Symbol)
1053 options.merge!(:as => args.shift)
1053 options.merge!(:as => args.shift)
1054 end
1054 end
1055 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1055 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1056 form_for(*args, &proc)
1056 form_for(*args, &proc)
1057 end
1057 end
1058
1058
1059 def labelled_fields_for(*args, &proc)
1059 def labelled_fields_for(*args, &proc)
1060 args << {} unless args.last.is_a?(Hash)
1060 args << {} unless args.last.is_a?(Hash)
1061 options = args.last
1061 options = args.last
1062 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1062 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
1063 fields_for(*args, &proc)
1063 fields_for(*args, &proc)
1064 end
1064 end
1065
1065
1066 def labelled_remote_form_for(*args, &proc)
1066 def labelled_remote_form_for(*args, &proc)
1067 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1067 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_remote_form_for is deprecated and will be removed in Redmine 2.2."
1068 args << {} unless args.last.is_a?(Hash)
1068 args << {} unless args.last.is_a?(Hash)
1069 options = args.last
1069 options = args.last
1070 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1070 options.merge!({:builder => Redmine::Views::LabelledFormBuilder, :remote => true})
1071 form_for(*args, &proc)
1071 form_for(*args, &proc)
1072 end
1072 end
1073
1073
1074 def error_messages_for(*objects)
1074 def error_messages_for(*objects)
1075 html = ""
1075 html = ""
1076 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1076 objects = objects.map {|o| o.is_a?(String) ? instance_variable_get("@#{o}") : o}.compact
1077 errors = objects.map {|o| o.errors.full_messages}.flatten
1077 errors = objects.map {|o| o.errors.full_messages}.flatten
1078 if errors.any?
1078 if errors.any?
1079 html << "<div id='errorExplanation'><ul>\n"
1079 html << "<div id='errorExplanation'><ul>\n"
1080 errors.each do |error|
1080 errors.each do |error|
1081 html << "<li>#{h error}</li>\n"
1081 html << "<li>#{h error}</li>\n"
1082 end
1082 end
1083 html << "</ul></div>\n"
1083 html << "</ul></div>\n"
1084 end
1084 end
1085 html.html_safe
1085 html.html_safe
1086 end
1086 end
1087
1087
1088 def delete_link(url, options={})
1088 def delete_link(url, options={})
1089 options = {
1089 options = {
1090 :method => :delete,
1090 :method => :delete,
1091 :data => {:confirm => l(:text_are_you_sure)},
1091 :data => {:confirm => l(:text_are_you_sure)},
1092 :class => 'icon icon-del'
1092 :class => 'icon icon-del'
1093 }.merge(options)
1093 }.merge(options)
1094
1094
1095 link_to l(:button_delete), url, options
1095 link_to l(:button_delete), url, options
1096 end
1096 end
1097
1097
1098 def preview_link(url, form, target='preview', options={})
1098 def preview_link(url, form, target='preview', options={})
1099 content_tag 'a', l(:label_preview), {
1099 content_tag 'a', l(:label_preview), {
1100 :href => "#",
1100 :href => "#",
1101 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1101 :onclick => %|submitPreview("#{escape_javascript url_for(url)}", "#{escape_javascript form}", "#{escape_javascript target}"); return false;|,
1102 :accesskey => accesskey(:preview)
1102 :accesskey => accesskey(:preview)
1103 }.merge(options)
1103 }.merge(options)
1104 end
1104 end
1105
1105
1106 def link_to_function(name, function, html_options={})
1106 def link_to_function(name, function, html_options={})
1107 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1107 content_tag(:a, name, {:href => '#', :onclick => "#{function}; return false;"}.merge(html_options))
1108 end
1108 end
1109
1109
1110 # Helper to render JSON in views
1110 # Helper to render JSON in views
1111 def raw_json(arg)
1111 def raw_json(arg)
1112 arg.to_json.to_s.gsub('/', '\/').html_safe
1112 arg.to_json.to_s.gsub('/', '\/').html_safe
1113 end
1113 end
1114
1114
1115 def back_url
1115 def back_url
1116 url = params[:back_url]
1116 url = params[:back_url]
1117 if url.nil? && referer = request.env['HTTP_REFERER']
1117 if url.nil? && referer = request.env['HTTP_REFERER']
1118 url = CGI.unescape(referer.to_s)
1118 url = CGI.unescape(referer.to_s)
1119 end
1119 end
1120 url
1120 url
1121 end
1121 end
1122
1122
1123 def back_url_hidden_field_tag
1123 def back_url_hidden_field_tag
1124 url = back_url
1124 url = back_url
1125 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1125 hidden_field_tag('back_url', url, :id => nil) unless url.blank?
1126 end
1126 end
1127
1127
1128 def check_all_links(form_name)
1128 def check_all_links(form_name)
1129 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1129 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
1130 " | ".html_safe +
1130 " | ".html_safe +
1131 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1131 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
1132 end
1132 end
1133
1133
1134 def progress_bar(pcts, options={})
1134 def progress_bar(pcts, options={})
1135 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1135 pcts = [pcts, pcts] unless pcts.is_a?(Array)
1136 pcts = pcts.collect(&:round)
1136 pcts = pcts.collect(&:round)
1137 pcts[1] = pcts[1] - pcts[0]
1137 pcts[1] = pcts[1] - pcts[0]
1138 pcts << (100 - pcts[1] - pcts[0])
1138 pcts << (100 - pcts[1] - pcts[0])
1139 width = options[:width] || '100px;'
1139 width = options[:width] || '100px;'
1140 legend = options[:legend] || ''
1140 legend = options[:legend] || ''
1141 content_tag('table',
1141 content_tag('table',
1142 content_tag('tr',
1142 content_tag('tr',
1143 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1143 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
1144 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1144 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
1145 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1145 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
1146 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1146 ), :class => "progress progress-#{pcts[0]}", :style => "width: #{width};").html_safe +
1147 content_tag('p', legend, :class => 'percent').html_safe
1147 content_tag('p', legend, :class => 'percent').html_safe
1148 end
1148 end
1149
1149
1150 def checked_image(checked=true)
1150 def checked_image(checked=true)
1151 if checked
1151 if checked
1152 image_tag 'toggle_check.png'
1152 image_tag 'toggle_check.png'
1153 end
1153 end
1154 end
1154 end
1155
1155
1156 def context_menu(url)
1156 def context_menu(url)
1157 unless @context_menu_included
1157 unless @context_menu_included
1158 content_for :header_tags do
1158 content_for :header_tags do
1159 javascript_include_tag('context_menu') +
1159 javascript_include_tag('context_menu') +
1160 stylesheet_link_tag('context_menu')
1160 stylesheet_link_tag('context_menu')
1161 end
1161 end
1162 if l(:direction) == 'rtl'
1162 if l(:direction) == 'rtl'
1163 content_for :header_tags do
1163 content_for :header_tags do
1164 stylesheet_link_tag('context_menu_rtl')
1164 stylesheet_link_tag('context_menu_rtl')
1165 end
1165 end
1166 end
1166 end
1167 @context_menu_included = true
1167 @context_menu_included = true
1168 end
1168 end
1169 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1169 javascript_tag "contextMenuInit('#{ url_for(url) }')"
1170 end
1170 end
1171
1171
1172 def calendar_for(field_id)
1172 def calendar_for(field_id)
1173 include_calendar_headers_tags
1173 include_calendar_headers_tags
1174 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1174 javascript_tag("$(function() { $('##{field_id}').datepicker(datepickerOptions); });")
1175 end
1175 end
1176
1176
1177 def include_calendar_headers_tags
1177 def include_calendar_headers_tags
1178 unless @calendar_headers_tags_included
1178 unless @calendar_headers_tags_included
1179 tags = javascript_include_tag("datepicker")
1179 tags = javascript_include_tag("datepicker")
1180 @calendar_headers_tags_included = true
1180 @calendar_headers_tags_included = true
1181 content_for :header_tags do
1181 content_for :header_tags do
1182 start_of_week = Setting.start_of_week
1182 start_of_week = Setting.start_of_week
1183 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1183 start_of_week = l(:general_first_day_of_week, :default => '1') if start_of_week.blank?
1184 # Redmine uses 1..7 (monday..sunday) in settings and locales
1184 # Redmine uses 1..7 (monday..sunday) in settings and locales
1185 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1185 # JQuery uses 0..6 (sunday..saturday), 7 needs to be changed to 0
1186 start_of_week = start_of_week.to_i % 7
1186 start_of_week = start_of_week.to_i % 7
1187 tags << javascript_tag(
1187 tags << javascript_tag(
1188 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1188 "var datepickerOptions={dateFormat: 'yy-mm-dd', firstDay: #{start_of_week}, " +
1189 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1189 "showOn: 'button', buttonImageOnly: true, buttonImage: '" +
1190 path_to_image('/images/calendar.png') +
1190 path_to_image('/images/calendar.png') +
1191 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1191 "', showButtonPanel: true, showWeek: true, showOtherMonths: true, " +
1192 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1192 "selectOtherMonths: true, changeMonth: true, changeYear: true, " +
1193 "beforeShow: beforeShowDatePicker};")
1193 "beforeShow: beforeShowDatePicker};")
1194 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1194 jquery_locale = l('jquery.locale', :default => current_language.to_s)
1195 unless jquery_locale == 'en'
1195 unless jquery_locale == 'en'
1196 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1196 tags << javascript_include_tag("i18n/datepicker-#{jquery_locale}.js")
1197 end
1197 end
1198 tags
1198 tags
1199 end
1199 end
1200 end
1200 end
1201 end
1201 end
1202
1202
1203 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1203 # Overrides Rails' stylesheet_link_tag with themes and plugins support.
1204 # Examples:
1204 # Examples:
1205 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1205 # stylesheet_link_tag('styles') # => picks styles.css from the current theme or defaults
1206 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1206 # stylesheet_link_tag('styles', :plugin => 'foo) # => picks styles.css from plugin's assets
1207 #
1207 #
1208 def stylesheet_link_tag(*sources)
1208 def stylesheet_link_tag(*sources)
1209 options = sources.last.is_a?(Hash) ? sources.pop : {}
1209 options = sources.last.is_a?(Hash) ? sources.pop : {}
1210 plugin = options.delete(:plugin)
1210 plugin = options.delete(:plugin)
1211 sources = sources.map do |source|
1211 sources = sources.map do |source|
1212 if plugin
1212 if plugin
1213 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1213 "/plugin_assets/#{plugin}/stylesheets/#{source}"
1214 elsif current_theme && current_theme.stylesheets.include?(source)
1214 elsif current_theme && current_theme.stylesheets.include?(source)
1215 current_theme.stylesheet_path(source)
1215 current_theme.stylesheet_path(source)
1216 else
1216 else
1217 source
1217 source
1218 end
1218 end
1219 end
1219 end
1220 super sources, options
1220 super sources, options
1221 end
1221 end
1222
1222
1223 # Overrides Rails' image_tag with themes and plugins support.
1223 # Overrides Rails' image_tag with themes and plugins support.
1224 # Examples:
1224 # Examples:
1225 # image_tag('image.png') # => picks image.png from the current theme or defaults
1225 # image_tag('image.png') # => picks image.png from the current theme or defaults
1226 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1226 # image_tag('image.png', :plugin => 'foo) # => picks image.png from plugin's assets
1227 #
1227 #
1228 def image_tag(source, options={})
1228 def image_tag(source, options={})
1229 if plugin = options.delete(:plugin)
1229 if plugin = options.delete(:plugin)
1230 source = "/plugin_assets/#{plugin}/images/#{source}"
1230 source = "/plugin_assets/#{plugin}/images/#{source}"
1231 elsif current_theme && current_theme.images.include?(source)
1231 elsif current_theme && current_theme.images.include?(source)
1232 source = current_theme.image_path(source)
1232 source = current_theme.image_path(source)
1233 end
1233 end
1234 super source, options
1234 super source, options
1235 end
1235 end
1236
1236
1237 # Overrides Rails' javascript_include_tag with plugins support
1237 # Overrides Rails' javascript_include_tag with plugins support
1238 # Examples:
1238 # Examples:
1239 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1239 # javascript_include_tag('scripts') # => picks scripts.js from defaults
1240 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1240 # javascript_include_tag('scripts', :plugin => 'foo) # => picks scripts.js from plugin's assets
1241 #
1241 #
1242 def javascript_include_tag(*sources)
1242 def javascript_include_tag(*sources)
1243 options = sources.last.is_a?(Hash) ? sources.pop : {}
1243 options = sources.last.is_a?(Hash) ? sources.pop : {}
1244 if plugin = options.delete(:plugin)
1244 if plugin = options.delete(:plugin)
1245 sources = sources.map do |source|
1245 sources = sources.map do |source|
1246 if plugin
1246 if plugin
1247 "/plugin_assets/#{plugin}/javascripts/#{source}"
1247 "/plugin_assets/#{plugin}/javascripts/#{source}"
1248 else
1248 else
1249 source
1249 source
1250 end
1250 end
1251 end
1251 end
1252 end
1252 end
1253 super sources, options
1253 super sources, options
1254 end
1254 end
1255
1255
1256 # TODO: remove this in 2.5.0
1256 # TODO: remove this in 2.5.0
1257 def has_content?(name)
1257 def has_content?(name)
1258 content_for?(name)
1258 content_for?(name)
1259 end
1259 end
1260
1260
1261 def sidebar_content?
1261 def sidebar_content?
1262 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1262 content_for?(:sidebar) || view_layouts_base_sidebar_hook_response.present?
1263 end
1263 end
1264
1264
1265 def view_layouts_base_sidebar_hook_response
1265 def view_layouts_base_sidebar_hook_response
1266 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1266 @view_layouts_base_sidebar_hook_response ||= call_hook(:view_layouts_base_sidebar)
1267 end
1267 end
1268
1268
1269 def email_delivery_enabled?
1269 def email_delivery_enabled?
1270 !!ActionMailer::Base.perform_deliveries
1270 !!ActionMailer::Base.perform_deliveries
1271 end
1271 end
1272
1272
1273 # Returns the avatar image tag for the given +user+ if avatars are enabled
1273 # Returns the avatar image tag for the given +user+ if avatars are enabled
1274 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1274 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1275 def avatar(user, options = { })
1275 def avatar(user, options = { })
1276 if Setting.gravatar_enabled?
1276 if Setting.gravatar_enabled?
1277 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1277 options.merge!({:ssl => (request && request.ssl?), :default => Setting.gravatar_default})
1278 email = nil
1278 email = nil
1279 if user.respond_to?(:mail)
1279 if user.respond_to?(:mail)
1280 email = user.mail
1280 email = user.mail
1281 elsif user.to_s =~ %r{<(.+?)>}
1281 elsif user.to_s =~ %r{<(.+?)>}
1282 email = $1
1282 email = $1
1283 end
1283 end
1284 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1284 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1285 else
1285 else
1286 ''
1286 ''
1287 end
1287 end
1288 end
1288 end
1289
1289
1290 def sanitize_anchor_name(anchor)
1290 def sanitize_anchor_name(anchor)
1291 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1291 if ''.respond_to?(:encoding) || RUBY_PLATFORM == 'java'
1292 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1292 anchor.gsub(%r{[^\s\-\p{Word}]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1293 else
1293 else
1294 # TODO: remove when ruby1.8 is no longer supported
1294 # TODO: remove when ruby1.8 is no longer supported
1295 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1295 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1296 end
1296 end
1297 end
1297 end
1298
1298
1299 # Returns the javascript tags that are included in the html layout head
1299 # Returns the javascript tags that are included in the html layout head
1300 def javascript_heads
1300 def javascript_heads
1301 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.1', 'application')
1301 tags = javascript_include_tag('jquery-1.11.1-ui-1.11.0-ujs-3.1.1', 'application')
1302 unless User.current.pref.warn_on_leaving_unsaved == '0'
1302 unless User.current.pref.warn_on_leaving_unsaved == '0'
1303 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1303 tags << "\n".html_safe + javascript_tag("$(window).load(function(){ warnLeavingUnsaved('#{escape_javascript l(:text_warn_on_leaving_unsaved)}'); });")
1304 end
1304 end
1305 tags
1305 tags
1306 end
1306 end
1307
1307
1308 def favicon
1308 def favicon
1309 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1309 "<link rel='shortcut icon' href='#{favicon_path}' />".html_safe
1310 end
1310 end
1311
1311
1312 # Returns the path to the favicon
1312 # Returns the path to the favicon
1313 def favicon_path
1313 def favicon_path
1314 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1314 icon = (current_theme && current_theme.favicon?) ? current_theme.favicon_path : '/favicon.ico'
1315 image_path(icon)
1315 image_path(icon)
1316 end
1316 end
1317
1317
1318 # Returns the full URL to the favicon
1318 # Returns the full URL to the favicon
1319 def favicon_url
1319 def favicon_url
1320 # TODO: use #image_url introduced in Rails4
1320 # TODO: use #image_url introduced in Rails4
1321 path = favicon_path
1321 path = favicon_path
1322 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1322 base = url_for(:controller => 'welcome', :action => 'index', :only_path => false)
1323 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1323 base.sub(%r{/+$},'') + '/' + path.sub(%r{^/+},'')
1324 end
1324 end
1325
1325
1326 def robot_exclusion_tag
1326 def robot_exclusion_tag
1327 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1327 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1328 end
1328 end
1329
1329
1330 # Returns true if arg is expected in the API response
1330 # Returns true if arg is expected in the API response
1331 def include_in_api_response?(arg)
1331 def include_in_api_response?(arg)
1332 unless @included_in_api_response
1332 unless @included_in_api_response
1333 param = params[:include]
1333 param = params[:include]
1334 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1334 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1335 @included_in_api_response.collect!(&:strip)
1335 @included_in_api_response.collect!(&:strip)
1336 end
1336 end
1337 @included_in_api_response.include?(arg.to_s)
1337 @included_in_api_response.include?(arg.to_s)
1338 end
1338 end
1339
1339
1340 # Returns options or nil if nometa param or X-Redmine-Nometa header
1340 # Returns options or nil if nometa param or X-Redmine-Nometa header
1341 # was set in the request
1341 # was set in the request
1342 def api_meta(options)
1342 def api_meta(options)
1343 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1343 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1344 # compatibility mode for activeresource clients that raise
1344 # compatibility mode for activeresource clients that raise
1345 # an error when deserializing an array with attributes
1345 # an error when deserializing an array with attributes
1346 nil
1346 nil
1347 else
1347 else
1348 options
1348 options
1349 end
1349 end
1350 end
1350 end
1351
1351
1352 private
1352 private
1353
1353
1354 def wiki_helper
1354 def wiki_helper
1355 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1355 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1356 extend helper
1356 extend helper
1357 return self
1357 return self
1358 end
1358 end
1359
1359
1360 def link_to_content_update(text, url_params = {}, html_options = {})
1360 def link_to_content_update(text, url_params = {}, html_options = {})
1361 link_to(text, url_params, html_options)
1361 link_to(text, url_params, html_options)
1362 end
1362 end
1363 end
1363 end
@@ -1,4066 +1,4066
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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 :issue_relations,
28 :issue_relations,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries,
44 :queries,
45 :repositories,
45 :repositories,
46 :changesets
46 :changesets
47
47
48 include Redmine::I18n
48 include Redmine::I18n
49
49
50 def setup
50 def setup
51 User.current = nil
51 User.current = nil
52 end
52 end
53
53
54 def test_index
54 def test_index
55 with_settings :default_language => "en" do
55 with_settings :default_language => "en" do
56 get :index
56 get :index
57 assert_response :success
57 assert_response :success
58 assert_template 'index'
58 assert_template 'index'
59 assert_not_nil assigns(:issues)
59 assert_not_nil assigns(:issues)
60 assert_nil assigns(:project)
60 assert_nil assigns(:project)
61
61
62 # links to visible issues
62 # links to visible issues
63 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
63 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
64 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
64 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
65 # private projects hidden
65 # private projects hidden
66 assert_select 'a[href=/issues/6]', 0
66 assert_select 'a[href=/issues/6]', 0
67 assert_select 'a[href=/issues/4]', 0
67 assert_select 'a[href=/issues/4]', 0
68 # project column
68 # project column
69 assert_select 'th', :text => /Project/
69 assert_select 'th', :text => /Project/
70 end
70 end
71 end
71 end
72
72
73 def test_index_should_not_list_issues_when_module_disabled
73 def test_index_should_not_list_issues_when_module_disabled
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 get :index
75 get :index
76 assert_response :success
76 assert_response :success
77 assert_template 'index'
77 assert_template 'index'
78 assert_not_nil assigns(:issues)
78 assert_not_nil assigns(:issues)
79 assert_nil assigns(:project)
79 assert_nil assigns(:project)
80
80
81 assert_select 'a[href=/issues/1]', 0
81 assert_select 'a[href=/issues/1]', 0
82 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
82 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
83 end
83 end
84
84
85 def test_index_should_list_visible_issues_only
85 def test_index_should_list_visible_issues_only
86 get :index, :per_page => 100
86 get :index, :per_page => 100
87 assert_response :success
87 assert_response :success
88 assert_not_nil assigns(:issues)
88 assert_not_nil assigns(:issues)
89 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
89 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
90 end
90 end
91
91
92 def test_index_with_project
92 def test_index_with_project
93 Setting.display_subprojects_issues = 0
93 Setting.display_subprojects_issues = 0
94 get :index, :project_id => 1
94 get :index, :project_id => 1
95 assert_response :success
95 assert_response :success
96 assert_template 'index'
96 assert_template 'index'
97 assert_not_nil assigns(:issues)
97 assert_not_nil assigns(:issues)
98
98
99 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
99 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
100 assert_select 'a[href=/issues/5]', 0
100 assert_select 'a[href=/issues/5]', 0
101 end
101 end
102
102
103 def test_index_with_project_and_subprojects
103 def test_index_with_project_and_subprojects
104 Setting.display_subprojects_issues = 1
104 Setting.display_subprojects_issues = 1
105 get :index, :project_id => 1
105 get :index, :project_id => 1
106 assert_response :success
106 assert_response :success
107 assert_template 'index'
107 assert_template 'index'
108 assert_not_nil assigns(:issues)
108 assert_not_nil assigns(:issues)
109
109
110 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
110 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
111 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
111 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
112 assert_select 'a[href=/issues/6]', 0
112 assert_select 'a[href=/issues/6]', 0
113 end
113 end
114
114
115 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
115 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
116 @request.session[:user_id] = 2
116 @request.session[:user_id] = 2
117 Setting.display_subprojects_issues = 1
117 Setting.display_subprojects_issues = 1
118 get :index, :project_id => 1
118 get :index, :project_id => 1
119 assert_response :success
119 assert_response :success
120 assert_template 'index'
120 assert_template 'index'
121 assert_not_nil assigns(:issues)
121 assert_not_nil assigns(:issues)
122
122
123 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
123 assert_select 'a[href=/issues/1]', :text => /#{ESCAPED_UCANT} print recipes/
124 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
124 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
125 assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/
125 assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/
126 end
126 end
127
127
128 def test_index_with_project_and_default_filter
128 def test_index_with_project_and_default_filter
129 get :index, :project_id => 1, :set_filter => 1
129 get :index, :project_id => 1, :set_filter => 1
130 assert_response :success
130 assert_response :success
131 assert_template 'index'
131 assert_template 'index'
132 assert_not_nil assigns(:issues)
132 assert_not_nil assigns(:issues)
133
133
134 query = assigns(:query)
134 query = assigns(:query)
135 assert_not_nil query
135 assert_not_nil query
136 # default filter
136 # default filter
137 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
137 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
138 end
138 end
139
139
140 def test_index_with_project_and_filter
140 def test_index_with_project_and_filter
141 get :index, :project_id => 1, :set_filter => 1,
141 get :index, :project_id => 1, :set_filter => 1,
142 :f => ['tracker_id'],
142 :f => ['tracker_id'],
143 :op => {'tracker_id' => '='},
143 :op => {'tracker_id' => '='},
144 :v => {'tracker_id' => ['1']}
144 :v => {'tracker_id' => ['1']}
145 assert_response :success
145 assert_response :success
146 assert_template 'index'
146 assert_template 'index'
147 assert_not_nil assigns(:issues)
147 assert_not_nil assigns(:issues)
148
148
149 query = assigns(:query)
149 query = assigns(:query)
150 assert_not_nil query
150 assert_not_nil query
151 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
151 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
152 end
152 end
153
153
154 def test_index_with_short_filters
154 def test_index_with_short_filters
155 to_test = {
155 to_test = {
156 'status_id' => {
156 'status_id' => {
157 'o' => { :op => 'o', :values => [''] },
157 'o' => { :op => 'o', :values => [''] },
158 'c' => { :op => 'c', :values => [''] },
158 'c' => { :op => 'c', :values => [''] },
159 '7' => { :op => '=', :values => ['7'] },
159 '7' => { :op => '=', :values => ['7'] },
160 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
160 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
161 '=7' => { :op => '=', :values => ['7'] },
161 '=7' => { :op => '=', :values => ['7'] },
162 '!3' => { :op => '!', :values => ['3'] },
162 '!3' => { :op => '!', :values => ['3'] },
163 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
163 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
164 'subject' => {
164 'subject' => {
165 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
165 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
166 'o' => { :op => '=', :values => ['o'] },
166 'o' => { :op => '=', :values => ['o'] },
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 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
168 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
169 'tracker_id' => {
169 'tracker_id' => {
170 '3' => { :op => '=', :values => ['3'] },
170 '3' => { :op => '=', :values => ['3'] },
171 '=3' => { :op => '=', :values => ['3'] }},
171 '=3' => { :op => '=', :values => ['3'] }},
172 'start_date' => {
172 'start_date' => {
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-12' => { :op => '<=', :values => ['2011-10-12'] },
176 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
177 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
177 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
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+2' => { :op => 't+', :values => ['2'] },
180 't+2' => { :op => 't+', :values => ['2'] },
181 't' => { :op => 't', :values => [''] },
181 't' => { :op => 't', :values => [''] },
182 'w' => { :op => 'w', :values => [''] },
182 'w' => { :op => 'w', :values => [''] },
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 't-2' => { :op => 't-', :values => ['2'] }},
185 't-2' => { :op => 't-', :values => ['2'] }},
186 'created_on' => {
186 'created_on' => {
187 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
187 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
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 't-2' => { :op => 't-', :values => ['2'] }},
190 't-2' => { :op => 't-', :values => ['2'] }},
191 'cf_1' => {
191 'cf_1' => {
192 'c' => { :op => '=', :values => ['c'] },
192 'c' => { :op => '=', :values => ['c'] },
193 '!c' => { :op => '!', :values => ['c'] },
193 '!c' => { :op => '!', :values => ['c'] },
194 '!*' => { :op => '!*', :values => [''] },
194 '!*' => { :op => '!*', :values => [''] },
195 '*' => { :op => '*', :values => [''] }},
195 '*' => { :op => '*', :values => [''] }},
196 'estimated_hours' => {
196 'estimated_hours' => {
197 '=13.4' => { :op => '=', :values => ['13.4'] },
197 '=13.4' => { :op => '=', :values => ['13.4'] },
198 '>=45' => { :op => '>=', :values => ['45'] },
198 '>=45' => { :op => '>=', :values => ['45'] },
199 '<=125' => { :op => '<=', :values => ['125'] },
199 '<=125' => { :op => '<=', :values => ['125'] },
200 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
200 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
201 '!*' => { :op => '!*', :values => [''] },
201 '!*' => { :op => '!*', :values => [''] },
202 '*' => { :op => '*', :values => [''] }}
202 '*' => { :op => '*', :values => [''] }}
203 }
203 }
204
204
205 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
205 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
206
206
207 to_test.each do |field, expression_and_expected|
207 to_test.each do |field, expression_and_expected|
208 expression_and_expected.each do |filter_expression, expected|
208 expression_and_expected.each do |filter_expression, expected|
209
209
210 get :index, :set_filter => 1, field => filter_expression
210 get :index, :set_filter => 1, field => filter_expression
211
211
212 assert_response :success
212 assert_response :success
213 assert_template 'index'
213 assert_template 'index'
214 assert_not_nil assigns(:issues)
214 assert_not_nil assigns(:issues)
215
215
216 query = assigns(:query)
216 query = assigns(:query)
217 assert_not_nil query
217 assert_not_nil query
218 assert query.has_filter?(field)
218 assert query.has_filter?(field)
219 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
219 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
220 end
220 end
221 end
221 end
222 end
222 end
223
223
224 def test_index_with_project_and_empty_filters
224 def test_index_with_project_and_empty_filters
225 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
225 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
226 assert_response :success
226 assert_response :success
227 assert_template 'index'
227 assert_template 'index'
228 assert_not_nil assigns(:issues)
228 assert_not_nil assigns(:issues)
229
229
230 query = assigns(:query)
230 query = assigns(:query)
231 assert_not_nil query
231 assert_not_nil query
232 # no filter
232 # no filter
233 assert_equal({}, query.filters)
233 assert_equal({}, query.filters)
234 end
234 end
235
235
236 def test_index_with_project_custom_field_filter
236 def test_index_with_project_custom_field_filter
237 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
237 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
238 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
238 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
239 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
239 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
240 filter_name = "project.cf_#{field.id}"
240 filter_name = "project.cf_#{field.id}"
241 @request.session[:user_id] = 1
241 @request.session[:user_id] = 1
242
242
243 get :index, :set_filter => 1,
243 get :index, :set_filter => 1,
244 :f => [filter_name],
244 :f => [filter_name],
245 :op => {filter_name => '='},
245 :op => {filter_name => '='},
246 :v => {filter_name => ['Foo']}
246 :v => {filter_name => ['Foo']}
247 assert_response :success
247 assert_response :success
248 assert_template 'index'
248 assert_template 'index'
249 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
249 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
250 end
250 end
251
251
252 def test_index_with_query
252 def test_index_with_query
253 get :index, :project_id => 1, :query_id => 5
253 get :index, :project_id => 1, :query_id => 5
254 assert_response :success
254 assert_response :success
255 assert_template 'index'
255 assert_template 'index'
256 assert_not_nil assigns(:issues)
256 assert_not_nil assigns(:issues)
257 assert_nil assigns(:issue_count_by_group)
257 assert_nil assigns(:issue_count_by_group)
258 end
258 end
259
259
260 def test_index_with_query_grouped_by_tracker
260 def test_index_with_query_grouped_by_tracker
261 get :index, :project_id => 1, :query_id => 6
261 get :index, :project_id => 1, :query_id => 6
262 assert_response :success
262 assert_response :success
263 assert_template 'index'
263 assert_template 'index'
264 assert_not_nil assigns(:issues)
264 assert_not_nil assigns(:issues)
265 assert_not_nil assigns(:issue_count_by_group)
265 assert_not_nil assigns(:issue_count_by_group)
266 end
266 end
267
267
268 def test_index_with_query_grouped_by_list_custom_field
268 def test_index_with_query_grouped_by_list_custom_field
269 get :index, :project_id => 1, :query_id => 9
269 get :index, :project_id => 1, :query_id => 9
270 assert_response :success
270 assert_response :success
271 assert_template 'index'
271 assert_template 'index'
272 assert_not_nil assigns(:issues)
272 assert_not_nil assigns(:issues)
273 assert_not_nil assigns(:issue_count_by_group)
273 assert_not_nil assigns(:issue_count_by_group)
274 end
274 end
275
275
276 def test_index_with_query_grouped_by_user_custom_field
276 def test_index_with_query_grouped_by_user_custom_field
277 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
277 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
278 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
278 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
279 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
279 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
280 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
280 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
281 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
281 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
282
282
283 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
283 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
284 assert_response :success
284 assert_response :success
285
285
286 assert_select 'tr.group', 3
286 assert_select 'tr.group', 3
287 assert_select 'tr.group' do
287 assert_select 'tr.group' do
288 assert_select 'a', :text => 'John Smith'
288 assert_select 'a', :text => 'John Smith'
289 assert_select 'span.count', :text => '1'
289 assert_select 'span.count', :text => '1'
290 end
290 end
291 assert_select 'tr.group' do
291 assert_select 'tr.group' do
292 assert_select 'a', :text => 'Dave Lopper'
292 assert_select 'a', :text => 'Dave Lopper'
293 assert_select 'span.count', :text => '2'
293 assert_select 'span.count', :text => '2'
294 end
294 end
295 end
295 end
296
296
297 def test_index_grouped_by_boolean_custom_field_should_distinguish_blank_and_false_values
297 def test_index_grouped_by_boolean_custom_field_should_distinguish_blank_and_false_values
298 cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool')
298 cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool')
299 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '1')
299 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '1')
300 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')
300 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')
301 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '')
301 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '')
302
302
303 with_settings :default_language => 'en' do
303 with_settings :default_language => 'en' do
304 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
304 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
305 assert_response :success
305 assert_response :success
306 end
306 end
307
307
308 assert_select 'tr.group', 3
308 assert_select 'tr.group', 3
309 assert_select 'tr.group', :text => /Yes/
309 assert_select 'tr.group', :text => /Yes/
310 assert_select 'tr.group', :text => /No/
310 assert_select 'tr.group', :text => /No/
311 assert_select 'tr.group', :text => /none/
311 assert_select 'tr.group', :text => /none/
312 end
312 end
313
313
314 def test_index_grouped_by_boolean_custom_field_with_false_group_in_first_position_should_show_the_group
314 def test_index_grouped_by_boolean_custom_field_with_false_group_in_first_position_should_show_the_group
315 cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool', :is_filter => true)
315 cf = IssueCustomField.create!(:name => 'Bool', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'bool', :is_filter => true)
316 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '0')
316 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '0')
317 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')
317 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '0')
318
318
319 with_settings :default_language => 'en' do
319 with_settings :default_language => 'en' do
320 get :index, :project_id => 1, :set_filter => 1, "cf_#{cf.id}" => "*", :group_by => "cf_#{cf.id}"
320 get :index, :project_id => 1, :set_filter => 1, "cf_#{cf.id}" => "*", :group_by => "cf_#{cf.id}"
321 assert_response :success
321 assert_response :success
322 assert_equal [1, 2], assigns(:issues).map(&:id).sort
322 assert_equal [1, 2], assigns(:issues).map(&:id).sort
323 end
323 end
324
324
325 assert_select 'tr.group', 1
325 assert_select 'tr.group', 1
326 assert_select 'tr.group', :text => /No/
326 assert_select 'tr.group', :text => /No/
327 end
327 end
328
328
329 def test_index_with_query_grouped_by_tracker_in_normal_order
329 def test_index_with_query_grouped_by_tracker_in_normal_order
330 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
330 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
331
331
332 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
332 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
333 assert_response :success
333 assert_response :success
334
334
335 trackers = assigns(:issues).map(&:tracker).uniq
335 trackers = assigns(:issues).map(&:tracker).uniq
336 assert_equal [1, 2, 3], trackers.map(&:id)
336 assert_equal [1, 2, 3], trackers.map(&:id)
337 end
337 end
338
338
339 def test_index_with_query_grouped_by_tracker_in_reverse_order
339 def test_index_with_query_grouped_by_tracker_in_reverse_order
340 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
340 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
341
341
342 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
342 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
343 assert_response :success
343 assert_response :success
344
344
345 trackers = assigns(:issues).map(&:tracker).uniq
345 trackers = assigns(:issues).map(&:tracker).uniq
346 assert_equal [3, 2, 1], trackers.map(&:id)
346 assert_equal [3, 2, 1], trackers.map(&:id)
347 end
347 end
348
348
349 def test_index_with_query_id_and_project_id_should_set_session_query
349 def test_index_with_query_id_and_project_id_should_set_session_query
350 get :index, :project_id => 1, :query_id => 4
350 get :index, :project_id => 1, :query_id => 4
351 assert_response :success
351 assert_response :success
352 assert_kind_of Hash, session[:query]
352 assert_kind_of Hash, session[:query]
353 assert_equal 4, session[:query][:id]
353 assert_equal 4, session[:query][:id]
354 assert_equal 1, session[:query][:project_id]
354 assert_equal 1, session[:query][:project_id]
355 end
355 end
356
356
357 def test_index_with_invalid_query_id_should_respond_404
357 def test_index_with_invalid_query_id_should_respond_404
358 get :index, :project_id => 1, :query_id => 999
358 get :index, :project_id => 1, :query_id => 999
359 assert_response 404
359 assert_response 404
360 end
360 end
361
361
362 def test_index_with_cross_project_query_in_session_should_show_project_issues
362 def test_index_with_cross_project_query_in_session_should_show_project_issues
363 q = IssueQuery.create!(:name => "test", :user_id => 2, :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
363 q = IssueQuery.create!(:name => "test", :user_id => 2, :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
364 @request.session[:query] = {:id => q.id, :project_id => 1}
364 @request.session[:query] = {:id => q.id, :project_id => 1}
365
365
366 with_settings :display_subprojects_issues => '0' do
366 with_settings :display_subprojects_issues => '0' do
367 get :index, :project_id => 1
367 get :index, :project_id => 1
368 end
368 end
369 assert_response :success
369 assert_response :success
370 assert_not_nil assigns(:query)
370 assert_not_nil assigns(:query)
371 assert_equal q.id, assigns(:query).id
371 assert_equal q.id, assigns(:query).id
372 assert_equal 1, assigns(:query).project_id
372 assert_equal 1, assigns(:query).project_id
373 assert_equal [1], assigns(:issues).map(&:project_id).uniq
373 assert_equal [1], assigns(:issues).map(&:project_id).uniq
374 end
374 end
375
375
376 def test_private_query_should_not_be_available_to_other_users
376 def test_private_query_should_not_be_available_to_other_users
377 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
377 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
378 @request.session[:user_id] = 3
378 @request.session[:user_id] = 3
379
379
380 get :index, :query_id => q.id
380 get :index, :query_id => q.id
381 assert_response 403
381 assert_response 403
382 end
382 end
383
383
384 def test_private_query_should_be_available_to_its_user
384 def test_private_query_should_be_available_to_its_user
385 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
385 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
386 @request.session[:user_id] = 2
386 @request.session[:user_id] = 2
387
387
388 get :index, :query_id => q.id
388 get :index, :query_id => q.id
389 assert_response :success
389 assert_response :success
390 end
390 end
391
391
392 def test_public_query_should_be_available_to_other_users
392 def test_public_query_should_be_available_to_other_users
393 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
393 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
394 @request.session[:user_id] = 3
394 @request.session[:user_id] = 3
395
395
396 get :index, :query_id => q.id
396 get :index, :query_id => q.id
397 assert_response :success
397 assert_response :success
398 end
398 end
399
399
400 def test_index_should_omit_page_param_in_export_links
400 def test_index_should_omit_page_param_in_export_links
401 get :index, :page => 2
401 get :index, :page => 2
402 assert_response :success
402 assert_response :success
403 assert_select 'a.atom[href=/issues.atom]'
403 assert_select 'a.atom[href=/issues.atom]'
404 assert_select 'a.csv[href=/issues.csv]'
404 assert_select 'a.csv[href=/issues.csv]'
405 assert_select 'a.pdf[href=/issues.pdf]'
405 assert_select 'a.pdf[href=/issues.pdf]'
406 assert_select 'form#csv-export-form[action=/issues.csv]'
406 assert_select 'form#csv-export-form[action=/issues.csv]'
407 end
407 end
408
408
409 def test_index_should_not_warn_when_not_exceeding_export_limit
409 def test_index_should_not_warn_when_not_exceeding_export_limit
410 with_settings :issues_export_limit => 200 do
410 with_settings :issues_export_limit => 200 do
411 get :index
411 get :index
412 assert_select '#csv-export-options p.icon-warning', 0
412 assert_select '#csv-export-options p.icon-warning', 0
413 end
413 end
414 end
414 end
415
415
416 def test_index_should_warn_when_exceeding_export_limit
416 def test_index_should_warn_when_exceeding_export_limit
417 with_settings :issues_export_limit => 2 do
417 with_settings :issues_export_limit => 2 do
418 get :index
418 get :index
419 assert_select '#csv-export-options p.icon-warning', :text => %r{limit: 2}
419 assert_select '#csv-export-options p.icon-warning', :text => %r{limit: 2}
420 end
420 end
421 end
421 end
422
422
423 def test_index_csv
423 def test_index_csv
424 get :index, :format => 'csv'
424 get :index, :format => 'csv'
425 assert_response :success
425 assert_response :success
426 assert_not_nil assigns(:issues)
426 assert_not_nil assigns(:issues)
427 assert_equal 'text/csv; header=present', @response.content_type
427 assert_equal 'text/csv; header=present', @response.content_type
428 assert @response.body.starts_with?("#,")
428 assert @response.body.starts_with?("#,")
429 lines = @response.body.chomp.split("\n")
429 lines = @response.body.chomp.split("\n")
430 assert_equal assigns(:query).columns.size, lines[0].split(',').size
430 assert_equal assigns(:query).columns.size, lines[0].split(',').size
431 end
431 end
432
432
433 def test_index_csv_with_project
433 def test_index_csv_with_project
434 get :index, :project_id => 1, :format => 'csv'
434 get :index, :project_id => 1, :format => 'csv'
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 end
438 end
439
439
440 def test_index_csv_with_description
440 def test_index_csv_with_description
441 Issue.generate!(:description => 'test_index_csv_with_description')
441 Issue.generate!(:description => 'test_index_csv_with_description')
442
442
443 with_settings :default_language => 'en' do
443 with_settings :default_language => 'en' do
444 get :index, :format => 'csv', :description => '1'
444 get :index, :format => 'csv', :description => '1'
445 assert_response :success
445 assert_response :success
446 assert_not_nil assigns(:issues)
446 assert_not_nil assigns(:issues)
447 end
447 end
448
448
449 assert_equal 'text/csv; header=present', response.content_type
449 assert_equal 'text/csv; header=present', response.content_type
450 headers = response.body.chomp.split("\n").first.split(',')
450 headers = response.body.chomp.split("\n").first.split(',')
451 assert_include 'Description', headers
451 assert_include 'Description', headers
452 assert_include 'test_index_csv_with_description', response.body
452 assert_include 'test_index_csv_with_description', response.body
453 end
453 end
454
454
455 def test_index_csv_with_spent_time_column
455 def test_index_csv_with_spent_time_column
456 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
456 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
457 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
457 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
458
458
459 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
459 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
460 assert_response :success
460 assert_response :success
461 assert_equal 'text/csv; header=present', @response.content_type
461 assert_equal 'text/csv; header=present', @response.content_type
462 lines = @response.body.chomp.split("\n")
462 lines = @response.body.chomp.split("\n")
463 assert_include "#{issue.id},#{issue.subject},7.33", lines
463 assert_include "#{issue.id},#{issue.subject},7.33", lines
464 end
464 end
465
465
466 def test_index_csv_with_all_columns
466 def test_index_csv_with_all_columns
467 get :index, :format => 'csv', :columns => 'all'
467 get :index, :format => 'csv', :columns => 'all'
468 assert_response :success
468 assert_response :success
469 assert_not_nil assigns(:issues)
469 assert_not_nil assigns(:issues)
470 assert_equal 'text/csv; header=present', @response.content_type
470 assert_equal 'text/csv; header=present', @response.content_type
471 assert_match /\A#,/, response.body
471 assert_match /\A#,/, response.body
472 lines = response.body.chomp.split("\n")
472 lines = response.body.chomp.split("\n")
473 assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size
473 assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size
474 end
474 end
475
475
476 def test_index_csv_with_multi_column_field
476 def test_index_csv_with_multi_column_field
477 CustomField.find(1).update_attribute :multiple, true
477 CustomField.find(1).update_attribute :multiple, true
478 issue = Issue.find(1)
478 issue = Issue.find(1)
479 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
479 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
480 issue.save!
480 issue.save!
481
481
482 get :index, :format => 'csv', :columns => 'all'
482 get :index, :format => 'csv', :columns => 'all'
483 assert_response :success
483 assert_response :success
484 lines = @response.body.chomp.split("\n")
484 lines = @response.body.chomp.split("\n")
485 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
485 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
486 end
486 end
487
487
488 def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator
488 def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator
489 field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float')
489 field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float')
490 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'})
490 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'})
491
491
492 with_settings :default_language => 'fr' do
492 with_settings :default_language => 'fr' do
493 get :index, :format => 'csv', :columns => 'all'
493 get :index, :format => 'csv', :columns => 'all'
494 assert_response :success
494 assert_response :success
495 issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s}
495 issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s}
496 assert_include '185,60', issue_line
496 assert_include '185,60', issue_line
497 end
497 end
498
498
499 with_settings :default_language => 'en' do
499 with_settings :default_language => 'en' do
500 get :index, :format => 'csv', :columns => 'all'
500 get :index, :format => 'csv', :columns => 'all'
501 assert_response :success
501 assert_response :success
502 issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s}
502 issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s}
503 assert_include '185.60', issue_line
503 assert_include '185.60', issue_line
504 end
504 end
505 end
505 end
506
506
507 def test_index_csv_should_fill_parent_column_with_parent_id
507 def test_index_csv_should_fill_parent_column_with_parent_id
508 Issue.delete_all
508 Issue.delete_all
509 parent = Issue.generate!
509 parent = Issue.generate!
510 child = Issue.generate!(:parent_issue_id => parent.id)
510 child = Issue.generate!(:parent_issue_id => parent.id)
511
511
512 with_settings :default_language => 'en' do
512 with_settings :default_language => 'en' do
513 get :index, :format => 'csv', :c => %w(parent)
513 get :index, :format => 'csv', :c => %w(parent)
514 end
514 end
515 lines = response.body.split
515 lines = response.body.split
516 assert_include "#{child.id},#{parent.id}", lines
516 assert_include "#{child.id},#{parent.id}", lines
517 end
517 end
518
518
519 def test_index_csv_big_5
519 def test_index_csv_big_5
520 with_settings :default_language => "zh-TW" do
520 with_settings :default_language => "zh-TW" do
521 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
521 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
522 str_big5 = "\xa4@\xa4\xeb"
522 str_big5 = "\xa4@\xa4\xeb"
523 if str_utf8.respond_to?(:force_encoding)
523 if str_utf8.respond_to?(:force_encoding)
524 str_utf8.force_encoding('UTF-8')
524 str_utf8.force_encoding('UTF-8')
525 str_big5.force_encoding('Big5')
525 str_big5.force_encoding('Big5')
526 end
526 end
527 issue = Issue.generate!(:subject => str_utf8)
527 issue = Issue.generate!(:subject => str_utf8)
528
528
529 get :index, :project_id => 1,
529 get :index, :project_id => 1,
530 :f => ['subject'],
530 :f => ['subject'],
531 :op => '=', :values => [str_utf8],
531 :op => '=', :values => [str_utf8],
532 :format => 'csv'
532 :format => 'csv'
533 assert_equal 'text/csv; header=present', @response.content_type
533 assert_equal 'text/csv; header=present', @response.content_type
534 lines = @response.body.chomp.split("\n")
534 lines = @response.body.chomp.split("\n")
535 s1 = "\xaa\xac\xbaA"
535 s1 = "\xaa\xac\xbaA"
536 if str_utf8.respond_to?(:force_encoding)
536 if str_utf8.respond_to?(:force_encoding)
537 s1.force_encoding('Big5')
537 s1.force_encoding('Big5')
538 end
538 end
539 assert_include s1, lines[0]
539 assert_include s1, lines[0]
540 assert_include str_big5, lines[1]
540 assert_include str_big5, lines[1]
541 end
541 end
542 end
542 end
543
543
544 def test_index_csv_cannot_convert_should_be_replaced_big_5
544 def test_index_csv_cannot_convert_should_be_replaced_big_5
545 with_settings :default_language => "zh-TW" do
545 with_settings :default_language => "zh-TW" do
546 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
546 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
547 if str_utf8.respond_to?(:force_encoding)
547 if str_utf8.respond_to?(:force_encoding)
548 str_utf8.force_encoding('UTF-8')
548 str_utf8.force_encoding('UTF-8')
549 end
549 end
550 issue = Issue.generate!(:subject => str_utf8)
550 issue = Issue.generate!(:subject => str_utf8)
551
551
552 get :index, :project_id => 1,
552 get :index, :project_id => 1,
553 :f => ['subject'],
553 :f => ['subject'],
554 :op => '=', :values => [str_utf8],
554 :op => '=', :values => [str_utf8],
555 :c => ['status', 'subject'],
555 :c => ['status', 'subject'],
556 :format => 'csv',
556 :format => 'csv',
557 :set_filter => 1
557 :set_filter => 1
558 assert_equal 'text/csv; header=present', @response.content_type
558 assert_equal 'text/csv; header=present', @response.content_type
559 lines = @response.body.chomp.split("\n")
559 lines = @response.body.chomp.split("\n")
560 s1 = "\xaa\xac\xbaA" # status
560 s1 = "\xaa\xac\xbaA" # status
561 if str_utf8.respond_to?(:force_encoding)
561 if str_utf8.respond_to?(:force_encoding)
562 s1.force_encoding('Big5')
562 s1.force_encoding('Big5')
563 end
563 end
564 assert lines[0].include?(s1)
564 assert lines[0].include?(s1)
565 s2 = lines[1].split(",")[2]
565 s2 = lines[1].split(",")[2]
566 if s1.respond_to?(:force_encoding)
566 if s1.respond_to?(:force_encoding)
567 s3 = "\xa5H?" # subject
567 s3 = "\xa5H?" # subject
568 s3.force_encoding('Big5')
568 s3.force_encoding('Big5')
569 assert_equal s3, s2
569 assert_equal s3, s2
570 elsif RUBY_PLATFORM == 'java'
570 elsif RUBY_PLATFORM == 'java'
571 assert_equal "??", s2
571 assert_equal "??", s2
572 else
572 else
573 assert_equal "\xa5H???", s2
573 assert_equal "\xa5H???", s2
574 end
574 end
575 end
575 end
576 end
576 end
577
577
578 def test_index_csv_tw
578 def test_index_csv_tw
579 with_settings :default_language => "zh-TW" do
579 with_settings :default_language => "zh-TW" do
580 str1 = "test_index_csv_tw"
580 str1 = "test_index_csv_tw"
581 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
581 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
582
582
583 get :index, :project_id => 1,
583 get :index, :project_id => 1,
584 :f => ['subject'],
584 :f => ['subject'],
585 :op => '=', :values => [str1],
585 :op => '=', :values => [str1],
586 :c => ['estimated_hours', 'subject'],
586 :c => ['estimated_hours', 'subject'],
587 :format => 'csv',
587 :format => 'csv',
588 :set_filter => 1
588 :set_filter => 1
589 assert_equal 'text/csv; header=present', @response.content_type
589 assert_equal 'text/csv; header=present', @response.content_type
590 lines = @response.body.chomp.split("\n")
590 lines = @response.body.chomp.split("\n")
591 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
591 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
592 end
592 end
593 end
593 end
594
594
595 def test_index_csv_fr
595 def test_index_csv_fr
596 with_settings :default_language => "fr" do
596 with_settings :default_language => "fr" do
597 str1 = "test_index_csv_fr"
597 str1 = "test_index_csv_fr"
598 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
598 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
599
599
600 get :index, :project_id => 1,
600 get :index, :project_id => 1,
601 :f => ['subject'],
601 :f => ['subject'],
602 :op => '=', :values => [str1],
602 :op => '=', :values => [str1],
603 :c => ['estimated_hours', 'subject'],
603 :c => ['estimated_hours', 'subject'],
604 :format => 'csv',
604 :format => 'csv',
605 :set_filter => 1
605 :set_filter => 1
606 assert_equal 'text/csv; header=present', @response.content_type
606 assert_equal 'text/csv; header=present', @response.content_type
607 lines = @response.body.chomp.split("\n")
607 lines = @response.body.chomp.split("\n")
608 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
608 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
609 end
609 end
610 end
610 end
611
611
612 def test_index_pdf
612 def test_index_pdf
613 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
613 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
614 with_settings :default_language => lang do
614 with_settings :default_language => lang do
615
615
616 get :index
616 get :index
617 assert_response :success
617 assert_response :success
618 assert_template 'index'
618 assert_template 'index'
619
619
620 get :index, :format => 'pdf'
620 get :index, :format => 'pdf'
621 assert_response :success
621 assert_response :success
622 assert_not_nil assigns(:issues)
622 assert_not_nil assigns(:issues)
623 assert_equal 'application/pdf', @response.content_type
623 assert_equal 'application/pdf', @response.content_type
624
624
625 get :index, :project_id => 1, :format => 'pdf'
625 get :index, :project_id => 1, :format => 'pdf'
626 assert_response :success
626 assert_response :success
627 assert_not_nil assigns(:issues)
627 assert_not_nil assigns(:issues)
628 assert_equal 'application/pdf', @response.content_type
628 assert_equal 'application/pdf', @response.content_type
629
629
630 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
630 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
631 assert_response :success
631 assert_response :success
632 assert_not_nil assigns(:issues)
632 assert_not_nil assigns(:issues)
633 assert_equal 'application/pdf', @response.content_type
633 assert_equal 'application/pdf', @response.content_type
634 end
634 end
635 end
635 end
636 end
636 end
637
637
638 def test_index_pdf_with_query_grouped_by_list_custom_field
638 def test_index_pdf_with_query_grouped_by_list_custom_field
639 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
639 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
640 assert_response :success
640 assert_response :success
641 assert_not_nil assigns(:issues)
641 assert_not_nil assigns(:issues)
642 assert_not_nil assigns(:issue_count_by_group)
642 assert_not_nil assigns(:issue_count_by_group)
643 assert_equal 'application/pdf', @response.content_type
643 assert_equal 'application/pdf', @response.content_type
644 end
644 end
645
645
646 def test_index_atom
646 def test_index_atom
647 get :index, :project_id => 'ecookbook', :format => 'atom'
647 get :index, :project_id => 'ecookbook', :format => 'atom'
648 assert_response :success
648 assert_response :success
649 assert_template 'common/feed'
649 assert_template 'common/feed'
650 assert_equal 'application/atom+xml', response.content_type
650 assert_equal 'application/atom+xml', response.content_type
651
651
652 assert_select 'feed' do
652 assert_select 'feed' do
653 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
653 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
654 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
654 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
655 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
655 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
656 end
656 end
657 end
657 end
658
658
659 def test_index_sort
659 def test_index_sort
660 get :index, :sort => 'tracker,id:desc'
660 get :index, :sort => 'tracker,id:desc'
661 assert_response :success
661 assert_response :success
662
662
663 sort_params = @request.session['issues_index_sort']
663 sort_params = @request.session['issues_index_sort']
664 assert sort_params.is_a?(String)
664 assert sort_params.is_a?(String)
665 assert_equal 'tracker,id:desc', sort_params
665 assert_equal 'tracker,id:desc', sort_params
666
666
667 issues = assigns(:issues)
667 issues = assigns(:issues)
668 assert_not_nil issues
668 assert_not_nil issues
669 assert !issues.empty?
669 assert !issues.empty?
670 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
670 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
671 assert_select 'table.issues.sort-by-tracker.sort-asc'
671 assert_select 'table.issues.sort-by-tracker.sort-asc'
672 end
672 end
673
673
674 def test_index_sort_by_field_not_included_in_columns
674 def test_index_sort_by_field_not_included_in_columns
675 Setting.issue_list_default_columns = %w(subject author)
675 Setting.issue_list_default_columns = %w(subject author)
676 get :index, :sort => 'tracker'
676 get :index, :sort => 'tracker'
677 end
677 end
678
678
679 def test_index_sort_by_assigned_to
679 def test_index_sort_by_assigned_to
680 get :index, :sort => 'assigned_to'
680 get :index, :sort => 'assigned_to'
681 assert_response :success
681 assert_response :success
682 assignees = assigns(:issues).collect(&:assigned_to).compact
682 assignees = assigns(:issues).collect(&:assigned_to).compact
683 assert_equal assignees.sort, assignees
683 assert_equal assignees.sort, assignees
684 assert_select 'table.issues.sort-by-assigned-to.sort-asc'
684 assert_select 'table.issues.sort-by-assigned-to.sort-asc'
685 end
685 end
686
686
687 def test_index_sort_by_assigned_to_desc
687 def test_index_sort_by_assigned_to_desc
688 get :index, :sort => 'assigned_to:desc'
688 get :index, :sort => 'assigned_to:desc'
689 assert_response :success
689 assert_response :success
690 assignees = assigns(:issues).collect(&:assigned_to).compact
690 assignees = assigns(:issues).collect(&:assigned_to).compact
691 assert_equal assignees.sort.reverse, assignees
691 assert_equal assignees.sort.reverse, assignees
692 assert_select 'table.issues.sort-by-assigned-to.sort-desc'
692 assert_select 'table.issues.sort-by-assigned-to.sort-desc'
693 end
693 end
694
694
695 def test_index_group_by_assigned_to
695 def test_index_group_by_assigned_to
696 get :index, :group_by => 'assigned_to', :sort => 'priority'
696 get :index, :group_by => 'assigned_to', :sort => 'priority'
697 assert_response :success
697 assert_response :success
698 end
698 end
699
699
700 def test_index_sort_by_author
700 def test_index_sort_by_author
701 get :index, :sort => 'author'
701 get :index, :sort => 'author'
702 assert_response :success
702 assert_response :success
703 authors = assigns(:issues).collect(&:author)
703 authors = assigns(:issues).collect(&:author)
704 assert_equal authors.sort, authors
704 assert_equal authors.sort, authors
705 end
705 end
706
706
707 def test_index_sort_by_author_desc
707 def test_index_sort_by_author_desc
708 get :index, :sort => 'author:desc'
708 get :index, :sort => 'author:desc'
709 assert_response :success
709 assert_response :success
710 authors = assigns(:issues).collect(&:author)
710 authors = assigns(:issues).collect(&:author)
711 assert_equal authors.sort.reverse, authors
711 assert_equal authors.sort.reverse, authors
712 end
712 end
713
713
714 def test_index_group_by_author
714 def test_index_group_by_author
715 get :index, :group_by => 'author', :sort => 'priority'
715 get :index, :group_by => 'author', :sort => 'priority'
716 assert_response :success
716 assert_response :success
717 end
717 end
718
718
719 def test_index_sort_by_spent_hours
719 def test_index_sort_by_spent_hours
720 get :index, :sort => 'spent_hours:desc'
720 get :index, :sort => 'spent_hours:desc'
721 assert_response :success
721 assert_response :success
722 hours = assigns(:issues).collect(&:spent_hours)
722 hours = assigns(:issues).collect(&:spent_hours)
723 assert_equal hours.sort.reverse, hours
723 assert_equal hours.sort.reverse, hours
724 end
724 end
725
725
726 def test_index_sort_by_user_custom_field
726 def test_index_sort_by_user_custom_field
727 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
727 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
728 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
728 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
729 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
729 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
730 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
730 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
731 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
731 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
732
732
733 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
733 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
734 assert_response :success
734 assert_response :success
735
735
736 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
736 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
737 end
737 end
738
738
739 def test_index_with_columns
739 def test_index_with_columns
740 columns = ['tracker', 'subject', 'assigned_to']
740 columns = ['tracker', 'subject', 'assigned_to']
741 get :index, :set_filter => 1, :c => columns
741 get :index, :set_filter => 1, :c => columns
742 assert_response :success
742 assert_response :success
743
743
744 # query should use specified columns
744 # query should use specified columns
745 query = assigns(:query)
745 query = assigns(:query)
746 assert_kind_of IssueQuery, query
746 assert_kind_of IssueQuery, query
747 assert_equal columns, query.column_names.map(&:to_s)
747 assert_equal columns, query.column_names.map(&:to_s)
748
748
749 # columns should be stored in session
749 # columns should be stored in session
750 assert_kind_of Hash, session[:query]
750 assert_kind_of Hash, session[:query]
751 assert_kind_of Array, session[:query][:column_names]
751 assert_kind_of Array, session[:query][:column_names]
752 assert_equal columns, session[:query][:column_names].map(&:to_s)
752 assert_equal columns, session[:query][:column_names].map(&:to_s)
753
753
754 # ensure only these columns are kept in the selected columns list
754 # ensure only these columns are kept in the selected columns list
755 assert_select 'select#selected_columns option' do
755 assert_select 'select#selected_columns option' do
756 assert_select 'option', 3
756 assert_select 'option', 3
757 assert_select 'option[value=tracker]'
757 assert_select 'option[value=tracker]'
758 assert_select 'option[value=project]', 0
758 assert_select 'option[value=project]', 0
759 end
759 end
760 end
760 end
761
761
762 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
762 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
763 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
763 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
764 get :index, :set_filter => 1
764 get :index, :set_filter => 1
765
765
766 # query should use specified columns
766 # query should use specified columns
767 query = assigns(:query)
767 query = assigns(:query)
768 assert_kind_of IssueQuery, query
768 assert_kind_of IssueQuery, query
769 assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
769 assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
770 end
770 end
771
771
772 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
772 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
773 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
773 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
774 columns = ['id', 'tracker', 'subject', 'assigned_to']
774 columns = ['id', 'tracker', 'subject', 'assigned_to']
775 get :index, :set_filter => 1, :c => columns
775 get :index, :set_filter => 1, :c => columns
776
776
777 # query should use specified columns
777 # query should use specified columns
778 query = assigns(:query)
778 query = assigns(:query)
779 assert_kind_of IssueQuery, query
779 assert_kind_of IssueQuery, query
780 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
780 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
781 end
781 end
782
782
783 def test_index_with_custom_field_column
783 def test_index_with_custom_field_column
784 columns = %w(tracker subject cf_2)
784 columns = %w(tracker subject cf_2)
785 get :index, :set_filter => 1, :c => columns
785 get :index, :set_filter => 1, :c => columns
786 assert_response :success
786 assert_response :success
787
787
788 # query should use specified columns
788 # query should use specified columns
789 query = assigns(:query)
789 query = assigns(:query)
790 assert_kind_of IssueQuery, query
790 assert_kind_of IssueQuery, query
791 assert_equal columns, query.column_names.map(&:to_s)
791 assert_equal columns, query.column_names.map(&:to_s)
792
792
793 assert_select 'table.issues td.cf_2.string'
793 assert_select 'table.issues td.cf_2.string'
794 end
794 end
795
795
796 def test_index_with_multi_custom_field_column
796 def test_index_with_multi_custom_field_column
797 field = CustomField.find(1)
797 field = CustomField.find(1)
798 field.update_attribute :multiple, true
798 field.update_attribute :multiple, true
799 issue = Issue.find(1)
799 issue = Issue.find(1)
800 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
800 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
801 issue.save!
801 issue.save!
802
802
803 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
803 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
804 assert_response :success
804 assert_response :success
805
805
806 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
806 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
807 end
807 end
808
808
809 def test_index_with_multi_user_custom_field_column
809 def test_index_with_multi_user_custom_field_column
810 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
810 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
811 :tracker_ids => [1], :is_for_all => true)
811 :tracker_ids => [1], :is_for_all => true)
812 issue = Issue.find(1)
812 issue = Issue.find(1)
813 issue.custom_field_values = {field.id => ['2', '3']}
813 issue.custom_field_values = {field.id => ['2', '3']}
814 issue.save!
814 issue.save!
815
815
816 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
816 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
817 assert_response :success
817 assert_response :success
818
818
819 assert_select "table.issues td.cf_#{field.id}" do
819 assert_select "table.issues td.cf_#{field.id}" do
820 assert_select 'a', 2
820 assert_select 'a', 2
821 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
821 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
822 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
822 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
823 end
823 end
824 end
824 end
825
825
826 def test_index_with_date_column
826 def test_index_with_date_column
827 with_settings :date_format => '%d/%m/%Y' do
827 with_settings :date_format => '%d/%m/%Y' do
828 Issue.find(1).update_attribute :start_date, '1987-08-24'
828 Issue.find(1).update_attribute :start_date, '1987-08-24'
829 get :index, :set_filter => 1, :c => %w(start_date)
829 get :index, :set_filter => 1, :c => %w(start_date)
830 assert_select "table.issues td.start_date", :text => '24/08/1987'
830 assert_select "table.issues td.start_date", :text => '24/08/1987'
831 end
831 end
832 end
832 end
833
833
834 def test_index_with_done_ratio_column
834 def test_index_with_done_ratio_column
835 Issue.find(1).update_attribute :done_ratio, 40
835 Issue.find(1).update_attribute :done_ratio, 40
836 get :index, :set_filter => 1, :c => %w(done_ratio)
836 get :index, :set_filter => 1, :c => %w(done_ratio)
837 assert_select 'table.issues td.done_ratio' do
837 assert_select 'table.issues td.done_ratio' do
838 assert_select 'table.progress' do
838 assert_select 'table.progress' do
839 assert_select 'td.closed[style=?]', 'width: 40%;'
839 assert_select 'td.closed[style=?]', 'width: 40%;'
840 end
840 end
841 end
841 end
842 end
842 end
843
843
844 def test_index_with_spent_hours_column
844 def test_index_with_spent_hours_column
845 get :index, :set_filter => 1, :c => %w(subject spent_hours)
845 get :index, :set_filter => 1, :c => %w(subject spent_hours)
846 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
846 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
847 end
847 end
848
848
849 def test_index_should_not_show_spent_hours_column_without_permission
849 def test_index_should_not_show_spent_hours_column_without_permission
850 Role.anonymous.remove_permission! :view_time_entries
850 Role.anonymous.remove_permission! :view_time_entries
851 get :index, :set_filter => 1, :c => %w(subject spent_hours)
851 get :index, :set_filter => 1, :c => %w(subject spent_hours)
852 assert_select 'td.spent_hours', 0
852 assert_select 'td.spent_hours', 0
853 end
853 end
854
854
855 def test_index_with_fixed_version_column
855 def test_index_with_fixed_version_column
856 get :index, :set_filter => 1, :c => %w(fixed_version)
856 get :index, :set_filter => 1, :c => %w(fixed_version)
857 assert_select 'table.issues td.fixed_version' do
857 assert_select 'table.issues td.fixed_version' do
858 assert_select 'a[href=?]', '/versions/2', :text => '1.0'
858 assert_select 'a[href=?]', '/versions/2', :text => 'eCookbook - 1.0'
859 end
859 end
860 end
860 end
861
861
862 def test_index_with_relations_column
862 def test_index_with_relations_column
863 IssueRelation.delete_all
863 IssueRelation.delete_all
864 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
864 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
865 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
865 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
866 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
866 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
867 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
867 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
868
868
869 get :index, :set_filter => 1, :c => %w(subject relations)
869 get :index, :set_filter => 1, :c => %w(subject relations)
870 assert_response :success
870 assert_response :success
871 assert_select "tr#issue-1 td.relations" do
871 assert_select "tr#issue-1 td.relations" do
872 assert_select "span", 3
872 assert_select "span", 3
873 assert_select "span", :text => "Related to #7"
873 assert_select "span", :text => "Related to #7"
874 assert_select "span", :text => "Related to #8"
874 assert_select "span", :text => "Related to #8"
875 assert_select "span", :text => "Blocks #11"
875 assert_select "span", :text => "Blocks #11"
876 end
876 end
877 assert_select "tr#issue-2 td.relations" do
877 assert_select "tr#issue-2 td.relations" do
878 assert_select "span", 1
878 assert_select "span", 1
879 assert_select "span", :text => "Blocked by #12"
879 assert_select "span", :text => "Blocked by #12"
880 end
880 end
881 assert_select "tr#issue-3 td.relations" do
881 assert_select "tr#issue-3 td.relations" do
882 assert_select "span", 0
882 assert_select "span", 0
883 end
883 end
884
884
885 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
885 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
886 assert_response :success
886 assert_response :success
887 assert_equal 'text/csv; header=present', response.content_type
887 assert_equal 'text/csv; header=present', response.content_type
888 lines = response.body.chomp.split("\n")
888 lines = response.body.chomp.split("\n")
889 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
889 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
890 assert_include '2,Blocked by #12', lines
890 assert_include '2,Blocked by #12', lines
891 assert_include '3,""', lines
891 assert_include '3,""', lines
892
892
893 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
893 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
894 assert_response :success
894 assert_response :success
895 assert_equal 'application/pdf', response.content_type
895 assert_equal 'application/pdf', response.content_type
896 end
896 end
897
897
898 def test_index_with_description_column
898 def test_index_with_description_column
899 get :index, :set_filter => 1, :c => %w(subject description)
899 get :index, :set_filter => 1, :c => %w(subject description)
900
900
901 assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject
901 assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject
902 assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes'
902 assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes'
903
903
904 get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf'
904 get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf'
905 assert_response :success
905 assert_response :success
906 assert_equal 'application/pdf', response.content_type
906 assert_equal 'application/pdf', response.content_type
907 end
907 end
908
908
909 def test_index_with_parent_column
909 def test_index_with_parent_column
910 Issue.delete_all
910 Issue.delete_all
911 parent = Issue.generate!
911 parent = Issue.generate!
912 child = Issue.generate!(:parent_issue_id => parent.id)
912 child = Issue.generate!(:parent_issue_id => parent.id)
913
913
914 get :index, :c => %w(parent)
914 get :index, :c => %w(parent)
915
915
916 assert_select 'td.parent', :text => "#{parent.tracker} ##{parent.id}"
916 assert_select 'td.parent', :text => "#{parent.tracker} ##{parent.id}"
917 assert_select 'td.parent a[title=?]', parent.subject
917 assert_select 'td.parent a[title=?]', parent.subject
918 end
918 end
919
919
920 def test_index_send_html_if_query_is_invalid
920 def test_index_send_html_if_query_is_invalid
921 get :index, :f => ['start_date'], :op => {:start_date => '='}
921 get :index, :f => ['start_date'], :op => {:start_date => '='}
922 assert_equal 'text/html', @response.content_type
922 assert_equal 'text/html', @response.content_type
923 assert_template 'index'
923 assert_template 'index'
924 end
924 end
925
925
926 def test_index_send_nothing_if_query_is_invalid
926 def test_index_send_nothing_if_query_is_invalid
927 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
927 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
928 assert_equal 'text/csv', @response.content_type
928 assert_equal 'text/csv', @response.content_type
929 assert @response.body.blank?
929 assert @response.body.blank?
930 end
930 end
931
931
932 def test_show_by_anonymous
932 def test_show_by_anonymous
933 get :show, :id => 1
933 get :show, :id => 1
934 assert_response :success
934 assert_response :success
935 assert_template 'show'
935 assert_template 'show'
936 assert_equal Issue.find(1), assigns(:issue)
936 assert_equal Issue.find(1), assigns(:issue)
937 assert_select 'div.issue div.description', :text => /Unable to print recipes/
937 assert_select 'div.issue div.description', :text => /Unable to print recipes/
938 # anonymous role is allowed to add a note
938 # anonymous role is allowed to add a note
939 assert_select 'form#issue-form' do
939 assert_select 'form#issue-form' do
940 assert_select 'fieldset' do
940 assert_select 'fieldset' do
941 assert_select 'legend', :text => 'Notes'
941 assert_select 'legend', :text => 'Notes'
942 assert_select 'textarea[name=?]', 'issue[notes]'
942 assert_select 'textarea[name=?]', 'issue[notes]'
943 end
943 end
944 end
944 end
945 assert_select 'title', :text => "Bug #1: #{ESCAPED_UCANT} print recipes - eCookbook - Redmine"
945 assert_select 'title', :text => "Bug #1: #{ESCAPED_UCANT} print recipes - eCookbook - Redmine"
946 end
946 end
947
947
948 def test_show_by_manager
948 def test_show_by_manager
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 assert_select 'a', :text => /Quote/
952 assert_select 'a', :text => /Quote/
953 assert_select 'form#issue-form' do
953 assert_select 'form#issue-form' do
954 assert_select 'fieldset' do
954 assert_select 'fieldset' do
955 assert_select 'legend', :text => 'Change properties'
955 assert_select 'legend', :text => 'Change properties'
956 assert_select 'input[name=?]', 'issue[subject]'
956 assert_select 'input[name=?]', 'issue[subject]'
957 end
957 end
958 assert_select 'fieldset' do
958 assert_select 'fieldset' do
959 assert_select 'legend', :text => 'Log time'
959 assert_select 'legend', :text => 'Log time'
960 assert_select 'input[name=?]', 'time_entry[hours]'
960 assert_select 'input[name=?]', 'time_entry[hours]'
961 end
961 end
962 assert_select 'fieldset' do
962 assert_select 'fieldset' do
963 assert_select 'legend', :text => 'Notes'
963 assert_select 'legend', :text => 'Notes'
964 assert_select 'textarea[name=?]', 'issue[notes]'
964 assert_select 'textarea[name=?]', 'issue[notes]'
965 end
965 end
966 end
966 end
967 end
967 end
968
968
969 def test_show_should_display_update_form
969 def test_show_should_display_update_form
970 @request.session[:user_id] = 2
970 @request.session[:user_id] = 2
971 get :show, :id => 1
971 get :show, :id => 1
972 assert_response :success
972 assert_response :success
973
973
974 assert_select 'form#issue-form' do
974 assert_select 'form#issue-form' do
975 assert_select 'input[name=?]', 'issue[is_private]'
975 assert_select 'input[name=?]', 'issue[is_private]'
976 assert_select 'select[name=?]', 'issue[project_id]'
976 assert_select 'select[name=?]', 'issue[project_id]'
977 assert_select 'select[name=?]', 'issue[tracker_id]'
977 assert_select 'select[name=?]', 'issue[tracker_id]'
978 assert_select 'input[name=?]', 'issue[subject]'
978 assert_select 'input[name=?]', 'issue[subject]'
979 assert_select 'textarea[name=?]', 'issue[description]'
979 assert_select 'textarea[name=?]', 'issue[description]'
980 assert_select 'select[name=?]', 'issue[status_id]'
980 assert_select 'select[name=?]', 'issue[status_id]'
981 assert_select 'select[name=?]', 'issue[priority_id]'
981 assert_select 'select[name=?]', 'issue[priority_id]'
982 assert_select 'select[name=?]', 'issue[assigned_to_id]'
982 assert_select 'select[name=?]', 'issue[assigned_to_id]'
983 assert_select 'select[name=?]', 'issue[category_id]'
983 assert_select 'select[name=?]', 'issue[category_id]'
984 assert_select 'select[name=?]', 'issue[fixed_version_id]'
984 assert_select 'select[name=?]', 'issue[fixed_version_id]'
985 assert_select 'input[name=?]', 'issue[parent_issue_id]'
985 assert_select 'input[name=?]', 'issue[parent_issue_id]'
986 assert_select 'input[name=?]', 'issue[start_date]'
986 assert_select 'input[name=?]', 'issue[start_date]'
987 assert_select 'input[name=?]', 'issue[due_date]'
987 assert_select 'input[name=?]', 'issue[due_date]'
988 assert_select 'select[name=?]', 'issue[done_ratio]'
988 assert_select 'select[name=?]', 'issue[done_ratio]'
989 assert_select 'input[name=?]', 'issue[custom_field_values][2]'
989 assert_select 'input[name=?]', 'issue[custom_field_values][2]'
990 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
990 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
991 assert_select 'textarea[name=?]', 'issue[notes]'
991 assert_select 'textarea[name=?]', 'issue[notes]'
992 end
992 end
993 end
993 end
994
994
995 def test_show_should_display_update_form_with_minimal_permissions
995 def test_show_should_display_update_form_with_minimal_permissions
996 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
996 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
997 WorkflowTransition.delete_all :role_id => 1
997 WorkflowTransition.delete_all :role_id => 1
998
998
999 @request.session[:user_id] = 2
999 @request.session[:user_id] = 2
1000 get :show, :id => 1
1000 get :show, :id => 1
1001 assert_response :success
1001 assert_response :success
1002
1002
1003 assert_select 'form#issue-form' do
1003 assert_select 'form#issue-form' do
1004 assert_select 'input[name=?]', 'issue[is_private]', 0
1004 assert_select 'input[name=?]', 'issue[is_private]', 0
1005 assert_select 'select[name=?]', 'issue[project_id]', 0
1005 assert_select 'select[name=?]', 'issue[project_id]', 0
1006 assert_select 'select[name=?]', 'issue[tracker_id]', 0
1006 assert_select 'select[name=?]', 'issue[tracker_id]', 0
1007 assert_select 'input[name=?]', 'issue[subject]', 0
1007 assert_select 'input[name=?]', 'issue[subject]', 0
1008 assert_select 'textarea[name=?]', 'issue[description]', 0
1008 assert_select 'textarea[name=?]', 'issue[description]', 0
1009 assert_select 'select[name=?]', 'issue[status_id]', 0
1009 assert_select 'select[name=?]', 'issue[status_id]', 0
1010 assert_select 'select[name=?]', 'issue[priority_id]', 0
1010 assert_select 'select[name=?]', 'issue[priority_id]', 0
1011 assert_select 'select[name=?]', 'issue[assigned_to_id]', 0
1011 assert_select 'select[name=?]', 'issue[assigned_to_id]', 0
1012 assert_select 'select[name=?]', 'issue[category_id]', 0
1012 assert_select 'select[name=?]', 'issue[category_id]', 0
1013 assert_select 'select[name=?]', 'issue[fixed_version_id]', 0
1013 assert_select 'select[name=?]', 'issue[fixed_version_id]', 0
1014 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1014 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1015 assert_select 'input[name=?]', 'issue[start_date]', 0
1015 assert_select 'input[name=?]', 'issue[start_date]', 0
1016 assert_select 'input[name=?]', 'issue[due_date]', 0
1016 assert_select 'input[name=?]', 'issue[due_date]', 0
1017 assert_select 'select[name=?]', 'issue[done_ratio]', 0
1017 assert_select 'select[name=?]', 'issue[done_ratio]', 0
1018 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
1018 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
1019 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1019 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1020 assert_select 'textarea[name=?]', 'issue[notes]'
1020 assert_select 'textarea[name=?]', 'issue[notes]'
1021 end
1021 end
1022 end
1022 end
1023
1023
1024 def test_show_should_display_update_form_with_workflow_permissions
1024 def test_show_should_display_update_form_with_workflow_permissions
1025 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
1025 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
1026
1026
1027 @request.session[:user_id] = 2
1027 @request.session[:user_id] = 2
1028 get :show, :id => 1
1028 get :show, :id => 1
1029 assert_response :success
1029 assert_response :success
1030
1030
1031 assert_select 'form#issue-form' do
1031 assert_select 'form#issue-form' do
1032 assert_select 'input[name=?]', 'issue[is_private]', 0
1032 assert_select 'input[name=?]', 'issue[is_private]', 0
1033 assert_select 'select[name=?]', 'issue[project_id]', 0
1033 assert_select 'select[name=?]', 'issue[project_id]', 0
1034 assert_select 'select[name=?]', 'issue[tracker_id]', 0
1034 assert_select 'select[name=?]', 'issue[tracker_id]', 0
1035 assert_select 'input[name=?]', 'issue[subject]', 0
1035 assert_select 'input[name=?]', 'issue[subject]', 0
1036 assert_select 'textarea[name=?]', 'issue[description]', 0
1036 assert_select 'textarea[name=?]', 'issue[description]', 0
1037 assert_select 'select[name=?]', 'issue[status_id]'
1037 assert_select 'select[name=?]', 'issue[status_id]'
1038 assert_select 'select[name=?]', 'issue[priority_id]', 0
1038 assert_select 'select[name=?]', 'issue[priority_id]', 0
1039 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1039 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1040 assert_select 'select[name=?]', 'issue[category_id]', 0
1040 assert_select 'select[name=?]', 'issue[category_id]', 0
1041 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1041 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1042 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1042 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1043 assert_select 'input[name=?]', 'issue[start_date]', 0
1043 assert_select 'input[name=?]', 'issue[start_date]', 0
1044 assert_select 'input[name=?]', 'issue[due_date]', 0
1044 assert_select 'input[name=?]', 'issue[due_date]', 0
1045 assert_select 'select[name=?]', 'issue[done_ratio]'
1045 assert_select 'select[name=?]', 'issue[done_ratio]'
1046 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
1046 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
1047 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1047 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1048 assert_select 'textarea[name=?]', 'issue[notes]'
1048 assert_select 'textarea[name=?]', 'issue[notes]'
1049 end
1049 end
1050 end
1050 end
1051
1051
1052 def test_show_should_not_display_update_form_without_permissions
1052 def test_show_should_not_display_update_form_without_permissions
1053 Role.find(1).update_attribute :permissions, [:view_issues]
1053 Role.find(1).update_attribute :permissions, [:view_issues]
1054
1054
1055 @request.session[:user_id] = 2
1055 @request.session[:user_id] = 2
1056 get :show, :id => 1
1056 get :show, :id => 1
1057 assert_response :success
1057 assert_response :success
1058
1058
1059 assert_select 'form#issue-form', 0
1059 assert_select 'form#issue-form', 0
1060 end
1060 end
1061
1061
1062 def test_update_form_should_not_display_inactive_enumerations
1062 def test_update_form_should_not_display_inactive_enumerations
1063 assert !IssuePriority.find(15).active?
1063 assert !IssuePriority.find(15).active?
1064
1064
1065 @request.session[:user_id] = 2
1065 @request.session[:user_id] = 2
1066 get :show, :id => 1
1066 get :show, :id => 1
1067 assert_response :success
1067 assert_response :success
1068
1068
1069 assert_select 'form#issue-form' do
1069 assert_select 'form#issue-form' do
1070 assert_select 'select[name=?]', 'issue[priority_id]' do
1070 assert_select 'select[name=?]', 'issue[priority_id]' do
1071 assert_select 'option[value=4]'
1071 assert_select 'option[value=4]'
1072 assert_select 'option[value=15]', 0
1072 assert_select 'option[value=15]', 0
1073 end
1073 end
1074 end
1074 end
1075 end
1075 end
1076
1076
1077 def test_update_form_should_allow_attachment_upload
1077 def test_update_form_should_allow_attachment_upload
1078 @request.session[:user_id] = 2
1078 @request.session[:user_id] = 2
1079 get :show, :id => 1
1079 get :show, :id => 1
1080
1080
1081 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
1081 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
1082 assert_select 'input[type=file][name=?]', 'attachments[dummy][file]'
1082 assert_select 'input[type=file][name=?]', 'attachments[dummy][file]'
1083 end
1083 end
1084 end
1084 end
1085
1085
1086 def test_show_should_deny_anonymous_access_without_permission
1086 def test_show_should_deny_anonymous_access_without_permission
1087 Role.anonymous.remove_permission!(:view_issues)
1087 Role.anonymous.remove_permission!(:view_issues)
1088 get :show, :id => 1
1088 get :show, :id => 1
1089 assert_response :redirect
1089 assert_response :redirect
1090 end
1090 end
1091
1091
1092 def test_show_should_deny_anonymous_access_to_private_issue
1092 def test_show_should_deny_anonymous_access_to_private_issue
1093 Issue.where(:id => 1).update_all(["is_private = ?", true])
1093 Issue.where(:id => 1).update_all(["is_private = ?", true])
1094 get :show, :id => 1
1094 get :show, :id => 1
1095 assert_response :redirect
1095 assert_response :redirect
1096 end
1096 end
1097
1097
1098 def test_show_should_deny_non_member_access_without_permission
1098 def test_show_should_deny_non_member_access_without_permission
1099 Role.non_member.remove_permission!(:view_issues)
1099 Role.non_member.remove_permission!(:view_issues)
1100 @request.session[:user_id] = 9
1100 @request.session[:user_id] = 9
1101 get :show, :id => 1
1101 get :show, :id => 1
1102 assert_response 403
1102 assert_response 403
1103 end
1103 end
1104
1104
1105 def test_show_should_deny_non_member_access_to_private_issue
1105 def test_show_should_deny_non_member_access_to_private_issue
1106 Issue.where(:id => 1).update_all(["is_private = ?", true])
1106 Issue.where(:id => 1).update_all(["is_private = ?", true])
1107 @request.session[:user_id] = 9
1107 @request.session[:user_id] = 9
1108 get :show, :id => 1
1108 get :show, :id => 1
1109 assert_response 403
1109 assert_response 403
1110 end
1110 end
1111
1111
1112 def test_show_should_deny_member_access_without_permission
1112 def test_show_should_deny_member_access_without_permission
1113 Role.find(1).remove_permission!(:view_issues)
1113 Role.find(1).remove_permission!(:view_issues)
1114 @request.session[:user_id] = 2
1114 @request.session[:user_id] = 2
1115 get :show, :id => 1
1115 get :show, :id => 1
1116 assert_response 403
1116 assert_response 403
1117 end
1117 end
1118
1118
1119 def test_show_should_deny_member_access_to_private_issue_without_permission
1119 def test_show_should_deny_member_access_to_private_issue_without_permission
1120 Issue.where(:id => 1).update_all(["is_private = ?", true])
1120 Issue.where(:id => 1).update_all(["is_private = ?", true])
1121 @request.session[:user_id] = 3
1121 @request.session[:user_id] = 3
1122 get :show, :id => 1
1122 get :show, :id => 1
1123 assert_response 403
1123 assert_response 403
1124 end
1124 end
1125
1125
1126 def test_show_should_allow_author_access_to_private_issue
1126 def test_show_should_allow_author_access_to_private_issue
1127 Issue.where(:id => 1).update_all(["is_private = ?, author_id = 3", true])
1127 Issue.where(:id => 1).update_all(["is_private = ?, author_id = 3", true])
1128 @request.session[:user_id] = 3
1128 @request.session[:user_id] = 3
1129 get :show, :id => 1
1129 get :show, :id => 1
1130 assert_response :success
1130 assert_response :success
1131 end
1131 end
1132
1132
1133 def test_show_should_allow_assignee_access_to_private_issue
1133 def test_show_should_allow_assignee_access_to_private_issue
1134 Issue.where(:id => 1).update_all(["is_private = ?, assigned_to_id = 3", true])
1134 Issue.where(:id => 1).update_all(["is_private = ?, assigned_to_id = 3", true])
1135 @request.session[:user_id] = 3
1135 @request.session[:user_id] = 3
1136 get :show, :id => 1
1136 get :show, :id => 1
1137 assert_response :success
1137 assert_response :success
1138 end
1138 end
1139
1139
1140 def test_show_should_allow_member_access_to_private_issue_with_permission
1140 def test_show_should_allow_member_access_to_private_issue_with_permission
1141 Issue.where(:id => 1).update_all(["is_private = ?", true])
1141 Issue.where(:id => 1).update_all(["is_private = ?", true])
1142 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1142 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1143 @request.session[:user_id] = 3
1143 @request.session[:user_id] = 3
1144 get :show, :id => 1
1144 get :show, :id => 1
1145 assert_response :success
1145 assert_response :success
1146 end
1146 end
1147
1147
1148 def test_show_should_not_disclose_relations_to_invisible_issues
1148 def test_show_should_not_disclose_relations_to_invisible_issues
1149 Setting.cross_project_issue_relations = '1'
1149 Setting.cross_project_issue_relations = '1'
1150 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1150 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1151 # Relation to a private project issue
1151 # Relation to a private project issue
1152 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1152 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1153
1153
1154 get :show, :id => 1
1154 get :show, :id => 1
1155 assert_response :success
1155 assert_response :success
1156
1156
1157 assert_select 'div#relations' do
1157 assert_select 'div#relations' do
1158 assert_select 'a', :text => /#2$/
1158 assert_select 'a', :text => /#2$/
1159 assert_select 'a', :text => /#4$/, :count => 0
1159 assert_select 'a', :text => /#4$/, :count => 0
1160 end
1160 end
1161 end
1161 end
1162
1162
1163 def test_show_should_list_subtasks
1163 def test_show_should_list_subtasks
1164 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1164 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1165
1165
1166 get :show, :id => 1
1166 get :show, :id => 1
1167 assert_response :success
1167 assert_response :success
1168
1168
1169 assert_select 'div#issue_tree' do
1169 assert_select 'div#issue_tree' do
1170 assert_select 'td.subject', :text => /Child Issue/
1170 assert_select 'td.subject', :text => /Child Issue/
1171 end
1171 end
1172 end
1172 end
1173
1173
1174 def test_show_should_list_parents
1174 def test_show_should_list_parents
1175 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1175 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1176
1176
1177 get :show, :id => issue.id
1177 get :show, :id => issue.id
1178 assert_response :success
1178 assert_response :success
1179
1179
1180 assert_select 'div.subject' do
1180 assert_select 'div.subject' do
1181 assert_select 'h3', 'Child Issue'
1181 assert_select 'h3', 'Child Issue'
1182 assert_select 'a[href=/issues/1]'
1182 assert_select 'a[href=/issues/1]'
1183 end
1183 end
1184 end
1184 end
1185
1185
1186 def test_show_should_not_display_prev_next_links_without_query_in_session
1186 def test_show_should_not_display_prev_next_links_without_query_in_session
1187 get :show, :id => 1
1187 get :show, :id => 1
1188 assert_response :success
1188 assert_response :success
1189 assert_nil assigns(:prev_issue_id)
1189 assert_nil assigns(:prev_issue_id)
1190 assert_nil assigns(:next_issue_id)
1190 assert_nil assigns(:next_issue_id)
1191
1191
1192 assert_select 'div.next-prev-links', 0
1192 assert_select 'div.next-prev-links', 0
1193 end
1193 end
1194
1194
1195 def test_show_should_display_prev_next_links_with_query_in_session
1195 def test_show_should_display_prev_next_links_with_query_in_session
1196 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1196 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1197 @request.session['issues_index_sort'] = 'id'
1197 @request.session['issues_index_sort'] = 'id'
1198
1198
1199 with_settings :display_subprojects_issues => '0' do
1199 with_settings :display_subprojects_issues => '0' do
1200 get :show, :id => 3
1200 get :show, :id => 3
1201 end
1201 end
1202
1202
1203 assert_response :success
1203 assert_response :success
1204 # Previous and next issues for all projects
1204 # Previous and next issues for all projects
1205 assert_equal 2, assigns(:prev_issue_id)
1205 assert_equal 2, assigns(:prev_issue_id)
1206 assert_equal 5, assigns(:next_issue_id)
1206 assert_equal 5, assigns(:next_issue_id)
1207
1207
1208 count = Issue.open.visible.count
1208 count = Issue.open.visible.count
1209
1209
1210 assert_select 'div.next-prev-links' do
1210 assert_select 'div.next-prev-links' do
1211 assert_select 'a[href=/issues/2]', :text => /Previous/
1211 assert_select 'a[href=/issues/2]', :text => /Previous/
1212 assert_select 'a[href=/issues/5]', :text => /Next/
1212 assert_select 'a[href=/issues/5]', :text => /Next/
1213 assert_select 'span.position', :text => "3 of #{count}"
1213 assert_select 'span.position', :text => "3 of #{count}"
1214 end
1214 end
1215 end
1215 end
1216
1216
1217 def test_show_should_display_prev_next_links_with_saved_query_in_session
1217 def test_show_should_display_prev_next_links_with_saved_query_in_session
1218 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1,
1218 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1,
1219 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1219 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1220 :sort_criteria => [['id', 'asc']])
1220 :sort_criteria => [['id', 'asc']])
1221 @request.session[:query] = {:id => query.id, :project_id => nil}
1221 @request.session[:query] = {:id => query.id, :project_id => nil}
1222
1222
1223 get :show, :id => 11
1223 get :show, :id => 11
1224
1224
1225 assert_response :success
1225 assert_response :success
1226 assert_equal query, assigns(:query)
1226 assert_equal query, assigns(:query)
1227 # Previous and next issues for all projects
1227 # Previous and next issues for all projects
1228 assert_equal 8, assigns(:prev_issue_id)
1228 assert_equal 8, assigns(:prev_issue_id)
1229 assert_equal 12, assigns(:next_issue_id)
1229 assert_equal 12, assigns(:next_issue_id)
1230
1230
1231 assert_select 'div.next-prev-links' do
1231 assert_select 'div.next-prev-links' do
1232 assert_select 'a[href=/issues/8]', :text => /Previous/
1232 assert_select 'a[href=/issues/8]', :text => /Previous/
1233 assert_select 'a[href=/issues/12]', :text => /Next/
1233 assert_select 'a[href=/issues/12]', :text => /Next/
1234 end
1234 end
1235 end
1235 end
1236
1236
1237 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1237 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1238 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1238 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1239
1239
1240 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1240 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1241 @request.session['issues_index_sort'] = assoc_sort
1241 @request.session['issues_index_sort'] = assoc_sort
1242
1242
1243 get :show, :id => 3
1243 get :show, :id => 3
1244 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1244 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1245
1245
1246 assert_select 'div.next-prev-links' do
1246 assert_select 'div.next-prev-links' do
1247 assert_select 'a', :text => /(Previous|Next)/
1247 assert_select 'a', :text => /(Previous|Next)/
1248 end
1248 end
1249 end
1249 end
1250 end
1250 end
1251
1251
1252 def test_show_should_display_prev_next_links_with_project_query_in_session
1252 def test_show_should_display_prev_next_links_with_project_query_in_session
1253 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1253 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1254 @request.session['issues_index_sort'] = 'id'
1254 @request.session['issues_index_sort'] = 'id'
1255
1255
1256 with_settings :display_subprojects_issues => '0' do
1256 with_settings :display_subprojects_issues => '0' do
1257 get :show, :id => 3
1257 get :show, :id => 3
1258 end
1258 end
1259
1259
1260 assert_response :success
1260 assert_response :success
1261 # Previous and next issues inside project
1261 # Previous and next issues inside project
1262 assert_equal 2, assigns(:prev_issue_id)
1262 assert_equal 2, assigns(:prev_issue_id)
1263 assert_equal 7, assigns(:next_issue_id)
1263 assert_equal 7, assigns(:next_issue_id)
1264
1264
1265 assert_select 'div.next-prev-links' do
1265 assert_select 'div.next-prev-links' do
1266 assert_select 'a[href=/issues/2]', :text => /Previous/
1266 assert_select 'a[href=/issues/2]', :text => /Previous/
1267 assert_select 'a[href=/issues/7]', :text => /Next/
1267 assert_select 'a[href=/issues/7]', :text => /Next/
1268 end
1268 end
1269 end
1269 end
1270
1270
1271 def test_show_should_not_display_prev_link_for_first_issue
1271 def test_show_should_not_display_prev_link_for_first_issue
1272 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1272 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1273 @request.session['issues_index_sort'] = 'id'
1273 @request.session['issues_index_sort'] = 'id'
1274
1274
1275 with_settings :display_subprojects_issues => '0' do
1275 with_settings :display_subprojects_issues => '0' do
1276 get :show, :id => 1
1276 get :show, :id => 1
1277 end
1277 end
1278
1278
1279 assert_response :success
1279 assert_response :success
1280 assert_nil assigns(:prev_issue_id)
1280 assert_nil assigns(:prev_issue_id)
1281 assert_equal 2, assigns(:next_issue_id)
1281 assert_equal 2, assigns(:next_issue_id)
1282
1282
1283 assert_select 'div.next-prev-links' do
1283 assert_select 'div.next-prev-links' do
1284 assert_select 'a', :text => /Previous/, :count => 0
1284 assert_select 'a', :text => /Previous/, :count => 0
1285 assert_select 'a[href=/issues/2]', :text => /Next/
1285 assert_select 'a[href=/issues/2]', :text => /Next/
1286 end
1286 end
1287 end
1287 end
1288
1288
1289 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1289 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1290 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1290 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1291 @request.session['issues_index_sort'] = 'id'
1291 @request.session['issues_index_sort'] = 'id'
1292
1292
1293 get :show, :id => 1
1293 get :show, :id => 1
1294
1294
1295 assert_response :success
1295 assert_response :success
1296 assert_nil assigns(:prev_issue_id)
1296 assert_nil assigns(:prev_issue_id)
1297 assert_nil assigns(:next_issue_id)
1297 assert_nil assigns(:next_issue_id)
1298
1298
1299 assert_select 'a', :text => /Previous/, :count => 0
1299 assert_select 'a', :text => /Previous/, :count => 0
1300 assert_select 'a', :text => /Next/, :count => 0
1300 assert_select 'a', :text => /Next/, :count => 0
1301 end
1301 end
1302
1302
1303 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1303 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1304 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1304 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1305 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1305 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1306 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1306 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1307 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1307 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1308 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1308 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1309
1309
1310 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1, :filters => {},
1310 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1, :filters => {},
1311 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1311 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1312 @request.session[:query] = {:id => query.id, :project_id => nil}
1312 @request.session[:query] = {:id => query.id, :project_id => nil}
1313
1313
1314 get :show, :id => 3
1314 get :show, :id => 3
1315 assert_response :success
1315 assert_response :success
1316
1316
1317 assert_equal 2, assigns(:prev_issue_id)
1317 assert_equal 2, assigns(:prev_issue_id)
1318 assert_equal 1, assigns(:next_issue_id)
1318 assert_equal 1, assigns(:next_issue_id)
1319
1319
1320 assert_select 'div.next-prev-links' do
1320 assert_select 'div.next-prev-links' do
1321 assert_select 'a[href=/issues/2]', :text => /Previous/
1321 assert_select 'a[href=/issues/2]', :text => /Previous/
1322 assert_select 'a[href=/issues/1]', :text => /Next/
1322 assert_select 'a[href=/issues/1]', :text => /Next/
1323 end
1323 end
1324 end
1324 end
1325
1325
1326 def test_show_should_display_link_to_the_assignee
1326 def test_show_should_display_link_to_the_assignee
1327 get :show, :id => 2
1327 get :show, :id => 2
1328 assert_response :success
1328 assert_response :success
1329 assert_select '.assigned-to' do
1329 assert_select '.assigned-to' do
1330 assert_select 'a[href=/users/3]'
1330 assert_select 'a[href=/users/3]'
1331 end
1331 end
1332 end
1332 end
1333
1333
1334 def test_show_should_display_visible_changesets_from_other_projects
1334 def test_show_should_display_visible_changesets_from_other_projects
1335 project = Project.find(2)
1335 project = Project.find(2)
1336 issue = project.issues.first
1336 issue = project.issues.first
1337 issue.changeset_ids = [102]
1337 issue.changeset_ids = [102]
1338 issue.save!
1338 issue.save!
1339 # changesets from other projects should be displayed even if repository
1339 # changesets from other projects should be displayed even if repository
1340 # is disabled on issue's project
1340 # is disabled on issue's project
1341 project.disable_module! :repository
1341 project.disable_module! :repository
1342
1342
1343 @request.session[:user_id] = 2
1343 @request.session[:user_id] = 2
1344 get :show, :id => issue.id
1344 get :show, :id => issue.id
1345
1345
1346 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1346 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1347 end
1347 end
1348
1348
1349 def test_show_should_display_watchers
1349 def test_show_should_display_watchers
1350 @request.session[:user_id] = 2
1350 @request.session[:user_id] = 2
1351 Issue.find(1).add_watcher User.find(2)
1351 Issue.find(1).add_watcher User.find(2)
1352
1352
1353 get :show, :id => 1
1353 get :show, :id => 1
1354 assert_select 'div#watchers ul' do
1354 assert_select 'div#watchers ul' do
1355 assert_select 'li' do
1355 assert_select 'li' do
1356 assert_select 'a[href=/users/2]'
1356 assert_select 'a[href=/users/2]'
1357 assert_select 'a img[alt=Delete]'
1357 assert_select 'a img[alt=Delete]'
1358 end
1358 end
1359 end
1359 end
1360 end
1360 end
1361
1361
1362 def test_show_should_display_watchers_with_gravatars
1362 def test_show_should_display_watchers_with_gravatars
1363 @request.session[:user_id] = 2
1363 @request.session[:user_id] = 2
1364 Issue.find(1).add_watcher User.find(2)
1364 Issue.find(1).add_watcher User.find(2)
1365
1365
1366 with_settings :gravatar_enabled => '1' do
1366 with_settings :gravatar_enabled => '1' do
1367 get :show, :id => 1
1367 get :show, :id => 1
1368 end
1368 end
1369
1369
1370 assert_select 'div#watchers ul' do
1370 assert_select 'div#watchers ul' do
1371 assert_select 'li' do
1371 assert_select 'li' do
1372 assert_select 'img.gravatar'
1372 assert_select 'img.gravatar'
1373 assert_select 'a[href=/users/2]'
1373 assert_select 'a[href=/users/2]'
1374 assert_select 'a img[alt=Delete]'
1374 assert_select 'a img[alt=Delete]'
1375 end
1375 end
1376 end
1376 end
1377 end
1377 end
1378
1378
1379 def test_show_with_thumbnails_enabled_should_display_thumbnails
1379 def test_show_with_thumbnails_enabled_should_display_thumbnails
1380 @request.session[:user_id] = 2
1380 @request.session[:user_id] = 2
1381
1381
1382 with_settings :thumbnails_enabled => '1' do
1382 with_settings :thumbnails_enabled => '1' do
1383 get :show, :id => 14
1383 get :show, :id => 14
1384 assert_response :success
1384 assert_response :success
1385 end
1385 end
1386
1386
1387 assert_select 'div.thumbnails' do
1387 assert_select 'div.thumbnails' do
1388 assert_select 'a[href=/attachments/16/testfile.png]' do
1388 assert_select 'a[href=/attachments/16/testfile.png]' do
1389 assert_select 'img[src=/attachments/thumbnail/16]'
1389 assert_select 'img[src=/attachments/thumbnail/16]'
1390 end
1390 end
1391 end
1391 end
1392 end
1392 end
1393
1393
1394 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1394 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1395 @request.session[:user_id] = 2
1395 @request.session[:user_id] = 2
1396
1396
1397 with_settings :thumbnails_enabled => '0' do
1397 with_settings :thumbnails_enabled => '0' do
1398 get :show, :id => 14
1398 get :show, :id => 14
1399 assert_response :success
1399 assert_response :success
1400 end
1400 end
1401
1401
1402 assert_select 'div.thumbnails', 0
1402 assert_select 'div.thumbnails', 0
1403 end
1403 end
1404
1404
1405 def test_show_with_multi_custom_field
1405 def test_show_with_multi_custom_field
1406 field = CustomField.find(1)
1406 field = CustomField.find(1)
1407 field.update_attribute :multiple, true
1407 field.update_attribute :multiple, true
1408 issue = Issue.find(1)
1408 issue = Issue.find(1)
1409 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1409 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1410 issue.save!
1410 issue.save!
1411
1411
1412 get :show, :id => 1
1412 get :show, :id => 1
1413 assert_response :success
1413 assert_response :success
1414
1414
1415 assert_select 'td', :text => 'MySQL, Oracle'
1415 assert_select 'td', :text => 'MySQL, Oracle'
1416 end
1416 end
1417
1417
1418 def test_show_with_multi_user_custom_field
1418 def test_show_with_multi_user_custom_field
1419 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1419 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1420 :tracker_ids => [1], :is_for_all => true)
1420 :tracker_ids => [1], :is_for_all => true)
1421 issue = Issue.find(1)
1421 issue = Issue.find(1)
1422 issue.custom_field_values = {field.id => ['2', '3']}
1422 issue.custom_field_values = {field.id => ['2', '3']}
1423 issue.save!
1423 issue.save!
1424
1424
1425 get :show, :id => 1
1425 get :show, :id => 1
1426 assert_response :success
1426 assert_response :success
1427
1427
1428 assert_select "td.cf_#{field.id}", :text => 'Dave Lopper, John Smith' do
1428 assert_select "td.cf_#{field.id}", :text => 'Dave Lopper, John Smith' do
1429 assert_select 'a', :text => 'Dave Lopper'
1429 assert_select 'a', :text => 'Dave Lopper'
1430 assert_select 'a', :text => 'John Smith'
1430 assert_select 'a', :text => 'John Smith'
1431 end
1431 end
1432 end
1432 end
1433
1433
1434 def test_show_should_display_private_notes_with_permission_only
1434 def test_show_should_display_private_notes_with_permission_only
1435 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1435 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1436 @request.session[:user_id] = 2
1436 @request.session[:user_id] = 2
1437
1437
1438 get :show, :id => 2
1438 get :show, :id => 2
1439 assert_response :success
1439 assert_response :success
1440 assert_include journal, assigns(:journals)
1440 assert_include journal, assigns(:journals)
1441
1441
1442 Role.find(1).remove_permission! :view_private_notes
1442 Role.find(1).remove_permission! :view_private_notes
1443 get :show, :id => 2
1443 get :show, :id => 2
1444 assert_response :success
1444 assert_response :success
1445 assert_not_include journal, assigns(:journals)
1445 assert_not_include journal, assigns(:journals)
1446 end
1446 end
1447
1447
1448 def test_show_atom
1448 def test_show_atom
1449 get :show, :id => 2, :format => 'atom'
1449 get :show, :id => 2, :format => 'atom'
1450 assert_response :success
1450 assert_response :success
1451 assert_template 'journals/index'
1451 assert_template 'journals/index'
1452 # Inline image
1452 # Inline image
1453 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1453 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1454 end
1454 end
1455
1455
1456 def test_show_export_to_pdf
1456 def test_show_export_to_pdf
1457 issue = Issue.find(3)
1457 issue = Issue.find(3)
1458 assert issue.relations.select{|r| r.other_issue(issue).visible?}.present?
1458 assert issue.relations.select{|r| r.other_issue(issue).visible?}.present?
1459 get :show, :id => 3, :format => 'pdf'
1459 get :show, :id => 3, :format => 'pdf'
1460 assert_response :success
1460 assert_response :success
1461 assert_equal 'application/pdf', @response.content_type
1461 assert_equal 'application/pdf', @response.content_type
1462 assert @response.body.starts_with?('%PDF')
1462 assert @response.body.starts_with?('%PDF')
1463 assert_not_nil assigns(:issue)
1463 assert_not_nil assigns(:issue)
1464 end
1464 end
1465
1465
1466 def test_export_to_pdf_with_utf8_u_fffd
1466 def test_export_to_pdf_with_utf8_u_fffd
1467 # U+FFFD
1467 # U+FFFD
1468 s = "\xef\xbf\xbd"
1468 s = "\xef\xbf\xbd"
1469 s.force_encoding('UTF-8') if s.respond_to?(:force_encoding)
1469 s.force_encoding('UTF-8') if s.respond_to?(:force_encoding)
1470 issue = Issue.generate!(:subject => s)
1470 issue = Issue.generate!(:subject => s)
1471 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
1471 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
1472 with_settings :default_language => lang do
1472 with_settings :default_language => lang do
1473 get :show, :id => issue.id, :format => 'pdf'
1473 get :show, :id => issue.id, :format => 'pdf'
1474 assert_response :success
1474 assert_response :success
1475 assert_equal 'application/pdf', @response.content_type
1475 assert_equal 'application/pdf', @response.content_type
1476 assert @response.body.starts_with?('%PDF')
1476 assert @response.body.starts_with?('%PDF')
1477 assert_not_nil assigns(:issue)
1477 assert_not_nil assigns(:issue)
1478 end
1478 end
1479 end
1479 end
1480 end
1480 end
1481
1481
1482 def test_show_export_to_pdf_with_ancestors
1482 def test_show_export_to_pdf_with_ancestors
1483 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1483 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1484
1484
1485 get :show, :id => issue.id, :format => 'pdf'
1485 get :show, :id => issue.id, :format => 'pdf'
1486 assert_response :success
1486 assert_response :success
1487 assert_equal 'application/pdf', @response.content_type
1487 assert_equal 'application/pdf', @response.content_type
1488 assert @response.body.starts_with?('%PDF')
1488 assert @response.body.starts_with?('%PDF')
1489 end
1489 end
1490
1490
1491 def test_show_export_to_pdf_with_descendants
1491 def test_show_export_to_pdf_with_descendants
1492 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1492 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1493 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1493 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1494 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1494 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1495
1495
1496 get :show, :id => 1, :format => 'pdf'
1496 get :show, :id => 1, :format => 'pdf'
1497 assert_response :success
1497 assert_response :success
1498 assert_equal 'application/pdf', @response.content_type
1498 assert_equal 'application/pdf', @response.content_type
1499 assert @response.body.starts_with?('%PDF')
1499 assert @response.body.starts_with?('%PDF')
1500 end
1500 end
1501
1501
1502 def test_show_export_to_pdf_with_journals
1502 def test_show_export_to_pdf_with_journals
1503 get :show, :id => 1, :format => 'pdf'
1503 get :show, :id => 1, :format => 'pdf'
1504 assert_response :success
1504 assert_response :success
1505 assert_equal 'application/pdf', @response.content_type
1505 assert_equal 'application/pdf', @response.content_type
1506 assert @response.body.starts_with?('%PDF')
1506 assert @response.body.starts_with?('%PDF')
1507 end
1507 end
1508
1508
1509 def test_show_export_to_pdf_with_changesets
1509 def test_show_export_to_pdf_with_changesets
1510 [[100], [100, 101], [100, 101, 102]].each do |cs|
1510 [[100], [100, 101], [100, 101, 102]].each do |cs|
1511 issue1 = Issue.find(3)
1511 issue1 = Issue.find(3)
1512 issue1.changesets = Changeset.find(cs)
1512 issue1.changesets = Changeset.find(cs)
1513 issue1.save!
1513 issue1.save!
1514 issue = Issue.find(3)
1514 issue = Issue.find(3)
1515 assert_equal issue.changesets.count, cs.size
1515 assert_equal issue.changesets.count, cs.size
1516 get :show, :id => 3, :format => 'pdf'
1516 get :show, :id => 3, :format => 'pdf'
1517 assert_response :success
1517 assert_response :success
1518 assert_equal 'application/pdf', @response.content_type
1518 assert_equal 'application/pdf', @response.content_type
1519 assert @response.body.starts_with?('%PDF')
1519 assert @response.body.starts_with?('%PDF')
1520 end
1520 end
1521 end
1521 end
1522
1522
1523 def test_show_invalid_should_respond_with_404
1523 def test_show_invalid_should_respond_with_404
1524 get :show, :id => 999
1524 get :show, :id => 999
1525 assert_response 404
1525 assert_response 404
1526 end
1526 end
1527
1527
1528 def test_get_new
1528 def test_get_new
1529 @request.session[:user_id] = 2
1529 @request.session[:user_id] = 2
1530 get :new, :project_id => 1, :tracker_id => 1
1530 get :new, :project_id => 1, :tracker_id => 1
1531 assert_response :success
1531 assert_response :success
1532 assert_template 'new'
1532 assert_template 'new'
1533
1533
1534 assert_select 'form#issue-form' do
1534 assert_select 'form#issue-form' do
1535 assert_select 'input[name=?]', 'issue[is_private]'
1535 assert_select 'input[name=?]', 'issue[is_private]'
1536 assert_select 'select[name=?]', 'issue[project_id]', 0
1536 assert_select 'select[name=?]', 'issue[project_id]', 0
1537 assert_select 'select[name=?]', 'issue[tracker_id]'
1537 assert_select 'select[name=?]', 'issue[tracker_id]'
1538 assert_select 'input[name=?]', 'issue[subject]'
1538 assert_select 'input[name=?]', 'issue[subject]'
1539 assert_select 'textarea[name=?]', 'issue[description]'
1539 assert_select 'textarea[name=?]', 'issue[description]'
1540 assert_select 'select[name=?]', 'issue[status_id]'
1540 assert_select 'select[name=?]', 'issue[status_id]'
1541 assert_select 'select[name=?]', 'issue[priority_id]'
1541 assert_select 'select[name=?]', 'issue[priority_id]'
1542 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1542 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1543 assert_select 'select[name=?]', 'issue[category_id]'
1543 assert_select 'select[name=?]', 'issue[category_id]'
1544 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1544 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1545 assert_select 'input[name=?]', 'issue[parent_issue_id]'
1545 assert_select 'input[name=?]', 'issue[parent_issue_id]'
1546 assert_select 'input[name=?]', 'issue[start_date]'
1546 assert_select 'input[name=?]', 'issue[start_date]'
1547 assert_select 'input[name=?]', 'issue[due_date]'
1547 assert_select 'input[name=?]', 'issue[due_date]'
1548 assert_select 'select[name=?]', 'issue[done_ratio]'
1548 assert_select 'select[name=?]', 'issue[done_ratio]'
1549 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1549 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1550 assert_select 'input[name=?]', 'issue[watcher_user_ids][]'
1550 assert_select 'input[name=?]', 'issue[watcher_user_ids][]'
1551 end
1551 end
1552
1552
1553 # Be sure we don't display inactive IssuePriorities
1553 # Be sure we don't display inactive IssuePriorities
1554 assert ! IssuePriority.find(15).active?
1554 assert ! IssuePriority.find(15).active?
1555 assert_select 'select[name=?]', 'issue[priority_id]' do
1555 assert_select 'select[name=?]', 'issue[priority_id]' do
1556 assert_select 'option[value=15]', 0
1556 assert_select 'option[value=15]', 0
1557 end
1557 end
1558 end
1558 end
1559
1559
1560 def test_get_new_with_minimal_permissions
1560 def test_get_new_with_minimal_permissions
1561 Role.find(1).update_attribute :permissions, [:add_issues]
1561 Role.find(1).update_attribute :permissions, [:add_issues]
1562 WorkflowTransition.delete_all :role_id => 1
1562 WorkflowTransition.delete_all :role_id => 1
1563
1563
1564 @request.session[:user_id] = 2
1564 @request.session[:user_id] = 2
1565 get :new, :project_id => 1, :tracker_id => 1
1565 get :new, :project_id => 1, :tracker_id => 1
1566 assert_response :success
1566 assert_response :success
1567 assert_template 'new'
1567 assert_template 'new'
1568
1568
1569 assert_select 'form#issue-form' do
1569 assert_select 'form#issue-form' do
1570 assert_select 'input[name=?]', 'issue[is_private]', 0
1570 assert_select 'input[name=?]', 'issue[is_private]', 0
1571 assert_select 'select[name=?]', 'issue[project_id]', 0
1571 assert_select 'select[name=?]', 'issue[project_id]', 0
1572 assert_select 'select[name=?]', 'issue[tracker_id]'
1572 assert_select 'select[name=?]', 'issue[tracker_id]'
1573 assert_select 'input[name=?]', 'issue[subject]'
1573 assert_select 'input[name=?]', 'issue[subject]'
1574 assert_select 'textarea[name=?]', 'issue[description]'
1574 assert_select 'textarea[name=?]', 'issue[description]'
1575 assert_select 'select[name=?]', 'issue[status_id]'
1575 assert_select 'select[name=?]', 'issue[status_id]'
1576 assert_select 'select[name=?]', 'issue[priority_id]'
1576 assert_select 'select[name=?]', 'issue[priority_id]'
1577 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1577 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1578 assert_select 'select[name=?]', 'issue[category_id]'
1578 assert_select 'select[name=?]', 'issue[category_id]'
1579 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1579 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1580 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1580 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1581 assert_select 'input[name=?]', 'issue[start_date]'
1581 assert_select 'input[name=?]', 'issue[start_date]'
1582 assert_select 'input[name=?]', 'issue[due_date]'
1582 assert_select 'input[name=?]', 'issue[due_date]'
1583 assert_select 'select[name=?]', 'issue[done_ratio]'
1583 assert_select 'select[name=?]', 'issue[done_ratio]'
1584 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1584 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1585 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1585 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1586 end
1586 end
1587 end
1587 end
1588
1588
1589 def test_get_new_with_list_custom_field
1589 def test_get_new_with_list_custom_field
1590 @request.session[:user_id] = 2
1590 @request.session[:user_id] = 2
1591 get :new, :project_id => 1, :tracker_id => 1
1591 get :new, :project_id => 1, :tracker_id => 1
1592 assert_response :success
1592 assert_response :success
1593 assert_template 'new'
1593 assert_template 'new'
1594
1594
1595 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1595 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1596 assert_select 'option', 4
1596 assert_select 'option', 4
1597 assert_select 'option[value=MySQL]', :text => 'MySQL'
1597 assert_select 'option[value=MySQL]', :text => 'MySQL'
1598 end
1598 end
1599 end
1599 end
1600
1600
1601 def test_get_new_with_multi_custom_field
1601 def test_get_new_with_multi_custom_field
1602 field = IssueCustomField.find(1)
1602 field = IssueCustomField.find(1)
1603 field.update_attribute :multiple, true
1603 field.update_attribute :multiple, true
1604
1604
1605 @request.session[:user_id] = 2
1605 @request.session[:user_id] = 2
1606 get :new, :project_id => 1, :tracker_id => 1
1606 get :new, :project_id => 1, :tracker_id => 1
1607 assert_response :success
1607 assert_response :success
1608 assert_template 'new'
1608 assert_template 'new'
1609
1609
1610 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1610 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1611 assert_select 'option', 3
1611 assert_select 'option', 3
1612 assert_select 'option[value=MySQL]', :text => 'MySQL'
1612 assert_select 'option[value=MySQL]', :text => 'MySQL'
1613 end
1613 end
1614 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1614 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1615 end
1615 end
1616
1616
1617 def test_get_new_with_multi_user_custom_field
1617 def test_get_new_with_multi_user_custom_field
1618 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1618 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1619 :tracker_ids => [1], :is_for_all => true)
1619 :tracker_ids => [1], :is_for_all => true)
1620
1620
1621 @request.session[:user_id] = 2
1621 @request.session[:user_id] = 2
1622 get :new, :project_id => 1, :tracker_id => 1
1622 get :new, :project_id => 1, :tracker_id => 1
1623 assert_response :success
1623 assert_response :success
1624 assert_template 'new'
1624 assert_template 'new'
1625
1625
1626 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1626 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1627 assert_select 'option', Project.find(1).users.count
1627 assert_select 'option', Project.find(1).users.count
1628 assert_select 'option[value=2]', :text => 'John Smith'
1628 assert_select 'option[value=2]', :text => 'John Smith'
1629 end
1629 end
1630 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1630 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1631 end
1631 end
1632
1632
1633 def test_get_new_with_date_custom_field
1633 def test_get_new_with_date_custom_field
1634 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1634 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1635
1635
1636 @request.session[:user_id] = 2
1636 @request.session[:user_id] = 2
1637 get :new, :project_id => 1, :tracker_id => 1
1637 get :new, :project_id => 1, :tracker_id => 1
1638 assert_response :success
1638 assert_response :success
1639
1639
1640 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1640 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1641 end
1641 end
1642
1642
1643 def test_get_new_with_text_custom_field
1643 def test_get_new_with_text_custom_field
1644 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1644 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1645
1645
1646 @request.session[:user_id] = 2
1646 @request.session[:user_id] = 2
1647 get :new, :project_id => 1, :tracker_id => 1
1647 get :new, :project_id => 1, :tracker_id => 1
1648 assert_response :success
1648 assert_response :success
1649
1649
1650 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1650 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1651 end
1651 end
1652
1652
1653 def test_get_new_without_default_start_date_is_creation_date
1653 def test_get_new_without_default_start_date_is_creation_date
1654 with_settings :default_issue_start_date_to_creation_date => 0 do
1654 with_settings :default_issue_start_date_to_creation_date => 0 do
1655 @request.session[:user_id] = 2
1655 @request.session[:user_id] = 2
1656 get :new, :project_id => 1, :tracker_id => 1
1656 get :new, :project_id => 1, :tracker_id => 1
1657 assert_response :success
1657 assert_response :success
1658 assert_template 'new'
1658 assert_template 'new'
1659 assert_select 'input[name=?]', 'issue[start_date]'
1659 assert_select 'input[name=?]', 'issue[start_date]'
1660 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1660 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1661 end
1661 end
1662 end
1662 end
1663
1663
1664 def test_get_new_with_default_start_date_is_creation_date
1664 def test_get_new_with_default_start_date_is_creation_date
1665 with_settings :default_issue_start_date_to_creation_date => 1 do
1665 with_settings :default_issue_start_date_to_creation_date => 1 do
1666 @request.session[:user_id] = 2
1666 @request.session[:user_id] = 2
1667 get :new, :project_id => 1, :tracker_id => 1
1667 get :new, :project_id => 1, :tracker_id => 1
1668 assert_response :success
1668 assert_response :success
1669 assert_template 'new'
1669 assert_template 'new'
1670 assert_select 'input[name=?][value=?]', 'issue[start_date]',
1670 assert_select 'input[name=?][value=?]', 'issue[start_date]',
1671 Date.today.to_s
1671 Date.today.to_s
1672 end
1672 end
1673 end
1673 end
1674
1674
1675 def test_get_new_form_should_allow_attachment_upload
1675 def test_get_new_form_should_allow_attachment_upload
1676 @request.session[:user_id] = 2
1676 @request.session[:user_id] = 2
1677 get :new, :project_id => 1, :tracker_id => 1
1677 get :new, :project_id => 1, :tracker_id => 1
1678
1678
1679 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1679 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1680 assert_select 'input[name=?][type=file]', 'attachments[dummy][file]'
1680 assert_select 'input[name=?][type=file]', 'attachments[dummy][file]'
1681 end
1681 end
1682 end
1682 end
1683
1683
1684 def test_get_new_should_prefill_the_form_from_params
1684 def test_get_new_should_prefill_the_form_from_params
1685 @request.session[:user_id] = 2
1685 @request.session[:user_id] = 2
1686 get :new, :project_id => 1,
1686 get :new, :project_id => 1,
1687 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1687 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1688
1688
1689 issue = assigns(:issue)
1689 issue = assigns(:issue)
1690 assert_equal 3, issue.tracker_id
1690 assert_equal 3, issue.tracker_id
1691 assert_equal 'Prefilled', issue.description
1691 assert_equal 'Prefilled', issue.description
1692 assert_equal 'Custom field value', issue.custom_field_value(2)
1692 assert_equal 'Custom field value', issue.custom_field_value(2)
1693
1693
1694 assert_select 'select[name=?]', 'issue[tracker_id]' do
1694 assert_select 'select[name=?]', 'issue[tracker_id]' do
1695 assert_select 'option[value=3][selected=selected]'
1695 assert_select 'option[value=3][selected=selected]'
1696 end
1696 end
1697 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1697 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1698 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1698 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1699 end
1699 end
1700
1700
1701 def test_get_new_should_mark_required_fields
1701 def test_get_new_should_mark_required_fields
1702 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1702 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1703 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1703 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1704 WorkflowPermission.delete_all
1704 WorkflowPermission.delete_all
1705 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1705 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1706 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1706 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1707 @request.session[:user_id] = 2
1707 @request.session[:user_id] = 2
1708
1708
1709 get :new, :project_id => 1
1709 get :new, :project_id => 1
1710 assert_response :success
1710 assert_response :success
1711 assert_template 'new'
1711 assert_template 'new'
1712
1712
1713 assert_select 'label[for=issue_start_date]' do
1713 assert_select 'label[for=issue_start_date]' do
1714 assert_select 'span[class=required]', 0
1714 assert_select 'span[class=required]', 0
1715 end
1715 end
1716 assert_select 'label[for=issue_due_date]' do
1716 assert_select 'label[for=issue_due_date]' do
1717 assert_select 'span[class=required]'
1717 assert_select 'span[class=required]'
1718 end
1718 end
1719 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1719 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1720 assert_select 'span[class=required]', 0
1720 assert_select 'span[class=required]', 0
1721 end
1721 end
1722 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1722 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1723 assert_select 'span[class=required]'
1723 assert_select 'span[class=required]'
1724 end
1724 end
1725 end
1725 end
1726
1726
1727 def test_get_new_should_not_display_readonly_fields
1727 def test_get_new_should_not_display_readonly_fields
1728 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1728 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1729 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1729 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1730 WorkflowPermission.delete_all
1730 WorkflowPermission.delete_all
1731 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1731 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1732 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1732 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1733 @request.session[:user_id] = 2
1733 @request.session[:user_id] = 2
1734
1734
1735 get :new, :project_id => 1
1735 get :new, :project_id => 1
1736 assert_response :success
1736 assert_response :success
1737 assert_template 'new'
1737 assert_template 'new'
1738
1738
1739 assert_select 'input[name=?]', 'issue[start_date]'
1739 assert_select 'input[name=?]', 'issue[start_date]'
1740 assert_select 'input[name=?]', 'issue[due_date]', 0
1740 assert_select 'input[name=?]', 'issue[due_date]', 0
1741 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1741 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1742 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1742 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1743 end
1743 end
1744
1744
1745 def test_get_new_without_tracker_id
1745 def test_get_new_without_tracker_id
1746 @request.session[:user_id] = 2
1746 @request.session[:user_id] = 2
1747 get :new, :project_id => 1
1747 get :new, :project_id => 1
1748 assert_response :success
1748 assert_response :success
1749 assert_template 'new'
1749 assert_template 'new'
1750
1750
1751 issue = assigns(:issue)
1751 issue = assigns(:issue)
1752 assert_not_nil issue
1752 assert_not_nil issue
1753 assert_equal Project.find(1).trackers.first, issue.tracker
1753 assert_equal Project.find(1).trackers.first, issue.tracker
1754 end
1754 end
1755
1755
1756 def test_get_new_with_no_default_status_should_display_an_error
1756 def test_get_new_with_no_default_status_should_display_an_error
1757 @request.session[:user_id] = 2
1757 @request.session[:user_id] = 2
1758 IssueStatus.delete_all
1758 IssueStatus.delete_all
1759
1759
1760 get :new, :project_id => 1
1760 get :new, :project_id => 1
1761 assert_response 500
1761 assert_response 500
1762 assert_error_tag :content => /No default issue/
1762 assert_error_tag :content => /No default issue/
1763 end
1763 end
1764
1764
1765 def test_get_new_with_no_tracker_should_display_an_error
1765 def test_get_new_with_no_tracker_should_display_an_error
1766 @request.session[:user_id] = 2
1766 @request.session[:user_id] = 2
1767 Tracker.delete_all
1767 Tracker.delete_all
1768
1768
1769 get :new, :project_id => 1
1769 get :new, :project_id => 1
1770 assert_response 500
1770 assert_response 500
1771 assert_error_tag :content => /No tracker/
1771 assert_error_tag :content => /No tracker/
1772 end
1772 end
1773
1773
1774 def test_update_form_for_new_issue
1774 def test_update_form_for_new_issue
1775 @request.session[:user_id] = 2
1775 @request.session[:user_id] = 2
1776 xhr :post, :update_form, :project_id => 1,
1776 xhr :post, :update_form, :project_id => 1,
1777 :issue => {:tracker_id => 2,
1777 :issue => {:tracker_id => 2,
1778 :subject => 'This is the test_new issue',
1778 :subject => 'This is the test_new issue',
1779 :description => 'This is the description',
1779 :description => 'This is the description',
1780 :priority_id => 5}
1780 :priority_id => 5}
1781 assert_response :success
1781 assert_response :success
1782 assert_template 'update_form'
1782 assert_template 'update_form'
1783 assert_template :partial => '_form'
1783 assert_template :partial => '_form'
1784 assert_equal 'text/javascript', response.content_type
1784 assert_equal 'text/javascript', response.content_type
1785
1785
1786 issue = assigns(:issue)
1786 issue = assigns(:issue)
1787 assert_kind_of Issue, issue
1787 assert_kind_of Issue, issue
1788 assert_equal 1, issue.project_id
1788 assert_equal 1, issue.project_id
1789 assert_equal 2, issue.tracker_id
1789 assert_equal 2, issue.tracker_id
1790 assert_equal 'This is the test_new issue', issue.subject
1790 assert_equal 'This is the test_new issue', issue.subject
1791 end
1791 end
1792
1792
1793 def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status
1793 def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status
1794 @request.session[:user_id] = 2
1794 @request.session[:user_id] = 2
1795 WorkflowTransition.delete_all
1795 WorkflowTransition.delete_all
1796 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1796 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1797 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1797 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1798 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1798 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1799
1799
1800 xhr :post, :update_form, :project_id => 1,
1800 xhr :post, :update_form, :project_id => 1,
1801 :issue => {:tracker_id => 1,
1801 :issue => {:tracker_id => 1,
1802 :status_id => 5,
1802 :status_id => 5,
1803 :subject => 'This is an issue'}
1803 :subject => 'This is an issue'}
1804
1804
1805 assert_equal 5, assigns(:issue).status_id
1805 assert_equal 5, assigns(:issue).status_id
1806 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1806 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1807 end
1807 end
1808
1808
1809 def test_post_create
1809 def test_post_create
1810 @request.session[:user_id] = 2
1810 @request.session[:user_id] = 2
1811 assert_difference 'Issue.count' do
1811 assert_difference 'Issue.count' do
1812 post :create, :project_id => 1,
1812 post :create, :project_id => 1,
1813 :issue => {:tracker_id => 3,
1813 :issue => {:tracker_id => 3,
1814 :status_id => 2,
1814 :status_id => 2,
1815 :subject => 'This is the test_new issue',
1815 :subject => 'This is the test_new issue',
1816 :description => 'This is the description',
1816 :description => 'This is the description',
1817 :priority_id => 5,
1817 :priority_id => 5,
1818 :start_date => '2010-11-07',
1818 :start_date => '2010-11-07',
1819 :estimated_hours => '',
1819 :estimated_hours => '',
1820 :custom_field_values => {'2' => 'Value for field 2'}}
1820 :custom_field_values => {'2' => 'Value for field 2'}}
1821 end
1821 end
1822 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1822 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1823
1823
1824 issue = Issue.find_by_subject('This is the test_new issue')
1824 issue = Issue.find_by_subject('This is the test_new issue')
1825 assert_not_nil issue
1825 assert_not_nil issue
1826 assert_equal 2, issue.author_id
1826 assert_equal 2, issue.author_id
1827 assert_equal 3, issue.tracker_id
1827 assert_equal 3, issue.tracker_id
1828 assert_equal 2, issue.status_id
1828 assert_equal 2, issue.status_id
1829 assert_equal Date.parse('2010-11-07'), issue.start_date
1829 assert_equal Date.parse('2010-11-07'), issue.start_date
1830 assert_nil issue.estimated_hours
1830 assert_nil issue.estimated_hours
1831 v = issue.custom_values.where(:custom_field_id => 2).first
1831 v = issue.custom_values.where(:custom_field_id => 2).first
1832 assert_not_nil v
1832 assert_not_nil v
1833 assert_equal 'Value for field 2', v.value
1833 assert_equal 'Value for field 2', v.value
1834 end
1834 end
1835
1835
1836 def test_post_new_with_group_assignment
1836 def test_post_new_with_group_assignment
1837 group = Group.find(11)
1837 group = Group.find(11)
1838 project = Project.find(1)
1838 project = Project.find(1)
1839 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1839 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1840
1840
1841 with_settings :issue_group_assignment => '1' do
1841 with_settings :issue_group_assignment => '1' do
1842 @request.session[:user_id] = 2
1842 @request.session[:user_id] = 2
1843 assert_difference 'Issue.count' do
1843 assert_difference 'Issue.count' do
1844 post :create, :project_id => project.id,
1844 post :create, :project_id => project.id,
1845 :issue => {:tracker_id => 3,
1845 :issue => {:tracker_id => 3,
1846 :status_id => 1,
1846 :status_id => 1,
1847 :subject => 'This is the test_new_with_group_assignment issue',
1847 :subject => 'This is the test_new_with_group_assignment issue',
1848 :assigned_to_id => group.id}
1848 :assigned_to_id => group.id}
1849 end
1849 end
1850 end
1850 end
1851 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1851 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1852
1852
1853 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1853 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1854 assert_not_nil issue
1854 assert_not_nil issue
1855 assert_equal group, issue.assigned_to
1855 assert_equal group, issue.assigned_to
1856 end
1856 end
1857
1857
1858 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1858 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1859 with_settings :default_issue_start_date_to_creation_date => 0 do
1859 with_settings :default_issue_start_date_to_creation_date => 0 do
1860 @request.session[:user_id] = 2
1860 @request.session[:user_id] = 2
1861 assert_difference 'Issue.count' do
1861 assert_difference 'Issue.count' do
1862 post :create, :project_id => 1,
1862 post :create, :project_id => 1,
1863 :issue => {:tracker_id => 3,
1863 :issue => {:tracker_id => 3,
1864 :status_id => 2,
1864 :status_id => 2,
1865 :subject => 'This is the test_new issue',
1865 :subject => 'This is the test_new issue',
1866 :description => 'This is the description',
1866 :description => 'This is the description',
1867 :priority_id => 5,
1867 :priority_id => 5,
1868 :estimated_hours => '',
1868 :estimated_hours => '',
1869 :custom_field_values => {'2' => 'Value for field 2'}}
1869 :custom_field_values => {'2' => 'Value for field 2'}}
1870 end
1870 end
1871 assert_redirected_to :controller => 'issues', :action => 'show',
1871 assert_redirected_to :controller => 'issues', :action => 'show',
1872 :id => Issue.last.id
1872 :id => Issue.last.id
1873 issue = Issue.find_by_subject('This is the test_new issue')
1873 issue = Issue.find_by_subject('This is the test_new issue')
1874 assert_not_nil issue
1874 assert_not_nil issue
1875 assert_nil issue.start_date
1875 assert_nil issue.start_date
1876 end
1876 end
1877 end
1877 end
1878
1878
1879 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1879 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1880 with_settings :default_issue_start_date_to_creation_date => 1 do
1880 with_settings :default_issue_start_date_to_creation_date => 1 do
1881 @request.session[:user_id] = 2
1881 @request.session[:user_id] = 2
1882 assert_difference 'Issue.count' do
1882 assert_difference 'Issue.count' do
1883 post :create, :project_id => 1,
1883 post :create, :project_id => 1,
1884 :issue => {:tracker_id => 3,
1884 :issue => {:tracker_id => 3,
1885 :status_id => 2,
1885 :status_id => 2,
1886 :subject => 'This is the test_new issue',
1886 :subject => 'This is the test_new issue',
1887 :description => 'This is the description',
1887 :description => 'This is the description',
1888 :priority_id => 5,
1888 :priority_id => 5,
1889 :estimated_hours => '',
1889 :estimated_hours => '',
1890 :custom_field_values => {'2' => 'Value for field 2'}}
1890 :custom_field_values => {'2' => 'Value for field 2'}}
1891 end
1891 end
1892 assert_redirected_to :controller => 'issues', :action => 'show',
1892 assert_redirected_to :controller => 'issues', :action => 'show',
1893 :id => Issue.last.id
1893 :id => Issue.last.id
1894 issue = Issue.find_by_subject('This is the test_new issue')
1894 issue = Issue.find_by_subject('This is the test_new issue')
1895 assert_not_nil issue
1895 assert_not_nil issue
1896 assert_equal Date.today, issue.start_date
1896 assert_equal Date.today, issue.start_date
1897 end
1897 end
1898 end
1898 end
1899
1899
1900 def test_post_create_and_continue
1900 def test_post_create_and_continue
1901 @request.session[:user_id] = 2
1901 @request.session[:user_id] = 2
1902 assert_difference 'Issue.count' do
1902 assert_difference 'Issue.count' do
1903 post :create, :project_id => 1,
1903 post :create, :project_id => 1,
1904 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1904 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1905 :continue => ''
1905 :continue => ''
1906 end
1906 end
1907
1907
1908 issue = Issue.order('id DESC').first
1908 issue = Issue.order('id DESC').first
1909 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1909 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1910 assert_not_nil flash[:notice], "flash was not set"
1910 assert_not_nil flash[:notice], "flash was not set"
1911 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"
1911 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"
1912 end
1912 end
1913
1913
1914 def test_post_create_without_custom_fields_param
1914 def test_post_create_without_custom_fields_param
1915 @request.session[:user_id] = 2
1915 @request.session[:user_id] = 2
1916 assert_difference 'Issue.count' do
1916 assert_difference 'Issue.count' do
1917 post :create, :project_id => 1,
1917 post :create, :project_id => 1,
1918 :issue => {:tracker_id => 1,
1918 :issue => {:tracker_id => 1,
1919 :subject => 'This is the test_new issue',
1919 :subject => 'This is the test_new issue',
1920 :description => 'This is the description',
1920 :description => 'This is the description',
1921 :priority_id => 5}
1921 :priority_id => 5}
1922 end
1922 end
1923 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1923 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1924 end
1924 end
1925
1925
1926 def test_post_create_with_multi_custom_field
1926 def test_post_create_with_multi_custom_field
1927 field = IssueCustomField.find_by_name('Database')
1927 field = IssueCustomField.find_by_name('Database')
1928 field.update_attribute(:multiple, true)
1928 field.update_attribute(:multiple, true)
1929
1929
1930 @request.session[:user_id] = 2
1930 @request.session[:user_id] = 2
1931 assert_difference 'Issue.count' do
1931 assert_difference 'Issue.count' do
1932 post :create, :project_id => 1,
1932 post :create, :project_id => 1,
1933 :issue => {:tracker_id => 1,
1933 :issue => {:tracker_id => 1,
1934 :subject => 'This is the test_new issue',
1934 :subject => 'This is the test_new issue',
1935 :description => 'This is the description',
1935 :description => 'This is the description',
1936 :priority_id => 5,
1936 :priority_id => 5,
1937 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1937 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1938 end
1938 end
1939 assert_response 302
1939 assert_response 302
1940 issue = Issue.order('id DESC').first
1940 issue = Issue.order('id DESC').first
1941 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1941 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1942 end
1942 end
1943
1943
1944 def test_post_create_with_empty_multi_custom_field
1944 def test_post_create_with_empty_multi_custom_field
1945 field = IssueCustomField.find_by_name('Database')
1945 field = IssueCustomField.find_by_name('Database')
1946 field.update_attribute(:multiple, true)
1946 field.update_attribute(:multiple, true)
1947
1947
1948 @request.session[:user_id] = 2
1948 @request.session[:user_id] = 2
1949 assert_difference 'Issue.count' do
1949 assert_difference 'Issue.count' do
1950 post :create, :project_id => 1,
1950 post :create, :project_id => 1,
1951 :issue => {:tracker_id => 1,
1951 :issue => {:tracker_id => 1,
1952 :subject => 'This is the test_new issue',
1952 :subject => 'This is the test_new issue',
1953 :description => 'This is the description',
1953 :description => 'This is the description',
1954 :priority_id => 5,
1954 :priority_id => 5,
1955 :custom_field_values => {'1' => ['']}}
1955 :custom_field_values => {'1' => ['']}}
1956 end
1956 end
1957 assert_response 302
1957 assert_response 302
1958 issue = Issue.order('id DESC').first
1958 issue = Issue.order('id DESC').first
1959 assert_equal [''], issue.custom_field_value(1).sort
1959 assert_equal [''], issue.custom_field_value(1).sort
1960 end
1960 end
1961
1961
1962 def test_post_create_with_multi_user_custom_field
1962 def test_post_create_with_multi_user_custom_field
1963 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1963 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1964 :tracker_ids => [1], :is_for_all => true)
1964 :tracker_ids => [1], :is_for_all => true)
1965
1965
1966 @request.session[:user_id] = 2
1966 @request.session[:user_id] = 2
1967 assert_difference 'Issue.count' do
1967 assert_difference 'Issue.count' do
1968 post :create, :project_id => 1,
1968 post :create, :project_id => 1,
1969 :issue => {:tracker_id => 1,
1969 :issue => {:tracker_id => 1,
1970 :subject => 'This is the test_new issue',
1970 :subject => 'This is the test_new issue',
1971 :description => 'This is the description',
1971 :description => 'This is the description',
1972 :priority_id => 5,
1972 :priority_id => 5,
1973 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1973 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1974 end
1974 end
1975 assert_response 302
1975 assert_response 302
1976 issue = Issue.order('id DESC').first
1976 issue = Issue.order('id DESC').first
1977 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1977 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1978 end
1978 end
1979
1979
1980 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1980 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1981 field = IssueCustomField.find_by_name('Database')
1981 field = IssueCustomField.find_by_name('Database')
1982 field.update_attribute(:is_required, true)
1982 field.update_attribute(:is_required, true)
1983
1983
1984 @request.session[:user_id] = 2
1984 @request.session[:user_id] = 2
1985 assert_no_difference 'Issue.count' do
1985 assert_no_difference 'Issue.count' do
1986 post :create, :project_id => 1,
1986 post :create, :project_id => 1,
1987 :issue => {:tracker_id => 1,
1987 :issue => {:tracker_id => 1,
1988 :subject => 'This is the test_new issue',
1988 :subject => 'This is the test_new issue',
1989 :description => 'This is the description',
1989 :description => 'This is the description',
1990 :priority_id => 5}
1990 :priority_id => 5}
1991 end
1991 end
1992 assert_response :success
1992 assert_response :success
1993 assert_template 'new'
1993 assert_template 'new'
1994 issue = assigns(:issue)
1994 issue = assigns(:issue)
1995 assert_not_nil issue
1995 assert_not_nil issue
1996 assert_error_tag :content => /Database #{ESCAPED_CANT} be blank/
1996 assert_error_tag :content => /Database #{ESCAPED_CANT} be blank/
1997 end
1997 end
1998
1998
1999 def test_create_should_validate_required_fields
1999 def test_create_should_validate_required_fields
2000 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2000 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2001 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2001 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2002 WorkflowPermission.delete_all
2002 WorkflowPermission.delete_all
2003 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
2003 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
2004 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
2004 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
2005 @request.session[:user_id] = 2
2005 @request.session[:user_id] = 2
2006
2006
2007 assert_no_difference 'Issue.count' do
2007 assert_no_difference 'Issue.count' do
2008 post :create, :project_id => 1, :issue => {
2008 post :create, :project_id => 1, :issue => {
2009 :tracker_id => 2,
2009 :tracker_id => 2,
2010 :status_id => 1,
2010 :status_id => 1,
2011 :subject => 'Test',
2011 :subject => 'Test',
2012 :start_date => '',
2012 :start_date => '',
2013 :due_date => '',
2013 :due_date => '',
2014 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
2014 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
2015 }
2015 }
2016 assert_response :success
2016 assert_response :success
2017 assert_template 'new'
2017 assert_template 'new'
2018 end
2018 end
2019
2019
2020 assert_error_tag :content => /Due date #{ESCAPED_CANT} be blank/i
2020 assert_error_tag :content => /Due date #{ESCAPED_CANT} be blank/i
2021 assert_error_tag :content => /Bar #{ESCAPED_CANT} be blank/i
2021 assert_error_tag :content => /Bar #{ESCAPED_CANT} be blank/i
2022 end
2022 end
2023
2023
2024 def test_create_should_ignore_readonly_fields
2024 def test_create_should_ignore_readonly_fields
2025 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2025 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2026 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2026 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2027 WorkflowPermission.delete_all
2027 WorkflowPermission.delete_all
2028 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
2028 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
2029 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
2029 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
2030 @request.session[:user_id] = 2
2030 @request.session[:user_id] = 2
2031
2031
2032 assert_difference 'Issue.count' do
2032 assert_difference 'Issue.count' do
2033 post :create, :project_id => 1, :issue => {
2033 post :create, :project_id => 1, :issue => {
2034 :tracker_id => 2,
2034 :tracker_id => 2,
2035 :status_id => 1,
2035 :status_id => 1,
2036 :subject => 'Test',
2036 :subject => 'Test',
2037 :start_date => '2012-07-14',
2037 :start_date => '2012-07-14',
2038 :due_date => '2012-07-16',
2038 :due_date => '2012-07-16',
2039 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
2039 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
2040 }
2040 }
2041 assert_response 302
2041 assert_response 302
2042 end
2042 end
2043
2043
2044 issue = Issue.order('id DESC').first
2044 issue = Issue.order('id DESC').first
2045 assert_equal Date.parse('2012-07-14'), issue.start_date
2045 assert_equal Date.parse('2012-07-14'), issue.start_date
2046 assert_nil issue.due_date
2046 assert_nil issue.due_date
2047 assert_equal 'value1', issue.custom_field_value(cf1)
2047 assert_equal 'value1', issue.custom_field_value(cf1)
2048 assert_nil issue.custom_field_value(cf2)
2048 assert_nil issue.custom_field_value(cf2)
2049 end
2049 end
2050
2050
2051 def test_post_create_with_watchers
2051 def test_post_create_with_watchers
2052 @request.session[:user_id] = 2
2052 @request.session[:user_id] = 2
2053 ActionMailer::Base.deliveries.clear
2053 ActionMailer::Base.deliveries.clear
2054
2054
2055 assert_difference 'Watcher.count', 2 do
2055 assert_difference 'Watcher.count', 2 do
2056 post :create, :project_id => 1,
2056 post :create, :project_id => 1,
2057 :issue => {:tracker_id => 1,
2057 :issue => {:tracker_id => 1,
2058 :subject => 'This is a new issue with watchers',
2058 :subject => 'This is a new issue with watchers',
2059 :description => 'This is the description',
2059 :description => 'This is the description',
2060 :priority_id => 5,
2060 :priority_id => 5,
2061 :watcher_user_ids => ['2', '3']}
2061 :watcher_user_ids => ['2', '3']}
2062 end
2062 end
2063 issue = Issue.find_by_subject('This is a new issue with watchers')
2063 issue = Issue.find_by_subject('This is a new issue with watchers')
2064 assert_not_nil issue
2064 assert_not_nil issue
2065 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
2065 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
2066
2066
2067 # Watchers added
2067 # Watchers added
2068 assert_equal [2, 3], issue.watcher_user_ids.sort
2068 assert_equal [2, 3], issue.watcher_user_ids.sort
2069 assert issue.watched_by?(User.find(3))
2069 assert issue.watched_by?(User.find(3))
2070 # Watchers notified
2070 # Watchers notified
2071 mail = ActionMailer::Base.deliveries.last
2071 mail = ActionMailer::Base.deliveries.last
2072 assert_not_nil mail
2072 assert_not_nil mail
2073 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
2073 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
2074 end
2074 end
2075
2075
2076 def test_post_create_subissue
2076 def test_post_create_subissue
2077 @request.session[:user_id] = 2
2077 @request.session[:user_id] = 2
2078
2078
2079 assert_difference 'Issue.count' do
2079 assert_difference 'Issue.count' do
2080 post :create, :project_id => 1,
2080 post :create, :project_id => 1,
2081 :issue => {:tracker_id => 1,
2081 :issue => {:tracker_id => 1,
2082 :subject => 'This is a child issue',
2082 :subject => 'This is a child issue',
2083 :parent_issue_id => '2'}
2083 :parent_issue_id => '2'}
2084 assert_response 302
2084 assert_response 302
2085 end
2085 end
2086 issue = Issue.order('id DESC').first
2086 issue = Issue.order('id DESC').first
2087 assert_equal Issue.find(2), issue.parent
2087 assert_equal Issue.find(2), issue.parent
2088 end
2088 end
2089
2089
2090 def test_post_create_subissue_with_sharp_parent_id
2090 def test_post_create_subissue_with_sharp_parent_id
2091 @request.session[:user_id] = 2
2091 @request.session[:user_id] = 2
2092
2092
2093 assert_difference 'Issue.count' do
2093 assert_difference 'Issue.count' do
2094 post :create, :project_id => 1,
2094 post :create, :project_id => 1,
2095 :issue => {:tracker_id => 1,
2095 :issue => {:tracker_id => 1,
2096 :subject => 'This is a child issue',
2096 :subject => 'This is a child issue',
2097 :parent_issue_id => '#2'}
2097 :parent_issue_id => '#2'}
2098 assert_response 302
2098 assert_response 302
2099 end
2099 end
2100 issue = Issue.order('id DESC').first
2100 issue = Issue.order('id DESC').first
2101 assert_equal Issue.find(2), issue.parent
2101 assert_equal Issue.find(2), issue.parent
2102 end
2102 end
2103
2103
2104 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
2104 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
2105 @request.session[:user_id] = 2
2105 @request.session[:user_id] = 2
2106
2106
2107 assert_no_difference 'Issue.count' do
2107 assert_no_difference 'Issue.count' do
2108 post :create, :project_id => 1,
2108 post :create, :project_id => 1,
2109 :issue => {:tracker_id => 1,
2109 :issue => {:tracker_id => 1,
2110 :subject => 'This is a child issue',
2110 :subject => 'This is a child issue',
2111 :parent_issue_id => '4'}
2111 :parent_issue_id => '4'}
2112
2112
2113 assert_response :success
2113 assert_response :success
2114 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
2114 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
2115 assert_error_tag :content => /Parent task is invalid/i
2115 assert_error_tag :content => /Parent task is invalid/i
2116 end
2116 end
2117 end
2117 end
2118
2118
2119 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
2119 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
2120 @request.session[:user_id] = 2
2120 @request.session[:user_id] = 2
2121
2121
2122 assert_no_difference 'Issue.count' do
2122 assert_no_difference 'Issue.count' do
2123 post :create, :project_id => 1,
2123 post :create, :project_id => 1,
2124 :issue => {:tracker_id => 1,
2124 :issue => {:tracker_id => 1,
2125 :subject => 'This is a child issue',
2125 :subject => 'This is a child issue',
2126 :parent_issue_id => '01ABC'}
2126 :parent_issue_id => '01ABC'}
2127
2127
2128 assert_response :success
2128 assert_response :success
2129 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
2129 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
2130 assert_error_tag :content => /Parent task is invalid/i
2130 assert_error_tag :content => /Parent task is invalid/i
2131 end
2131 end
2132 end
2132 end
2133
2133
2134 def test_post_create_private
2134 def test_post_create_private
2135 @request.session[:user_id] = 2
2135 @request.session[:user_id] = 2
2136
2136
2137 assert_difference 'Issue.count' do
2137 assert_difference 'Issue.count' do
2138 post :create, :project_id => 1,
2138 post :create, :project_id => 1,
2139 :issue => {:tracker_id => 1,
2139 :issue => {:tracker_id => 1,
2140 :subject => 'This is a private issue',
2140 :subject => 'This is a private issue',
2141 :is_private => '1'}
2141 :is_private => '1'}
2142 end
2142 end
2143 issue = Issue.order('id DESC').first
2143 issue = Issue.order('id DESC').first
2144 assert issue.is_private?
2144 assert issue.is_private?
2145 end
2145 end
2146
2146
2147 def test_post_create_private_with_set_own_issues_private_permission
2147 def test_post_create_private_with_set_own_issues_private_permission
2148 role = Role.find(1)
2148 role = Role.find(1)
2149 role.remove_permission! :set_issues_private
2149 role.remove_permission! :set_issues_private
2150 role.add_permission! :set_own_issues_private
2150 role.add_permission! :set_own_issues_private
2151
2151
2152 @request.session[:user_id] = 2
2152 @request.session[:user_id] = 2
2153
2153
2154 assert_difference 'Issue.count' do
2154 assert_difference 'Issue.count' do
2155 post :create, :project_id => 1,
2155 post :create, :project_id => 1,
2156 :issue => {:tracker_id => 1,
2156 :issue => {:tracker_id => 1,
2157 :subject => 'This is a private issue',
2157 :subject => 'This is a private issue',
2158 :is_private => '1'}
2158 :is_private => '1'}
2159 end
2159 end
2160 issue = Issue.order('id DESC').first
2160 issue = Issue.order('id DESC').first
2161 assert issue.is_private?
2161 assert issue.is_private?
2162 end
2162 end
2163
2163
2164 def test_post_create_should_send_a_notification
2164 def test_post_create_should_send_a_notification
2165 ActionMailer::Base.deliveries.clear
2165 ActionMailer::Base.deliveries.clear
2166 @request.session[:user_id] = 2
2166 @request.session[:user_id] = 2
2167 assert_difference 'Issue.count' do
2167 assert_difference 'Issue.count' do
2168 post :create, :project_id => 1,
2168 post :create, :project_id => 1,
2169 :issue => {:tracker_id => 3,
2169 :issue => {:tracker_id => 3,
2170 :subject => 'This is the test_new issue',
2170 :subject => 'This is the test_new issue',
2171 :description => 'This is the description',
2171 :description => 'This is the description',
2172 :priority_id => 5,
2172 :priority_id => 5,
2173 :estimated_hours => '',
2173 :estimated_hours => '',
2174 :custom_field_values => {'2' => 'Value for field 2'}}
2174 :custom_field_values => {'2' => 'Value for field 2'}}
2175 end
2175 end
2176 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2176 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2177
2177
2178 assert_equal 1, ActionMailer::Base.deliveries.size
2178 assert_equal 1, ActionMailer::Base.deliveries.size
2179 end
2179 end
2180
2180
2181 def test_post_create_should_preserve_fields_values_on_validation_failure
2181 def test_post_create_should_preserve_fields_values_on_validation_failure
2182 @request.session[:user_id] = 2
2182 @request.session[:user_id] = 2
2183 post :create, :project_id => 1,
2183 post :create, :project_id => 1,
2184 :issue => {:tracker_id => 1,
2184 :issue => {:tracker_id => 1,
2185 # empty subject
2185 # empty subject
2186 :subject => '',
2186 :subject => '',
2187 :description => 'This is a description',
2187 :description => 'This is a description',
2188 :priority_id => 6,
2188 :priority_id => 6,
2189 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2189 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2190 assert_response :success
2190 assert_response :success
2191 assert_template 'new'
2191 assert_template 'new'
2192
2192
2193 assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description'
2193 assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description'
2194 assert_select 'select[name=?]', 'issue[priority_id]' do
2194 assert_select 'select[name=?]', 'issue[priority_id]' do
2195 assert_select 'option[value=6][selected=selected]', :text => 'High'
2195 assert_select 'option[value=6][selected=selected]', :text => 'High'
2196 end
2196 end
2197 # Custom fields
2197 # Custom fields
2198 assert_select 'select[name=?]', 'issue[custom_field_values][1]' do
2198 assert_select 'select[name=?]', 'issue[custom_field_values][1]' do
2199 assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle'
2199 assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle'
2200 end
2200 end
2201 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2'
2201 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2'
2202 end
2202 end
2203
2203
2204 def test_post_create_with_failure_should_preserve_watchers
2204 def test_post_create_with_failure_should_preserve_watchers
2205 assert !User.find(8).member_of?(Project.find(1))
2205 assert !User.find(8).member_of?(Project.find(1))
2206
2206
2207 @request.session[:user_id] = 2
2207 @request.session[:user_id] = 2
2208 post :create, :project_id => 1,
2208 post :create, :project_id => 1,
2209 :issue => {:tracker_id => 1,
2209 :issue => {:tracker_id => 1,
2210 :watcher_user_ids => ['3', '8']}
2210 :watcher_user_ids => ['3', '8']}
2211 assert_response :success
2211 assert_response :success
2212 assert_template 'new'
2212 assert_template 'new'
2213
2213
2214 assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]'
2214 assert_select 'input[name=?][value=2]:not(checked)', 'issue[watcher_user_ids][]'
2215 assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]'
2215 assert_select 'input[name=?][value=3][checked=checked]', 'issue[watcher_user_ids][]'
2216 assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]'
2216 assert_select 'input[name=?][value=8][checked=checked]', 'issue[watcher_user_ids][]'
2217 end
2217 end
2218
2218
2219 def test_post_create_should_ignore_non_safe_attributes
2219 def test_post_create_should_ignore_non_safe_attributes
2220 @request.session[:user_id] = 2
2220 @request.session[:user_id] = 2
2221 assert_nothing_raised do
2221 assert_nothing_raised do
2222 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2222 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2223 end
2223 end
2224 end
2224 end
2225
2225
2226 def test_post_create_with_attachment
2226 def test_post_create_with_attachment
2227 set_tmp_attachments_directory
2227 set_tmp_attachments_directory
2228 @request.session[:user_id] = 2
2228 @request.session[:user_id] = 2
2229
2229
2230 assert_difference 'Issue.count' do
2230 assert_difference 'Issue.count' do
2231 assert_difference 'Attachment.count' do
2231 assert_difference 'Attachment.count' do
2232 post :create, :project_id => 1,
2232 post :create, :project_id => 1,
2233 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2233 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2234 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2234 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2235 end
2235 end
2236 end
2236 end
2237
2237
2238 issue = Issue.order('id DESC').first
2238 issue = Issue.order('id DESC').first
2239 attachment = Attachment.order('id DESC').first
2239 attachment = Attachment.order('id DESC').first
2240
2240
2241 assert_equal issue, attachment.container
2241 assert_equal issue, attachment.container
2242 assert_equal 2, attachment.author_id
2242 assert_equal 2, attachment.author_id
2243 assert_equal 'testfile.txt', attachment.filename
2243 assert_equal 'testfile.txt', attachment.filename
2244 assert_equal 'text/plain', attachment.content_type
2244 assert_equal 'text/plain', attachment.content_type
2245 assert_equal 'test file', attachment.description
2245 assert_equal 'test file', attachment.description
2246 assert_equal 59, attachment.filesize
2246 assert_equal 59, attachment.filesize
2247 assert File.exists?(attachment.diskfile)
2247 assert File.exists?(attachment.diskfile)
2248 assert_equal 59, File.size(attachment.diskfile)
2248 assert_equal 59, File.size(attachment.diskfile)
2249 end
2249 end
2250
2250
2251 def test_post_create_with_attachment_should_notify_with_attachments
2251 def test_post_create_with_attachment_should_notify_with_attachments
2252 ActionMailer::Base.deliveries.clear
2252 ActionMailer::Base.deliveries.clear
2253 set_tmp_attachments_directory
2253 set_tmp_attachments_directory
2254 @request.session[:user_id] = 2
2254 @request.session[:user_id] = 2
2255
2255
2256 with_settings :host_name => 'mydomain.foo', :protocol => 'http' do
2256 with_settings :host_name => 'mydomain.foo', :protocol => 'http' do
2257 assert_difference 'Issue.count' do
2257 assert_difference 'Issue.count' do
2258 post :create, :project_id => 1,
2258 post :create, :project_id => 1,
2259 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2259 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2260 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2260 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2261 end
2261 end
2262 end
2262 end
2263
2263
2264 assert_not_nil ActionMailer::Base.deliveries.last
2264 assert_not_nil ActionMailer::Base.deliveries.last
2265 assert_select_email do
2265 assert_select_email do
2266 assert_select 'a[href^=?]', 'http://mydomain.foo/attachments/download', 'testfile.txt'
2266 assert_select 'a[href^=?]', 'http://mydomain.foo/attachments/download', 'testfile.txt'
2267 end
2267 end
2268 end
2268 end
2269
2269
2270 def test_post_create_with_failure_should_save_attachments
2270 def test_post_create_with_failure_should_save_attachments
2271 set_tmp_attachments_directory
2271 set_tmp_attachments_directory
2272 @request.session[:user_id] = 2
2272 @request.session[:user_id] = 2
2273
2273
2274 assert_no_difference 'Issue.count' do
2274 assert_no_difference 'Issue.count' do
2275 assert_difference 'Attachment.count' do
2275 assert_difference 'Attachment.count' do
2276 post :create, :project_id => 1,
2276 post :create, :project_id => 1,
2277 :issue => { :tracker_id => '1', :subject => '' },
2277 :issue => { :tracker_id => '1', :subject => '' },
2278 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2278 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2279 assert_response :success
2279 assert_response :success
2280 assert_template 'new'
2280 assert_template 'new'
2281 end
2281 end
2282 end
2282 end
2283
2283
2284 attachment = Attachment.order('id DESC').first
2284 attachment = Attachment.order('id DESC').first
2285 assert_equal 'testfile.txt', attachment.filename
2285 assert_equal 'testfile.txt', attachment.filename
2286 assert File.exists?(attachment.diskfile)
2286 assert File.exists?(attachment.diskfile)
2287 assert_nil attachment.container
2287 assert_nil attachment.container
2288
2288
2289 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2289 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2290 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2290 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2291 end
2291 end
2292
2292
2293 def test_post_create_with_failure_should_keep_saved_attachments
2293 def test_post_create_with_failure_should_keep_saved_attachments
2294 set_tmp_attachments_directory
2294 set_tmp_attachments_directory
2295 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2295 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2296 @request.session[:user_id] = 2
2296 @request.session[:user_id] = 2
2297
2297
2298 assert_no_difference 'Issue.count' do
2298 assert_no_difference 'Issue.count' do
2299 assert_no_difference 'Attachment.count' do
2299 assert_no_difference 'Attachment.count' do
2300 post :create, :project_id => 1,
2300 post :create, :project_id => 1,
2301 :issue => { :tracker_id => '1', :subject => '' },
2301 :issue => { :tracker_id => '1', :subject => '' },
2302 :attachments => {'p0' => {'token' => attachment.token}}
2302 :attachments => {'p0' => {'token' => attachment.token}}
2303 assert_response :success
2303 assert_response :success
2304 assert_template 'new'
2304 assert_template 'new'
2305 end
2305 end
2306 end
2306 end
2307
2307
2308 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2308 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2309 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2309 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2310 end
2310 end
2311
2311
2312 def test_post_create_should_attach_saved_attachments
2312 def test_post_create_should_attach_saved_attachments
2313 set_tmp_attachments_directory
2313 set_tmp_attachments_directory
2314 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2314 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2315 @request.session[:user_id] = 2
2315 @request.session[:user_id] = 2
2316
2316
2317 assert_difference 'Issue.count' do
2317 assert_difference 'Issue.count' do
2318 assert_no_difference 'Attachment.count' do
2318 assert_no_difference 'Attachment.count' do
2319 post :create, :project_id => 1,
2319 post :create, :project_id => 1,
2320 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2320 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2321 :attachments => {'p0' => {'token' => attachment.token}}
2321 :attachments => {'p0' => {'token' => attachment.token}}
2322 assert_response 302
2322 assert_response 302
2323 end
2323 end
2324 end
2324 end
2325
2325
2326 issue = Issue.order('id DESC').first
2326 issue = Issue.order('id DESC').first
2327 assert_equal 1, issue.attachments.count
2327 assert_equal 1, issue.attachments.count
2328
2328
2329 attachment.reload
2329 attachment.reload
2330 assert_equal issue, attachment.container
2330 assert_equal issue, attachment.container
2331 end
2331 end
2332
2332
2333 def setup_without_workflow_privilege
2333 def setup_without_workflow_privilege
2334 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2334 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2335 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2335 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2336 end
2336 end
2337 private :setup_without_workflow_privilege
2337 private :setup_without_workflow_privilege
2338
2338
2339 test "without workflow privilege #new should propose default status only" do
2339 test "without workflow privilege #new should propose default status only" do
2340 setup_without_workflow_privilege
2340 setup_without_workflow_privilege
2341 get :new, :project_id => 1
2341 get :new, :project_id => 1
2342 assert_response :success
2342 assert_response :success
2343 assert_template 'new'
2343 assert_template 'new'
2344 assert_select 'select[name=?]', 'issue[status_id]' do
2344 assert_select 'select[name=?]', 'issue[status_id]' do
2345 assert_select 'option', 1
2345 assert_select 'option', 1
2346 assert_select 'option[value=?]', IssueStatus.default.id.to_s
2346 assert_select 'option[value=?]', IssueStatus.default.id.to_s
2347 end
2347 end
2348 end
2348 end
2349
2349
2350 test "without workflow privilege #new should accept default status" do
2350 test "without workflow privilege #new should accept default status" do
2351 setup_without_workflow_privilege
2351 setup_without_workflow_privilege
2352 assert_difference 'Issue.count' do
2352 assert_difference 'Issue.count' do
2353 post :create, :project_id => 1,
2353 post :create, :project_id => 1,
2354 :issue => {:tracker_id => 1,
2354 :issue => {:tracker_id => 1,
2355 :subject => 'This is an issue',
2355 :subject => 'This is an issue',
2356 :status_id => 1}
2356 :status_id => 1}
2357 end
2357 end
2358 issue = Issue.order('id').last
2358 issue = Issue.order('id').last
2359 assert_equal IssueStatus.default, issue.status
2359 assert_equal IssueStatus.default, issue.status
2360 end
2360 end
2361
2361
2362 test "without workflow privilege #new should ignore unauthorized status" do
2362 test "without workflow privilege #new should ignore unauthorized status" do
2363 setup_without_workflow_privilege
2363 setup_without_workflow_privilege
2364 assert_difference 'Issue.count' do
2364 assert_difference 'Issue.count' do
2365 post :create, :project_id => 1,
2365 post :create, :project_id => 1,
2366 :issue => {:tracker_id => 1,
2366 :issue => {:tracker_id => 1,
2367 :subject => 'This is an issue',
2367 :subject => 'This is an issue',
2368 :status_id => 3}
2368 :status_id => 3}
2369 end
2369 end
2370 issue = Issue.order('id').last
2370 issue = Issue.order('id').last
2371 assert_equal IssueStatus.default, issue.status
2371 assert_equal IssueStatus.default, issue.status
2372 end
2372 end
2373
2373
2374 test "without workflow privilege #update should ignore status change" do
2374 test "without workflow privilege #update should ignore status change" do
2375 setup_without_workflow_privilege
2375 setup_without_workflow_privilege
2376 assert_difference 'Journal.count' do
2376 assert_difference 'Journal.count' do
2377 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2377 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2378 end
2378 end
2379 assert_equal 1, Issue.find(1).status_id
2379 assert_equal 1, Issue.find(1).status_id
2380 end
2380 end
2381
2381
2382 test "without workflow privilege #update ignore attributes changes" do
2382 test "without workflow privilege #update ignore attributes changes" do
2383 setup_without_workflow_privilege
2383 setup_without_workflow_privilege
2384 assert_difference 'Journal.count' do
2384 assert_difference 'Journal.count' do
2385 put :update, :id => 1,
2385 put :update, :id => 1,
2386 :issue => {:subject => 'changed', :assigned_to_id => 2,
2386 :issue => {:subject => 'changed', :assigned_to_id => 2,
2387 :notes => 'just trying'}
2387 :notes => 'just trying'}
2388 end
2388 end
2389 issue = Issue.find(1)
2389 issue = Issue.find(1)
2390 assert_equal "Can't print recipes", issue.subject
2390 assert_equal "Can't print recipes", issue.subject
2391 assert_nil issue.assigned_to
2391 assert_nil issue.assigned_to
2392 end
2392 end
2393
2393
2394 def setup_with_workflow_privilege
2394 def setup_with_workflow_privilege
2395 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2395 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2396 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2396 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2397 :old_status_id => 1, :new_status_id => 3)
2397 :old_status_id => 1, :new_status_id => 3)
2398 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2398 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2399 :old_status_id => 1, :new_status_id => 4)
2399 :old_status_id => 1, :new_status_id => 4)
2400 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2400 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2401 end
2401 end
2402 private :setup_with_workflow_privilege
2402 private :setup_with_workflow_privilege
2403
2403
2404 test "with workflow privilege #update should accept authorized status" do
2404 test "with workflow privilege #update should accept authorized status" do
2405 setup_with_workflow_privilege
2405 setup_with_workflow_privilege
2406 assert_difference 'Journal.count' do
2406 assert_difference 'Journal.count' do
2407 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2407 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2408 end
2408 end
2409 assert_equal 3, Issue.find(1).status_id
2409 assert_equal 3, Issue.find(1).status_id
2410 end
2410 end
2411
2411
2412 test "with workflow privilege #update should ignore unauthorized status" do
2412 test "with workflow privilege #update should ignore unauthorized status" do
2413 setup_with_workflow_privilege
2413 setup_with_workflow_privilege
2414 assert_difference 'Journal.count' do
2414 assert_difference 'Journal.count' do
2415 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2415 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2416 end
2416 end
2417 assert_equal 1, Issue.find(1).status_id
2417 assert_equal 1, Issue.find(1).status_id
2418 end
2418 end
2419
2419
2420 test "with workflow privilege #update should accept authorized attributes changes" do
2420 test "with workflow privilege #update should accept authorized attributes changes" do
2421 setup_with_workflow_privilege
2421 setup_with_workflow_privilege
2422 assert_difference 'Journal.count' do
2422 assert_difference 'Journal.count' do
2423 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2423 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2424 end
2424 end
2425 issue = Issue.find(1)
2425 issue = Issue.find(1)
2426 assert_equal 2, issue.assigned_to_id
2426 assert_equal 2, issue.assigned_to_id
2427 end
2427 end
2428
2428
2429 test "with workflow privilege #update should ignore unauthorized attributes changes" do
2429 test "with workflow privilege #update should ignore unauthorized attributes changes" do
2430 setup_with_workflow_privilege
2430 setup_with_workflow_privilege
2431 assert_difference 'Journal.count' do
2431 assert_difference 'Journal.count' do
2432 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2432 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2433 end
2433 end
2434 issue = Issue.find(1)
2434 issue = Issue.find(1)
2435 assert_equal "Can't print recipes", issue.subject
2435 assert_equal "Can't print recipes", issue.subject
2436 end
2436 end
2437
2437
2438 def setup_with_workflow_privilege_and_edit_issues_permission
2438 def setup_with_workflow_privilege_and_edit_issues_permission
2439 setup_with_workflow_privilege
2439 setup_with_workflow_privilege
2440 Role.anonymous.add_permission! :add_issues, :edit_issues
2440 Role.anonymous.add_permission! :add_issues, :edit_issues
2441 end
2441 end
2442 private :setup_with_workflow_privilege_and_edit_issues_permission
2442 private :setup_with_workflow_privilege_and_edit_issues_permission
2443
2443
2444 test "with workflow privilege and :edit_issues permission should accept authorized status" do
2444 test "with workflow privilege and :edit_issues permission should accept authorized status" do
2445 setup_with_workflow_privilege_and_edit_issues_permission
2445 setup_with_workflow_privilege_and_edit_issues_permission
2446 assert_difference 'Journal.count' do
2446 assert_difference 'Journal.count' do
2447 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2447 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2448 end
2448 end
2449 assert_equal 3, Issue.find(1).status_id
2449 assert_equal 3, Issue.find(1).status_id
2450 end
2450 end
2451
2451
2452 test "with workflow privilege and :edit_issues permission should ignore unauthorized status" do
2452 test "with workflow privilege and :edit_issues permission should ignore unauthorized status" do
2453 setup_with_workflow_privilege_and_edit_issues_permission
2453 setup_with_workflow_privilege_and_edit_issues_permission
2454 assert_difference 'Journal.count' do
2454 assert_difference 'Journal.count' do
2455 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2455 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2456 end
2456 end
2457 assert_equal 1, Issue.find(1).status_id
2457 assert_equal 1, Issue.find(1).status_id
2458 end
2458 end
2459
2459
2460 test "with workflow privilege and :edit_issues permission should accept authorized attributes changes" do
2460 test "with workflow privilege and :edit_issues permission should accept authorized attributes changes" do
2461 setup_with_workflow_privilege_and_edit_issues_permission
2461 setup_with_workflow_privilege_and_edit_issues_permission
2462 assert_difference 'Journal.count' do
2462 assert_difference 'Journal.count' do
2463 put :update, :id => 1,
2463 put :update, :id => 1,
2464 :issue => {:subject => 'changed', :assigned_to_id => 2,
2464 :issue => {:subject => 'changed', :assigned_to_id => 2,
2465 :notes => 'just trying'}
2465 :notes => 'just trying'}
2466 end
2466 end
2467 issue = Issue.find(1)
2467 issue = Issue.find(1)
2468 assert_equal "changed", issue.subject
2468 assert_equal "changed", issue.subject
2469 assert_equal 2, issue.assigned_to_id
2469 assert_equal 2, issue.assigned_to_id
2470 end
2470 end
2471
2471
2472 def test_new_as_copy
2472 def test_new_as_copy
2473 @request.session[:user_id] = 2
2473 @request.session[:user_id] = 2
2474 get :new, :project_id => 1, :copy_from => 1
2474 get :new, :project_id => 1, :copy_from => 1
2475
2475
2476 assert_response :success
2476 assert_response :success
2477 assert_template 'new'
2477 assert_template 'new'
2478
2478
2479 assert_not_nil assigns(:issue)
2479 assert_not_nil assigns(:issue)
2480 orig = Issue.find(1)
2480 orig = Issue.find(1)
2481 assert_equal 1, assigns(:issue).project_id
2481 assert_equal 1, assigns(:issue).project_id
2482 assert_equal orig.subject, assigns(:issue).subject
2482 assert_equal orig.subject, assigns(:issue).subject
2483 assert assigns(:issue).copy?
2483 assert assigns(:issue).copy?
2484
2484
2485 assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do
2485 assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do
2486 assert_select 'select[name=?]', 'issue[project_id]' do
2486 assert_select 'select[name=?]', 'issue[project_id]' do
2487 assert_select 'option[value=1][selected=selected]', :text => 'eCookbook'
2487 assert_select 'option[value=1][selected=selected]', :text => 'eCookbook'
2488 assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore'
2488 assert_select 'option[value=2]:not([selected])', :text => 'OnlineStore'
2489 end
2489 end
2490 assert_select 'input[name=copy_from][value=1]'
2490 assert_select 'input[name=copy_from][value=1]'
2491 end
2491 end
2492
2492
2493 # "New issue" menu item should not link to copy
2493 # "New issue" menu item should not link to copy
2494 assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]'
2494 assert_select '#main-menu a.new-issue[href=/projects/ecookbook/issues/new]'
2495 end
2495 end
2496
2496
2497 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2497 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2498 @request.session[:user_id] = 2
2498 @request.session[:user_id] = 2
2499 issue = Issue.find(3)
2499 issue = Issue.find(3)
2500 assert issue.attachments.count > 0
2500 assert issue.attachments.count > 0
2501 get :new, :project_id => 1, :copy_from => 3
2501 get :new, :project_id => 1, :copy_from => 3
2502
2502
2503 assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]'
2503 assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value=1]'
2504 end
2504 end
2505
2505
2506 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2506 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2507 @request.session[:user_id] = 2
2507 @request.session[:user_id] = 2
2508 issue = Issue.find(3)
2508 issue = Issue.find(3)
2509 issue.attachments.delete_all
2509 issue.attachments.delete_all
2510 get :new, :project_id => 1, :copy_from => 3
2510 get :new, :project_id => 1, :copy_from => 3
2511
2511
2512 assert_select 'input[name=copy_attachments]', 0
2512 assert_select 'input[name=copy_attachments]', 0
2513 end
2513 end
2514
2514
2515 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2515 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2516 @request.session[:user_id] = 2
2516 @request.session[:user_id] = 2
2517 issue = Issue.generate_with_descendants!
2517 issue = Issue.generate_with_descendants!
2518 get :new, :project_id => 1, :copy_from => issue.id
2518 get :new, :project_id => 1, :copy_from => issue.id
2519
2519
2520 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2520 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2521 end
2521 end
2522
2522
2523 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2523 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2524 @request.session[:user_id] = 2
2524 @request.session[:user_id] = 2
2525 get :new, :project_id => 1, :copy_from => 99999
2525 get :new, :project_id => 1, :copy_from => 99999
2526 assert_response 404
2526 assert_response 404
2527 end
2527 end
2528
2528
2529 def test_create_as_copy_on_different_project
2529 def test_create_as_copy_on_different_project
2530 @request.session[:user_id] = 2
2530 @request.session[:user_id] = 2
2531 assert_difference 'Issue.count' do
2531 assert_difference 'Issue.count' do
2532 post :create, :project_id => 1, :copy_from => 1,
2532 post :create, :project_id => 1, :copy_from => 1,
2533 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2533 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2534
2534
2535 assert_not_nil assigns(:issue)
2535 assert_not_nil assigns(:issue)
2536 assert assigns(:issue).copy?
2536 assert assigns(:issue).copy?
2537 end
2537 end
2538 issue = Issue.order('id DESC').first
2538 issue = Issue.order('id DESC').first
2539 assert_redirected_to "/issues/#{issue.id}"
2539 assert_redirected_to "/issues/#{issue.id}"
2540
2540
2541 assert_equal 2, issue.project_id
2541 assert_equal 2, issue.project_id
2542 assert_equal 3, issue.tracker_id
2542 assert_equal 3, issue.tracker_id
2543 assert_equal 'Copy', issue.subject
2543 assert_equal 'Copy', issue.subject
2544 end
2544 end
2545
2545
2546 def test_create_as_copy_should_copy_attachments
2546 def test_create_as_copy_should_copy_attachments
2547 @request.session[:user_id] = 2
2547 @request.session[:user_id] = 2
2548 issue = Issue.find(3)
2548 issue = Issue.find(3)
2549 count = issue.attachments.count
2549 count = issue.attachments.count
2550 assert count > 0
2550 assert count > 0
2551 assert_difference 'Issue.count' do
2551 assert_difference 'Issue.count' do
2552 assert_difference 'Attachment.count', count do
2552 assert_difference 'Attachment.count', count do
2553 assert_difference 'Journal.count', 2 do
2553 assert_difference 'Journal.count', 2 do
2554 post :create, :project_id => 1, :copy_from => 3,
2554 post :create, :project_id => 1, :copy_from => 3,
2555 :issue => {:project_id => '1', :tracker_id => '3',
2555 :issue => {:project_id => '1', :tracker_id => '3',
2556 :status_id => '1', :subject => 'Copy with attachments'},
2556 :status_id => '1', :subject => 'Copy with attachments'},
2557 :copy_attachments => '1'
2557 :copy_attachments => '1'
2558 end
2558 end
2559 end
2559 end
2560 end
2560 end
2561 copy = Issue.order('id DESC').first
2561 copy = Issue.order('id DESC').first
2562 assert_equal count, copy.attachments.count
2562 assert_equal count, copy.attachments.count
2563 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2563 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2564 end
2564 end
2565
2565
2566 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2566 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2567 @request.session[:user_id] = 2
2567 @request.session[:user_id] = 2
2568 issue = Issue.find(3)
2568 issue = Issue.find(3)
2569 count = issue.attachments.count
2569 count = issue.attachments.count
2570 assert count > 0
2570 assert count > 0
2571 assert_difference 'Issue.count' do
2571 assert_difference 'Issue.count' do
2572 assert_no_difference 'Attachment.count' do
2572 assert_no_difference 'Attachment.count' do
2573 assert_difference 'Journal.count', 2 do
2573 assert_difference 'Journal.count', 2 do
2574 post :create, :project_id => 1, :copy_from => 3,
2574 post :create, :project_id => 1, :copy_from => 3,
2575 :issue => {:project_id => '1', :tracker_id => '3',
2575 :issue => {:project_id => '1', :tracker_id => '3',
2576 :status_id => '1', :subject => 'Copy with attachments'}
2576 :status_id => '1', :subject => 'Copy with attachments'}
2577 end
2577 end
2578 end
2578 end
2579 end
2579 end
2580 copy = Issue.order('id DESC').first
2580 copy = Issue.order('id DESC').first
2581 assert_equal 0, copy.attachments.count
2581 assert_equal 0, copy.attachments.count
2582 end
2582 end
2583
2583
2584 def test_create_as_copy_with_attachments_should_add_new_files
2584 def test_create_as_copy_with_attachments_should_add_new_files
2585 @request.session[:user_id] = 2
2585 @request.session[:user_id] = 2
2586 issue = Issue.find(3)
2586 issue = Issue.find(3)
2587 count = issue.attachments.count
2587 count = issue.attachments.count
2588 assert count > 0
2588 assert count > 0
2589 assert_difference 'Issue.count' do
2589 assert_difference 'Issue.count' do
2590 assert_difference 'Attachment.count', count + 1 do
2590 assert_difference 'Attachment.count', count + 1 do
2591 assert_difference 'Journal.count', 2 do
2591 assert_difference 'Journal.count', 2 do
2592 post :create, :project_id => 1, :copy_from => 3,
2592 post :create, :project_id => 1, :copy_from => 3,
2593 :issue => {:project_id => '1', :tracker_id => '3',
2593 :issue => {:project_id => '1', :tracker_id => '3',
2594 :status_id => '1', :subject => 'Copy with attachments'},
2594 :status_id => '1', :subject => 'Copy with attachments'},
2595 :copy_attachments => '1',
2595 :copy_attachments => '1',
2596 :attachments => {'1' =>
2596 :attachments => {'1' =>
2597 {'file' => uploaded_test_file('testfile.txt', 'text/plain'),
2597 {'file' => uploaded_test_file('testfile.txt', 'text/plain'),
2598 'description' => 'test file'}}
2598 'description' => 'test file'}}
2599 end
2599 end
2600 end
2600 end
2601 end
2601 end
2602 copy = Issue.order('id DESC').first
2602 copy = Issue.order('id DESC').first
2603 assert_equal count + 1, copy.attachments.count
2603 assert_equal count + 1, copy.attachments.count
2604 end
2604 end
2605
2605
2606 def test_create_as_copy_should_add_relation_with_copied_issue
2606 def test_create_as_copy_should_add_relation_with_copied_issue
2607 @request.session[:user_id] = 2
2607 @request.session[:user_id] = 2
2608 assert_difference 'Issue.count' do
2608 assert_difference 'Issue.count' do
2609 assert_difference 'IssueRelation.count' do
2609 assert_difference 'IssueRelation.count' do
2610 post :create, :project_id => 1, :copy_from => 1,
2610 post :create, :project_id => 1, :copy_from => 1,
2611 :issue => {:project_id => '1', :tracker_id => '3',
2611 :issue => {:project_id => '1', :tracker_id => '3',
2612 :status_id => '1', :subject => 'Copy'}
2612 :status_id => '1', :subject => 'Copy'}
2613 end
2613 end
2614 end
2614 end
2615 copy = Issue.order('id DESC').first
2615 copy = Issue.order('id DESC').first
2616 assert_equal 1, copy.relations.size
2616 assert_equal 1, copy.relations.size
2617 end
2617 end
2618
2618
2619 def test_create_as_copy_should_copy_subtasks
2619 def test_create_as_copy_should_copy_subtasks
2620 @request.session[:user_id] = 2
2620 @request.session[:user_id] = 2
2621 issue = Issue.generate_with_descendants!
2621 issue = Issue.generate_with_descendants!
2622 count = issue.descendants.count
2622 count = issue.descendants.count
2623 assert_difference 'Issue.count', count + 1 do
2623 assert_difference 'Issue.count', count + 1 do
2624 assert_difference 'Journal.count', (count + 1) * 2 do
2624 assert_difference 'Journal.count', (count + 1) * 2 do
2625 post :create, :project_id => 1, :copy_from => issue.id,
2625 post :create, :project_id => 1, :copy_from => issue.id,
2626 :issue => {:project_id => '1', :tracker_id => '3',
2626 :issue => {:project_id => '1', :tracker_id => '3',
2627 :status_id => '1', :subject => 'Copy with subtasks'},
2627 :status_id => '1', :subject => 'Copy with subtasks'},
2628 :copy_subtasks => '1'
2628 :copy_subtasks => '1'
2629 end
2629 end
2630 end
2630 end
2631 copy = Issue.where(:parent_id => nil).order('id DESC').first
2631 copy = Issue.where(:parent_id => nil).order('id DESC').first
2632 assert_equal count, copy.descendants.count
2632 assert_equal count, copy.descendants.count
2633 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2633 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2634 end
2634 end
2635
2635
2636 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2636 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2637 @request.session[:user_id] = 2
2637 @request.session[:user_id] = 2
2638 issue = Issue.generate_with_descendants!
2638 issue = Issue.generate_with_descendants!
2639 assert_difference 'Issue.count', 1 do
2639 assert_difference 'Issue.count', 1 do
2640 assert_difference 'Journal.count', 2 do
2640 assert_difference 'Journal.count', 2 do
2641 post :create, :project_id => 1, :copy_from => 3,
2641 post :create, :project_id => 1, :copy_from => 3,
2642 :issue => {:project_id => '1', :tracker_id => '3',
2642 :issue => {:project_id => '1', :tracker_id => '3',
2643 :status_id => '1', :subject => 'Copy with subtasks'}
2643 :status_id => '1', :subject => 'Copy with subtasks'}
2644 end
2644 end
2645 end
2645 end
2646 copy = Issue.where(:parent_id => nil).order('id DESC').first
2646 copy = Issue.where(:parent_id => nil).order('id DESC').first
2647 assert_equal 0, copy.descendants.count
2647 assert_equal 0, copy.descendants.count
2648 end
2648 end
2649
2649
2650 def test_create_as_copy_with_failure
2650 def test_create_as_copy_with_failure
2651 @request.session[:user_id] = 2
2651 @request.session[:user_id] = 2
2652 post :create, :project_id => 1, :copy_from => 1,
2652 post :create, :project_id => 1, :copy_from => 1,
2653 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2653 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2654
2654
2655 assert_response :success
2655 assert_response :success
2656 assert_template 'new'
2656 assert_template 'new'
2657
2657
2658 assert_not_nil assigns(:issue)
2658 assert_not_nil assigns(:issue)
2659 assert assigns(:issue).copy?
2659 assert assigns(:issue).copy?
2660
2660
2661 assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do
2661 assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do
2662 assert_select 'select[name=?]', 'issue[project_id]' do
2662 assert_select 'select[name=?]', 'issue[project_id]' do
2663 assert_select 'option[value=1]:not([selected])', :text => 'eCookbook'
2663 assert_select 'option[value=1]:not([selected])', :text => 'eCookbook'
2664 assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore'
2664 assert_select 'option[value=2][selected=selected]', :text => 'OnlineStore'
2665 end
2665 end
2666 assert_select 'input[name=copy_from][value=1]'
2666 assert_select 'input[name=copy_from][value=1]'
2667 end
2667 end
2668 end
2668 end
2669
2669
2670 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2670 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2671 @request.session[:user_id] = 2
2671 @request.session[:user_id] = 2
2672 assert !User.find(2).member_of?(Project.find(4))
2672 assert !User.find(2).member_of?(Project.find(4))
2673
2673
2674 assert_difference 'Issue.count' do
2674 assert_difference 'Issue.count' do
2675 post :create, :project_id => 1, :copy_from => 1,
2675 post :create, :project_id => 1, :copy_from => 1,
2676 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2676 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2677 end
2677 end
2678 issue = Issue.order('id DESC').first
2678 issue = Issue.order('id DESC').first
2679 assert_equal 1, issue.project_id
2679 assert_equal 1, issue.project_id
2680 end
2680 end
2681
2681
2682 def test_get_edit
2682 def test_get_edit
2683 @request.session[:user_id] = 2
2683 @request.session[:user_id] = 2
2684 get :edit, :id => 1
2684 get :edit, :id => 1
2685 assert_response :success
2685 assert_response :success
2686 assert_template 'edit'
2686 assert_template 'edit'
2687 assert_not_nil assigns(:issue)
2687 assert_not_nil assigns(:issue)
2688 assert_equal Issue.find(1), assigns(:issue)
2688 assert_equal Issue.find(1), assigns(:issue)
2689
2689
2690 # Be sure we don't display inactive IssuePriorities
2690 # Be sure we don't display inactive IssuePriorities
2691 assert ! IssuePriority.find(15).active?
2691 assert ! IssuePriority.find(15).active?
2692 assert_select 'select[name=?]', 'issue[priority_id]' do
2692 assert_select 'select[name=?]', 'issue[priority_id]' do
2693 assert_select 'option[value=15]', 0
2693 assert_select 'option[value=15]', 0
2694 end
2694 end
2695 end
2695 end
2696
2696
2697 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2697 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2698 @request.session[:user_id] = 2
2698 @request.session[:user_id] = 2
2699 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2699 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2700
2700
2701 get :edit, :id => 1
2701 get :edit, :id => 1
2702 assert_select 'input[name=?]', 'time_entry[hours]'
2702 assert_select 'input[name=?]', 'time_entry[hours]'
2703 end
2703 end
2704
2704
2705 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2705 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2706 @request.session[:user_id] = 2
2706 @request.session[:user_id] = 2
2707 Role.find_by_name('Manager').remove_permission! :log_time
2707 Role.find_by_name('Manager').remove_permission! :log_time
2708
2708
2709 get :edit, :id => 1
2709 get :edit, :id => 1
2710 assert_select 'input[name=?]', 'time_entry[hours]', 0
2710 assert_select 'input[name=?]', 'time_entry[hours]', 0
2711 end
2711 end
2712
2712
2713 def test_get_edit_with_params
2713 def test_get_edit_with_params
2714 @request.session[:user_id] = 2
2714 @request.session[:user_id] = 2
2715 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2715 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2716 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 }
2716 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 }
2717 assert_response :success
2717 assert_response :success
2718 assert_template 'edit'
2718 assert_template 'edit'
2719
2719
2720 issue = assigns(:issue)
2720 issue = assigns(:issue)
2721 assert_not_nil issue
2721 assert_not_nil issue
2722
2722
2723 assert_equal 5, issue.status_id
2723 assert_equal 5, issue.status_id
2724 assert_select 'select[name=?]', 'issue[status_id]' do
2724 assert_select 'select[name=?]', 'issue[status_id]' do
2725 assert_select 'option[value=5][selected=selected]', :text => 'Closed'
2725 assert_select 'option[value=5][selected=selected]', :text => 'Closed'
2726 end
2726 end
2727
2727
2728 assert_equal 7, issue.priority_id
2728 assert_equal 7, issue.priority_id
2729 assert_select 'select[name=?]', 'issue[priority_id]' do
2729 assert_select 'select[name=?]', 'issue[priority_id]' do
2730 assert_select 'option[value=7][selected=selected]', :text => 'Urgent'
2730 assert_select 'option[value=7][selected=selected]', :text => 'Urgent'
2731 end
2731 end
2732
2732
2733 assert_select 'input[name=?][value=2.5]', 'time_entry[hours]'
2733 assert_select 'input[name=?][value=2.5]', 'time_entry[hours]'
2734 assert_select 'select[name=?]', 'time_entry[activity_id]' do
2734 assert_select 'select[name=?]', 'time_entry[activity_id]' do
2735 assert_select 'option[value=10][selected=selected]', :text => 'Development'
2735 assert_select 'option[value=10][selected=selected]', :text => 'Development'
2736 end
2736 end
2737 assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]'
2737 assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]'
2738 end
2738 end
2739
2739
2740 def test_get_edit_with_multi_custom_field
2740 def test_get_edit_with_multi_custom_field
2741 field = CustomField.find(1)
2741 field = CustomField.find(1)
2742 field.update_attribute :multiple, true
2742 field.update_attribute :multiple, true
2743 issue = Issue.find(1)
2743 issue = Issue.find(1)
2744 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2744 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2745 issue.save!
2745 issue.save!
2746
2746
2747 @request.session[:user_id] = 2
2747 @request.session[:user_id] = 2
2748 get :edit, :id => 1
2748 get :edit, :id => 1
2749 assert_response :success
2749 assert_response :success
2750 assert_template 'edit'
2750 assert_template 'edit'
2751
2751
2752 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
2752 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
2753 assert_select 'option', 3
2753 assert_select 'option', 3
2754 assert_select 'option[value=MySQL][selected=selected]'
2754 assert_select 'option[value=MySQL][selected=selected]'
2755 assert_select 'option[value=Oracle][selected=selected]'
2755 assert_select 'option[value=Oracle][selected=selected]'
2756 assert_select 'option[value=PostgreSQL]:not([selected])'
2756 assert_select 'option[value=PostgreSQL]:not([selected])'
2757 end
2757 end
2758 end
2758 end
2759
2759
2760 def test_update_form_for_existing_issue
2760 def test_update_form_for_existing_issue
2761 @request.session[:user_id] = 2
2761 @request.session[:user_id] = 2
2762 xhr :put, :update_form, :project_id => 1,
2762 xhr :put, :update_form, :project_id => 1,
2763 :id => 1,
2763 :id => 1,
2764 :issue => {:tracker_id => 2,
2764 :issue => {:tracker_id => 2,
2765 :subject => 'This is the test_new issue',
2765 :subject => 'This is the test_new issue',
2766 :description => 'This is the description',
2766 :description => 'This is the description',
2767 :priority_id => 5}
2767 :priority_id => 5}
2768 assert_response :success
2768 assert_response :success
2769 assert_equal 'text/javascript', response.content_type
2769 assert_equal 'text/javascript', response.content_type
2770 assert_template 'update_form'
2770 assert_template 'update_form'
2771 assert_template :partial => '_form'
2771 assert_template :partial => '_form'
2772
2772
2773 issue = assigns(:issue)
2773 issue = assigns(:issue)
2774 assert_kind_of Issue, issue
2774 assert_kind_of Issue, issue
2775 assert_equal 1, issue.id
2775 assert_equal 1, issue.id
2776 assert_equal 1, issue.project_id
2776 assert_equal 1, issue.project_id
2777 assert_equal 2, issue.tracker_id
2777 assert_equal 2, issue.tracker_id
2778 assert_equal 'This is the test_new issue', issue.subject
2778 assert_equal 'This is the test_new issue', issue.subject
2779 end
2779 end
2780
2780
2781 def test_update_form_for_existing_issue_should_keep_issue_author
2781 def test_update_form_for_existing_issue_should_keep_issue_author
2782 @request.session[:user_id] = 3
2782 @request.session[:user_id] = 3
2783 xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2783 xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2784 assert_response :success
2784 assert_response :success
2785 assert_equal 'text/javascript', response.content_type
2785 assert_equal 'text/javascript', response.content_type
2786
2786
2787 issue = assigns(:issue)
2787 issue = assigns(:issue)
2788 assert_equal User.find(2), issue.author
2788 assert_equal User.find(2), issue.author
2789 assert_equal 2, issue.author_id
2789 assert_equal 2, issue.author_id
2790 assert_not_equal User.current, issue.author
2790 assert_not_equal User.current, issue.author
2791 end
2791 end
2792
2792
2793 def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status
2793 def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status
2794 @request.session[:user_id] = 2
2794 @request.session[:user_id] = 2
2795 WorkflowTransition.delete_all
2795 WorkflowTransition.delete_all
2796 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2796 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2797 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2797 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2798 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2798 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2799
2799
2800 xhr :put, :update_form, :project_id => 1,
2800 xhr :put, :update_form, :project_id => 1,
2801 :id => 2,
2801 :id => 2,
2802 :issue => {:tracker_id => 2,
2802 :issue => {:tracker_id => 2,
2803 :status_id => 5,
2803 :status_id => 5,
2804 :subject => 'This is an issue'}
2804 :subject => 'This is an issue'}
2805
2805
2806 assert_equal 5, assigns(:issue).status_id
2806 assert_equal 5, assigns(:issue).status_id
2807 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2807 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2808 end
2808 end
2809
2809
2810 def test_update_form_for_existing_issue_with_project_change
2810 def test_update_form_for_existing_issue_with_project_change
2811 @request.session[:user_id] = 2
2811 @request.session[:user_id] = 2
2812 xhr :put, :update_form, :project_id => 1,
2812 xhr :put, :update_form, :project_id => 1,
2813 :id => 1,
2813 :id => 1,
2814 :issue => {:project_id => 2,
2814 :issue => {:project_id => 2,
2815 :tracker_id => 2,
2815 :tracker_id => 2,
2816 :subject => 'This is the test_new issue',
2816 :subject => 'This is the test_new issue',
2817 :description => 'This is the description',
2817 :description => 'This is the description',
2818 :priority_id => 5}
2818 :priority_id => 5}
2819 assert_response :success
2819 assert_response :success
2820 assert_template :partial => '_form'
2820 assert_template :partial => '_form'
2821
2821
2822 issue = assigns(:issue)
2822 issue = assigns(:issue)
2823 assert_kind_of Issue, issue
2823 assert_kind_of Issue, issue
2824 assert_equal 1, issue.id
2824 assert_equal 1, issue.id
2825 assert_equal 2, issue.project_id
2825 assert_equal 2, issue.project_id
2826 assert_equal 2, issue.tracker_id
2826 assert_equal 2, issue.tracker_id
2827 assert_equal 'This is the test_new issue', issue.subject
2827 assert_equal 'This is the test_new issue', issue.subject
2828 end
2828 end
2829
2829
2830 def test_update_form_should_propose_default_status_for_existing_issue
2830 def test_update_form_should_propose_default_status_for_existing_issue
2831 @request.session[:user_id] = 2
2831 @request.session[:user_id] = 2
2832 WorkflowTransition.delete_all
2832 WorkflowTransition.delete_all
2833 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2833 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2834
2834
2835 xhr :put, :update_form, :project_id => 1, :id => 2
2835 xhr :put, :update_form, :project_id => 1, :id => 2
2836 assert_response :success
2836 assert_response :success
2837 assert_equal [2,3], assigns(:allowed_statuses).map(&:id).sort
2837 assert_equal [2,3], assigns(:allowed_statuses).map(&:id).sort
2838 end
2838 end
2839
2839
2840 def test_put_update_without_custom_fields_param
2840 def test_put_update_without_custom_fields_param
2841 @request.session[:user_id] = 2
2841 @request.session[:user_id] = 2
2842 ActionMailer::Base.deliveries.clear
2842 ActionMailer::Base.deliveries.clear
2843
2843
2844 issue = Issue.find(1)
2844 issue = Issue.find(1)
2845 assert_equal '125', issue.custom_value_for(2).value
2845 assert_equal '125', issue.custom_value_for(2).value
2846 old_subject = issue.subject
2846 old_subject = issue.subject
2847 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2847 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2848
2848
2849 assert_difference('Journal.count') do
2849 assert_difference('Journal.count') do
2850 assert_difference('JournalDetail.count', 2) do
2850 assert_difference('JournalDetail.count', 2) do
2851 put :update, :id => 1, :issue => {:subject => new_subject,
2851 put :update, :id => 1, :issue => {:subject => new_subject,
2852 :priority_id => '6',
2852 :priority_id => '6',
2853 :category_id => '1' # no change
2853 :category_id => '1' # no change
2854 }
2854 }
2855 end
2855 end
2856 end
2856 end
2857 assert_redirected_to :action => 'show', :id => '1'
2857 assert_redirected_to :action => 'show', :id => '1'
2858 issue.reload
2858 issue.reload
2859 assert_equal new_subject, issue.subject
2859 assert_equal new_subject, issue.subject
2860 # Make sure custom fields were not cleared
2860 # Make sure custom fields were not cleared
2861 assert_equal '125', issue.custom_value_for(2).value
2861 assert_equal '125', issue.custom_value_for(2).value
2862
2862
2863 mail = ActionMailer::Base.deliveries.last
2863 mail = ActionMailer::Base.deliveries.last
2864 assert_not_nil mail
2864 assert_not_nil mail
2865 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2865 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2866 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2866 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2867 end
2867 end
2868
2868
2869 def test_put_update_with_project_change
2869 def test_put_update_with_project_change
2870 @request.session[:user_id] = 2
2870 @request.session[:user_id] = 2
2871 ActionMailer::Base.deliveries.clear
2871 ActionMailer::Base.deliveries.clear
2872
2872
2873 assert_difference('Journal.count') do
2873 assert_difference('Journal.count') do
2874 assert_difference('JournalDetail.count', 3) do
2874 assert_difference('JournalDetail.count', 3) do
2875 put :update, :id => 1, :issue => {:project_id => '2',
2875 put :update, :id => 1, :issue => {:project_id => '2',
2876 :tracker_id => '1', # no change
2876 :tracker_id => '1', # no change
2877 :priority_id => '6',
2877 :priority_id => '6',
2878 :category_id => '3'
2878 :category_id => '3'
2879 }
2879 }
2880 end
2880 end
2881 end
2881 end
2882 assert_redirected_to :action => 'show', :id => '1'
2882 assert_redirected_to :action => 'show', :id => '1'
2883 issue = Issue.find(1)
2883 issue = Issue.find(1)
2884 assert_equal 2, issue.project_id
2884 assert_equal 2, issue.project_id
2885 assert_equal 1, issue.tracker_id
2885 assert_equal 1, issue.tracker_id
2886 assert_equal 6, issue.priority_id
2886 assert_equal 6, issue.priority_id
2887 assert_equal 3, issue.category_id
2887 assert_equal 3, issue.category_id
2888
2888
2889 mail = ActionMailer::Base.deliveries.last
2889 mail = ActionMailer::Base.deliveries.last
2890 assert_not_nil mail
2890 assert_not_nil mail
2891 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2891 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2892 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2892 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2893 end
2893 end
2894
2894
2895 def test_put_update_with_tracker_change
2895 def test_put_update_with_tracker_change
2896 @request.session[:user_id] = 2
2896 @request.session[:user_id] = 2
2897 ActionMailer::Base.deliveries.clear
2897 ActionMailer::Base.deliveries.clear
2898
2898
2899 assert_difference('Journal.count') do
2899 assert_difference('Journal.count') do
2900 assert_difference('JournalDetail.count', 2) do
2900 assert_difference('JournalDetail.count', 2) do
2901 put :update, :id => 1, :issue => {:project_id => '1',
2901 put :update, :id => 1, :issue => {:project_id => '1',
2902 :tracker_id => '2',
2902 :tracker_id => '2',
2903 :priority_id => '6'
2903 :priority_id => '6'
2904 }
2904 }
2905 end
2905 end
2906 end
2906 end
2907 assert_redirected_to :action => 'show', :id => '1'
2907 assert_redirected_to :action => 'show', :id => '1'
2908 issue = Issue.find(1)
2908 issue = Issue.find(1)
2909 assert_equal 1, issue.project_id
2909 assert_equal 1, issue.project_id
2910 assert_equal 2, issue.tracker_id
2910 assert_equal 2, issue.tracker_id
2911 assert_equal 6, issue.priority_id
2911 assert_equal 6, issue.priority_id
2912 assert_equal 1, issue.category_id
2912 assert_equal 1, issue.category_id
2913
2913
2914 mail = ActionMailer::Base.deliveries.last
2914 mail = ActionMailer::Base.deliveries.last
2915 assert_not_nil mail
2915 assert_not_nil mail
2916 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2916 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2917 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2917 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2918 end
2918 end
2919
2919
2920 def test_put_update_with_custom_field_change
2920 def test_put_update_with_custom_field_change
2921 @request.session[:user_id] = 2
2921 @request.session[:user_id] = 2
2922 issue = Issue.find(1)
2922 issue = Issue.find(1)
2923 assert_equal '125', issue.custom_value_for(2).value
2923 assert_equal '125', issue.custom_value_for(2).value
2924
2924
2925 assert_difference('Journal.count') do
2925 assert_difference('Journal.count') do
2926 assert_difference('JournalDetail.count', 3) do
2926 assert_difference('JournalDetail.count', 3) do
2927 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2927 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2928 :priority_id => '6',
2928 :priority_id => '6',
2929 :category_id => '1', # no change
2929 :category_id => '1', # no change
2930 :custom_field_values => { '2' => 'New custom value' }
2930 :custom_field_values => { '2' => 'New custom value' }
2931 }
2931 }
2932 end
2932 end
2933 end
2933 end
2934 assert_redirected_to :action => 'show', :id => '1'
2934 assert_redirected_to :action => 'show', :id => '1'
2935 issue.reload
2935 issue.reload
2936 assert_equal 'New custom value', issue.custom_value_for(2).value
2936 assert_equal 'New custom value', issue.custom_value_for(2).value
2937
2937
2938 mail = ActionMailer::Base.deliveries.last
2938 mail = ActionMailer::Base.deliveries.last
2939 assert_not_nil mail
2939 assert_not_nil mail
2940 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2940 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2941 end
2941 end
2942
2942
2943 def test_put_update_with_multi_custom_field_change
2943 def test_put_update_with_multi_custom_field_change
2944 field = CustomField.find(1)
2944 field = CustomField.find(1)
2945 field.update_attribute :multiple, true
2945 field.update_attribute :multiple, true
2946 issue = Issue.find(1)
2946 issue = Issue.find(1)
2947 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2947 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2948 issue.save!
2948 issue.save!
2949
2949
2950 @request.session[:user_id] = 2
2950 @request.session[:user_id] = 2
2951 assert_difference('Journal.count') do
2951 assert_difference('Journal.count') do
2952 assert_difference('JournalDetail.count', 3) do
2952 assert_difference('JournalDetail.count', 3) do
2953 put :update, :id => 1,
2953 put :update, :id => 1,
2954 :issue => {
2954 :issue => {
2955 :subject => 'Custom field change',
2955 :subject => 'Custom field change',
2956 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2956 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2957 }
2957 }
2958 end
2958 end
2959 end
2959 end
2960 assert_redirected_to :action => 'show', :id => '1'
2960 assert_redirected_to :action => 'show', :id => '1'
2961 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2961 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2962 end
2962 end
2963
2963
2964 def test_put_update_with_status_and_assignee_change
2964 def test_put_update_with_status_and_assignee_change
2965 issue = Issue.find(1)
2965 issue = Issue.find(1)
2966 assert_equal 1, issue.status_id
2966 assert_equal 1, issue.status_id
2967 @request.session[:user_id] = 2
2967 @request.session[:user_id] = 2
2968 assert_difference('TimeEntry.count', 0) do
2968 assert_difference('TimeEntry.count', 0) do
2969 put :update,
2969 put :update,
2970 :id => 1,
2970 :id => 1,
2971 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2971 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2972 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2972 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2973 end
2973 end
2974 assert_redirected_to :action => 'show', :id => '1'
2974 assert_redirected_to :action => 'show', :id => '1'
2975 issue.reload
2975 issue.reload
2976 assert_equal 2, issue.status_id
2976 assert_equal 2, issue.status_id
2977 j = Journal.order('id DESC').first
2977 j = Journal.order('id DESC').first
2978 assert_equal 'Assigned to dlopper', j.notes
2978 assert_equal 'Assigned to dlopper', j.notes
2979 assert_equal 2, j.details.size
2979 assert_equal 2, j.details.size
2980
2980
2981 mail = ActionMailer::Base.deliveries.last
2981 mail = ActionMailer::Base.deliveries.last
2982 assert_mail_body_match "Status changed from New to Assigned", mail
2982 assert_mail_body_match "Status changed from New to Assigned", mail
2983 # subject should contain the new status
2983 # subject should contain the new status
2984 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2984 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2985 end
2985 end
2986
2986
2987 def test_put_update_with_note_only
2987 def test_put_update_with_note_only
2988 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2988 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2989 # anonymous user
2989 # anonymous user
2990 put :update,
2990 put :update,
2991 :id => 1,
2991 :id => 1,
2992 :issue => { :notes => notes }
2992 :issue => { :notes => notes }
2993 assert_redirected_to :action => 'show', :id => '1'
2993 assert_redirected_to :action => 'show', :id => '1'
2994 j = Journal.order('id DESC').first
2994 j = Journal.order('id DESC').first
2995 assert_equal notes, j.notes
2995 assert_equal notes, j.notes
2996 assert_equal 0, j.details.size
2996 assert_equal 0, j.details.size
2997 assert_equal User.anonymous, j.user
2997 assert_equal User.anonymous, j.user
2998
2998
2999 mail = ActionMailer::Base.deliveries.last
2999 mail = ActionMailer::Base.deliveries.last
3000 assert_mail_body_match notes, mail
3000 assert_mail_body_match notes, mail
3001 end
3001 end
3002
3002
3003 def test_put_update_with_private_note_only
3003 def test_put_update_with_private_note_only
3004 notes = 'Private note'
3004 notes = 'Private note'
3005 @request.session[:user_id] = 2
3005 @request.session[:user_id] = 2
3006
3006
3007 assert_difference 'Journal.count' do
3007 assert_difference 'Journal.count' do
3008 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
3008 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
3009 assert_redirected_to :action => 'show', :id => '1'
3009 assert_redirected_to :action => 'show', :id => '1'
3010 end
3010 end
3011
3011
3012 j = Journal.order('id DESC').first
3012 j = Journal.order('id DESC').first
3013 assert_equal notes, j.notes
3013 assert_equal notes, j.notes
3014 assert_equal true, j.private_notes
3014 assert_equal true, j.private_notes
3015 end
3015 end
3016
3016
3017 def test_put_update_with_private_note_and_changes
3017 def test_put_update_with_private_note_and_changes
3018 notes = 'Private note'
3018 notes = 'Private note'
3019 @request.session[:user_id] = 2
3019 @request.session[:user_id] = 2
3020
3020
3021 assert_difference 'Journal.count', 2 do
3021 assert_difference 'Journal.count', 2 do
3022 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
3022 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
3023 assert_redirected_to :action => 'show', :id => '1'
3023 assert_redirected_to :action => 'show', :id => '1'
3024 end
3024 end
3025
3025
3026 j = Journal.order('id DESC').first
3026 j = Journal.order('id DESC').first
3027 assert_equal notes, j.notes
3027 assert_equal notes, j.notes
3028 assert_equal true, j.private_notes
3028 assert_equal true, j.private_notes
3029 assert_equal 0, j.details.count
3029 assert_equal 0, j.details.count
3030
3030
3031 j = Journal.order('id DESC').offset(1).first
3031 j = Journal.order('id DESC').offset(1).first
3032 assert_nil j.notes
3032 assert_nil j.notes
3033 assert_equal false, j.private_notes
3033 assert_equal false, j.private_notes
3034 assert_equal 1, j.details.count
3034 assert_equal 1, j.details.count
3035 end
3035 end
3036
3036
3037 def test_put_update_with_note_and_spent_time
3037 def test_put_update_with_note_and_spent_time
3038 @request.session[:user_id] = 2
3038 @request.session[:user_id] = 2
3039 spent_hours_before = Issue.find(1).spent_hours
3039 spent_hours_before = Issue.find(1).spent_hours
3040 assert_difference('TimeEntry.count') do
3040 assert_difference('TimeEntry.count') do
3041 put :update,
3041 put :update,
3042 :id => 1,
3042 :id => 1,
3043 :issue => { :notes => '2.5 hours added' },
3043 :issue => { :notes => '2.5 hours added' },
3044 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
3044 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
3045 end
3045 end
3046 assert_redirected_to :action => 'show', :id => '1'
3046 assert_redirected_to :action => 'show', :id => '1'
3047
3047
3048 issue = Issue.find(1)
3048 issue = Issue.find(1)
3049
3049
3050 j = Journal.order('id DESC').first
3050 j = Journal.order('id DESC').first
3051 assert_equal '2.5 hours added', j.notes
3051 assert_equal '2.5 hours added', j.notes
3052 assert_equal 0, j.details.size
3052 assert_equal 0, j.details.size
3053
3053
3054 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
3054 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
3055 assert_not_nil t
3055 assert_not_nil t
3056 assert_equal 2.5, t.hours
3056 assert_equal 2.5, t.hours
3057 assert_equal spent_hours_before + 2.5, issue.spent_hours
3057 assert_equal spent_hours_before + 2.5, issue.spent_hours
3058 end
3058 end
3059
3059
3060 def test_put_update_should_preserve_parent_issue_even_if_not_visible
3060 def test_put_update_should_preserve_parent_issue_even_if_not_visible
3061 parent = Issue.generate!(:project_id => 1, :is_private => true)
3061 parent = Issue.generate!(:project_id => 1, :is_private => true)
3062 issue = Issue.generate!(:parent_issue_id => parent.id)
3062 issue = Issue.generate!(:parent_issue_id => parent.id)
3063 assert !parent.visible?(User.find(3))
3063 assert !parent.visible?(User.find(3))
3064 @request.session[:user_id] = 3
3064 @request.session[:user_id] = 3
3065
3065
3066 get :edit, :id => issue.id
3066 get :edit, :id => issue.id
3067 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', parent.id.to_s
3067 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', parent.id.to_s
3068
3068
3069 put :update, :id => issue.id, :issue => {:subject => 'New subject', :parent_issue_id => parent.id.to_s}
3069 put :update, :id => issue.id, :issue => {:subject => 'New subject', :parent_issue_id => parent.id.to_s}
3070 assert_response 302
3070 assert_response 302
3071 assert_equal parent, issue.parent
3071 assert_equal parent, issue.parent
3072 end
3072 end
3073
3073
3074 def test_put_update_with_attachment_only
3074 def test_put_update_with_attachment_only
3075 set_tmp_attachments_directory
3075 set_tmp_attachments_directory
3076
3076
3077 # Delete all fixtured journals, a race condition can occur causing the wrong
3077 # Delete all fixtured journals, a race condition can occur causing the wrong
3078 # journal to get fetched in the next find.
3078 # journal to get fetched in the next find.
3079 Journal.delete_all
3079 Journal.delete_all
3080
3080
3081 # anonymous user
3081 # anonymous user
3082 assert_difference 'Attachment.count' do
3082 assert_difference 'Attachment.count' do
3083 put :update, :id => 1,
3083 put :update, :id => 1,
3084 :issue => {:notes => ''},
3084 :issue => {:notes => ''},
3085 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3085 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3086 end
3086 end
3087
3087
3088 assert_redirected_to :action => 'show', :id => '1'
3088 assert_redirected_to :action => 'show', :id => '1'
3089 j = Issue.find(1).journals.reorder('id DESC').first
3089 j = Issue.find(1).journals.reorder('id DESC').first
3090 assert j.notes.blank?
3090 assert j.notes.blank?
3091 assert_equal 1, j.details.size
3091 assert_equal 1, j.details.size
3092 assert_equal 'testfile.txt', j.details.first.value
3092 assert_equal 'testfile.txt', j.details.first.value
3093 assert_equal User.anonymous, j.user
3093 assert_equal User.anonymous, j.user
3094
3094
3095 attachment = Attachment.order('id DESC').first
3095 attachment = Attachment.order('id DESC').first
3096 assert_equal Issue.find(1), attachment.container
3096 assert_equal Issue.find(1), attachment.container
3097 assert_equal User.anonymous, attachment.author
3097 assert_equal User.anonymous, attachment.author
3098 assert_equal 'testfile.txt', attachment.filename
3098 assert_equal 'testfile.txt', attachment.filename
3099 assert_equal 'text/plain', attachment.content_type
3099 assert_equal 'text/plain', attachment.content_type
3100 assert_equal 'test file', attachment.description
3100 assert_equal 'test file', attachment.description
3101 assert_equal 59, attachment.filesize
3101 assert_equal 59, attachment.filesize
3102 assert File.exists?(attachment.diskfile)
3102 assert File.exists?(attachment.diskfile)
3103 assert_equal 59, File.size(attachment.diskfile)
3103 assert_equal 59, File.size(attachment.diskfile)
3104
3104
3105 mail = ActionMailer::Base.deliveries.last
3105 mail = ActionMailer::Base.deliveries.last
3106 assert_mail_body_match 'testfile.txt', mail
3106 assert_mail_body_match 'testfile.txt', mail
3107 end
3107 end
3108
3108
3109 def test_put_update_with_failure_should_save_attachments
3109 def test_put_update_with_failure_should_save_attachments
3110 set_tmp_attachments_directory
3110 set_tmp_attachments_directory
3111 @request.session[:user_id] = 2
3111 @request.session[:user_id] = 2
3112
3112
3113 assert_no_difference 'Journal.count' do
3113 assert_no_difference 'Journal.count' do
3114 assert_difference 'Attachment.count' do
3114 assert_difference 'Attachment.count' do
3115 put :update, :id => 1,
3115 put :update, :id => 1,
3116 :issue => { :subject => '' },
3116 :issue => { :subject => '' },
3117 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3117 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3118 assert_response :success
3118 assert_response :success
3119 assert_template 'edit'
3119 assert_template 'edit'
3120 end
3120 end
3121 end
3121 end
3122
3122
3123 attachment = Attachment.order('id DESC').first
3123 attachment = Attachment.order('id DESC').first
3124 assert_equal 'testfile.txt', attachment.filename
3124 assert_equal 'testfile.txt', attachment.filename
3125 assert File.exists?(attachment.diskfile)
3125 assert File.exists?(attachment.diskfile)
3126 assert_nil attachment.container
3126 assert_nil attachment.container
3127
3127
3128 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3128 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3129 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3129 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3130 end
3130 end
3131
3131
3132 def test_put_update_with_failure_should_keep_saved_attachments
3132 def test_put_update_with_failure_should_keep_saved_attachments
3133 set_tmp_attachments_directory
3133 set_tmp_attachments_directory
3134 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3134 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3135 @request.session[:user_id] = 2
3135 @request.session[:user_id] = 2
3136
3136
3137 assert_no_difference 'Journal.count' do
3137 assert_no_difference 'Journal.count' do
3138 assert_no_difference 'Attachment.count' do
3138 assert_no_difference 'Attachment.count' do
3139 put :update, :id => 1,
3139 put :update, :id => 1,
3140 :issue => { :subject => '' },
3140 :issue => { :subject => '' },
3141 :attachments => {'p0' => {'token' => attachment.token}}
3141 :attachments => {'p0' => {'token' => attachment.token}}
3142 assert_response :success
3142 assert_response :success
3143 assert_template 'edit'
3143 assert_template 'edit'
3144 end
3144 end
3145 end
3145 end
3146
3146
3147 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3147 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3148 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3148 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3149 end
3149 end
3150
3150
3151 def test_put_update_should_attach_saved_attachments
3151 def test_put_update_should_attach_saved_attachments
3152 set_tmp_attachments_directory
3152 set_tmp_attachments_directory
3153 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3153 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3154 @request.session[:user_id] = 2
3154 @request.session[:user_id] = 2
3155
3155
3156 assert_difference 'Journal.count' do
3156 assert_difference 'Journal.count' do
3157 assert_difference 'JournalDetail.count' do
3157 assert_difference 'JournalDetail.count' do
3158 assert_no_difference 'Attachment.count' do
3158 assert_no_difference 'Attachment.count' do
3159 put :update, :id => 1,
3159 put :update, :id => 1,
3160 :issue => {:notes => 'Attachment added'},
3160 :issue => {:notes => 'Attachment added'},
3161 :attachments => {'p0' => {'token' => attachment.token}}
3161 :attachments => {'p0' => {'token' => attachment.token}}
3162 assert_redirected_to '/issues/1'
3162 assert_redirected_to '/issues/1'
3163 end
3163 end
3164 end
3164 end
3165 end
3165 end
3166
3166
3167 attachment.reload
3167 attachment.reload
3168 assert_equal Issue.find(1), attachment.container
3168 assert_equal Issue.find(1), attachment.container
3169
3169
3170 journal = Journal.order('id DESC').first
3170 journal = Journal.order('id DESC').first
3171 assert_equal 1, journal.details.size
3171 assert_equal 1, journal.details.size
3172 assert_equal 'testfile.txt', journal.details.first.value
3172 assert_equal 'testfile.txt', journal.details.first.value
3173 end
3173 end
3174
3174
3175 def test_put_update_with_attachment_that_fails_to_save
3175 def test_put_update_with_attachment_that_fails_to_save
3176 set_tmp_attachments_directory
3176 set_tmp_attachments_directory
3177
3177
3178 # Delete all fixtured journals, a race condition can occur causing the wrong
3178 # Delete all fixtured journals, a race condition can occur causing the wrong
3179 # journal to get fetched in the next find.
3179 # journal to get fetched in the next find.
3180 Journal.delete_all
3180 Journal.delete_all
3181
3181
3182 # Mock out the unsaved attachment
3182 # Mock out the unsaved attachment
3183 Attachment.any_instance.stubs(:create).returns(Attachment.new)
3183 Attachment.any_instance.stubs(:create).returns(Attachment.new)
3184
3184
3185 # anonymous user
3185 # anonymous user
3186 put :update,
3186 put :update,
3187 :id => 1,
3187 :id => 1,
3188 :issue => {:notes => ''},
3188 :issue => {:notes => ''},
3189 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
3189 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
3190 assert_redirected_to :action => 'show', :id => '1'
3190 assert_redirected_to :action => 'show', :id => '1'
3191 assert_equal '1 file(s) could not be saved.', flash[:warning]
3191 assert_equal '1 file(s) could not be saved.', flash[:warning]
3192 end
3192 end
3193
3193
3194 def test_put_update_with_no_change
3194 def test_put_update_with_no_change
3195 issue = Issue.find(1)
3195 issue = Issue.find(1)
3196 issue.journals.clear
3196 issue.journals.clear
3197 ActionMailer::Base.deliveries.clear
3197 ActionMailer::Base.deliveries.clear
3198
3198
3199 put :update,
3199 put :update,
3200 :id => 1,
3200 :id => 1,
3201 :issue => {:notes => ''}
3201 :issue => {:notes => ''}
3202 assert_redirected_to :action => 'show', :id => '1'
3202 assert_redirected_to :action => 'show', :id => '1'
3203
3203
3204 issue.reload
3204 issue.reload
3205 assert issue.journals.empty?
3205 assert issue.journals.empty?
3206 # No email should be sent
3206 # No email should be sent
3207 assert ActionMailer::Base.deliveries.empty?
3207 assert ActionMailer::Base.deliveries.empty?
3208 end
3208 end
3209
3209
3210 def test_put_update_should_send_a_notification
3210 def test_put_update_should_send_a_notification
3211 @request.session[:user_id] = 2
3211 @request.session[:user_id] = 2
3212 ActionMailer::Base.deliveries.clear
3212 ActionMailer::Base.deliveries.clear
3213 issue = Issue.find(1)
3213 issue = Issue.find(1)
3214 old_subject = issue.subject
3214 old_subject = issue.subject
3215 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3215 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3216
3216
3217 put :update, :id => 1, :issue => {:subject => new_subject,
3217 put :update, :id => 1, :issue => {:subject => new_subject,
3218 :priority_id => '6',
3218 :priority_id => '6',
3219 :category_id => '1' # no change
3219 :category_id => '1' # no change
3220 }
3220 }
3221 assert_equal 1, ActionMailer::Base.deliveries.size
3221 assert_equal 1, ActionMailer::Base.deliveries.size
3222 end
3222 end
3223
3223
3224 def test_put_update_with_invalid_spent_time_hours_only
3224 def test_put_update_with_invalid_spent_time_hours_only
3225 @request.session[:user_id] = 2
3225 @request.session[:user_id] = 2
3226 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3226 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3227
3227
3228 assert_no_difference('Journal.count') do
3228 assert_no_difference('Journal.count') do
3229 put :update,
3229 put :update,
3230 :id => 1,
3230 :id => 1,
3231 :issue => {:notes => notes},
3231 :issue => {:notes => notes},
3232 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3232 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3233 end
3233 end
3234 assert_response :success
3234 assert_response :success
3235 assert_template 'edit'
3235 assert_template 'edit'
3236
3236
3237 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3237 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3238 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3238 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3239 assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z'
3239 assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z'
3240 end
3240 end
3241
3241
3242 def test_put_update_with_invalid_spent_time_comments_only
3242 def test_put_update_with_invalid_spent_time_comments_only
3243 @request.session[:user_id] = 2
3243 @request.session[:user_id] = 2
3244 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3244 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3245
3245
3246 assert_no_difference('Journal.count') do
3246 assert_no_difference('Journal.count') do
3247 put :update,
3247 put :update,
3248 :id => 1,
3248 :id => 1,
3249 :issue => {:notes => notes},
3249 :issue => {:notes => notes},
3250 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3250 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3251 end
3251 end
3252 assert_response :success
3252 assert_response :success
3253 assert_template 'edit'
3253 assert_template 'edit'
3254
3254
3255 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3255 assert_error_tag :descendant => {:content => /Activity #{ESCAPED_CANT} be blank/}
3256 assert_error_tag :descendant => {:content => /Hours #{ESCAPED_CANT} be blank/}
3256 assert_error_tag :descendant => {:content => /Hours #{ESCAPED_CANT} be blank/}
3257 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3257 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3258 assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment'
3258 assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment'
3259 end
3259 end
3260
3260
3261 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3261 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3262 issue = Issue.find(2)
3262 issue = Issue.find(2)
3263 @request.session[:user_id] = 2
3263 @request.session[:user_id] = 2
3264
3264
3265 put :update,
3265 put :update,
3266 :id => issue.id,
3266 :id => issue.id,
3267 :issue => {
3267 :issue => {
3268 :fixed_version_id => 4
3268 :fixed_version_id => 4
3269 }
3269 }
3270
3270
3271 assert_response :redirect
3271 assert_response :redirect
3272 issue.reload
3272 issue.reload
3273 assert_equal 4, issue.fixed_version_id
3273 assert_equal 4, issue.fixed_version_id
3274 assert_not_equal issue.project_id, issue.fixed_version.project_id
3274 assert_not_equal issue.project_id, issue.fixed_version.project_id
3275 end
3275 end
3276
3276
3277 def test_put_update_should_redirect_back_using_the_back_url_parameter
3277 def test_put_update_should_redirect_back_using_the_back_url_parameter
3278 issue = Issue.find(2)
3278 issue = Issue.find(2)
3279 @request.session[:user_id] = 2
3279 @request.session[:user_id] = 2
3280
3280
3281 put :update,
3281 put :update,
3282 :id => issue.id,
3282 :id => issue.id,
3283 :issue => {
3283 :issue => {
3284 :fixed_version_id => 4
3284 :fixed_version_id => 4
3285 },
3285 },
3286 :back_url => '/issues'
3286 :back_url => '/issues'
3287
3287
3288 assert_response :redirect
3288 assert_response :redirect
3289 assert_redirected_to '/issues'
3289 assert_redirected_to '/issues'
3290 end
3290 end
3291
3291
3292 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3292 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3293 issue = Issue.find(2)
3293 issue = Issue.find(2)
3294 @request.session[:user_id] = 2
3294 @request.session[:user_id] = 2
3295
3295
3296 put :update,
3296 put :update,
3297 :id => issue.id,
3297 :id => issue.id,
3298 :issue => {
3298 :issue => {
3299 :fixed_version_id => 4
3299 :fixed_version_id => 4
3300 },
3300 },
3301 :back_url => 'http://google.com'
3301 :back_url => 'http://google.com'
3302
3302
3303 assert_response :redirect
3303 assert_response :redirect
3304 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3304 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3305 end
3305 end
3306
3306
3307 def test_get_bulk_edit
3307 def test_get_bulk_edit
3308 @request.session[:user_id] = 2
3308 @request.session[:user_id] = 2
3309 get :bulk_edit, :ids => [1, 2]
3309 get :bulk_edit, :ids => [1, 2]
3310 assert_response :success
3310 assert_response :success
3311 assert_template 'bulk_edit'
3311 assert_template 'bulk_edit'
3312
3312
3313 assert_select 'ul#bulk-selection' do
3313 assert_select 'ul#bulk-selection' do
3314 assert_select 'li', 2
3314 assert_select 'li', 2
3315 assert_select 'li a', :text => 'Bug #1'
3315 assert_select 'li a', :text => 'Bug #1'
3316 end
3316 end
3317
3317
3318 assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do
3318 assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do
3319 assert_select 'input[name=?]', 'ids[]', 2
3319 assert_select 'input[name=?]', 'ids[]', 2
3320 assert_select 'input[name=?][value=1][type=hidden]', 'ids[]'
3320 assert_select 'input[name=?][value=1][type=hidden]', 'ids[]'
3321
3321
3322 assert_select 'select[name=?]', 'issue[project_id]'
3322 assert_select 'select[name=?]', 'issue[project_id]'
3323 assert_select 'input[name=?]', 'issue[parent_issue_id]'
3323 assert_select 'input[name=?]', 'issue[parent_issue_id]'
3324
3324
3325 # Project specific custom field, date type
3325 # Project specific custom field, date type
3326 field = CustomField.find(9)
3326 field = CustomField.find(9)
3327 assert !field.is_for_all?
3327 assert !field.is_for_all?
3328 assert_equal 'date', field.field_format
3328 assert_equal 'date', field.field_format
3329 assert_select 'input[name=?]', 'issue[custom_field_values][9]'
3329 assert_select 'input[name=?]', 'issue[custom_field_values][9]'
3330
3330
3331 # System wide custom field
3331 # System wide custom field
3332 assert CustomField.find(1).is_for_all?
3332 assert CustomField.find(1).is_for_all?
3333 assert_select 'select[name=?]', 'issue[custom_field_values][1]'
3333 assert_select 'select[name=?]', 'issue[custom_field_values][1]'
3334
3334
3335 # Be sure we don't display inactive IssuePriorities
3335 # Be sure we don't display inactive IssuePriorities
3336 assert ! IssuePriority.find(15).active?
3336 assert ! IssuePriority.find(15).active?
3337 assert_select 'select[name=?]', 'issue[priority_id]' do
3337 assert_select 'select[name=?]', 'issue[priority_id]' do
3338 assert_select 'option[value=15]', 0
3338 assert_select 'option[value=15]', 0
3339 end
3339 end
3340 end
3340 end
3341 end
3341 end
3342
3342
3343 def test_get_bulk_edit_on_different_projects
3343 def test_get_bulk_edit_on_different_projects
3344 @request.session[:user_id] = 2
3344 @request.session[:user_id] = 2
3345 get :bulk_edit, :ids => [1, 2, 6]
3345 get :bulk_edit, :ids => [1, 2, 6]
3346 assert_response :success
3346 assert_response :success
3347 assert_template 'bulk_edit'
3347 assert_template 'bulk_edit'
3348
3348
3349 # Can not set issues from different projects as children of an issue
3349 # Can not set issues from different projects as children of an issue
3350 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
3350 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
3351
3351
3352 # Project specific custom field, date type
3352 # Project specific custom field, date type
3353 field = CustomField.find(9)
3353 field = CustomField.find(9)
3354 assert !field.is_for_all?
3354 assert !field.is_for_all?
3355 assert !field.project_ids.include?(Issue.find(6).project_id)
3355 assert !field.project_ids.include?(Issue.find(6).project_id)
3356 assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0
3356 assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0
3357 end
3357 end
3358
3358
3359 def test_get_bulk_edit_with_user_custom_field
3359 def test_get_bulk_edit_with_user_custom_field
3360 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3360 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3361
3361
3362 @request.session[:user_id] = 2
3362 @request.session[:user_id] = 2
3363 get :bulk_edit, :ids => [1, 2]
3363 get :bulk_edit, :ids => [1, 2]
3364 assert_response :success
3364 assert_response :success
3365 assert_template 'bulk_edit'
3365 assert_template 'bulk_edit'
3366
3366
3367 assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3367 assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3368 assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options
3368 assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options
3369 end
3369 end
3370 end
3370 end
3371
3371
3372 def test_get_bulk_edit_with_version_custom_field
3372 def test_get_bulk_edit_with_version_custom_field
3373 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3373 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3374
3374
3375 @request.session[:user_id] = 2
3375 @request.session[:user_id] = 2
3376 get :bulk_edit, :ids => [1, 2]
3376 get :bulk_edit, :ids => [1, 2]
3377 assert_response :success
3377 assert_response :success
3378 assert_template 'bulk_edit'
3378 assert_template 'bulk_edit'
3379
3379
3380 assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3380 assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3381 assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3381 assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3382 end
3382 end
3383 end
3383 end
3384
3384
3385 def test_get_bulk_edit_with_multi_custom_field
3385 def test_get_bulk_edit_with_multi_custom_field
3386 field = CustomField.find(1)
3386 field = CustomField.find(1)
3387 field.update_attribute :multiple, true
3387 field.update_attribute :multiple, true
3388
3388
3389 @request.session[:user_id] = 2
3389 @request.session[:user_id] = 2
3390 get :bulk_edit, :ids => [1, 2]
3390 get :bulk_edit, :ids => [1, 2]
3391 assert_response :success
3391 assert_response :success
3392 assert_template 'bulk_edit'
3392 assert_template 'bulk_edit'
3393
3393
3394 assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do
3394 assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do
3395 assert_select 'option', field.possible_values.size + 1 # "none" options
3395 assert_select 'option', field.possible_values.size + 1 # "none" options
3396 end
3396 end
3397 end
3397 end
3398
3398
3399 def test_bulk_edit_should_propose_to_clear_text_custom_fields
3399 def test_bulk_edit_should_propose_to_clear_text_custom_fields
3400 @request.session[:user_id] = 2
3400 @request.session[:user_id] = 2
3401 get :bulk_edit, :ids => [1, 3]
3401 get :bulk_edit, :ids => [1, 3]
3402 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', '__none__'
3402 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', '__none__'
3403 end
3403 end
3404
3404
3405 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3405 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3406 WorkflowTransition.delete_all
3406 WorkflowTransition.delete_all
3407 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3407 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3408 :old_status_id => 1, :new_status_id => 1)
3408 :old_status_id => 1, :new_status_id => 1)
3409 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3409 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3410 :old_status_id => 1, :new_status_id => 3)
3410 :old_status_id => 1, :new_status_id => 3)
3411 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3411 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3412 :old_status_id => 1, :new_status_id => 4)
3412 :old_status_id => 1, :new_status_id => 4)
3413 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3413 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3414 :old_status_id => 2, :new_status_id => 1)
3414 :old_status_id => 2, :new_status_id => 1)
3415 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3415 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3416 :old_status_id => 2, :new_status_id => 3)
3416 :old_status_id => 2, :new_status_id => 3)
3417 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3417 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3418 :old_status_id => 2, :new_status_id => 5)
3418 :old_status_id => 2, :new_status_id => 5)
3419 @request.session[:user_id] = 2
3419 @request.session[:user_id] = 2
3420 get :bulk_edit, :ids => [1, 2]
3420 get :bulk_edit, :ids => [1, 2]
3421
3421
3422 assert_response :success
3422 assert_response :success
3423 statuses = assigns(:available_statuses)
3423 statuses = assigns(:available_statuses)
3424 assert_not_nil statuses
3424 assert_not_nil statuses
3425 assert_equal [1, 3], statuses.map(&:id).sort
3425 assert_equal [1, 3], statuses.map(&:id).sort
3426
3426
3427 assert_select 'select[name=?]', 'issue[status_id]' do
3427 assert_select 'select[name=?]', 'issue[status_id]' do
3428 assert_select 'option', 3 # 2 statuses + "no change" option
3428 assert_select 'option', 3 # 2 statuses + "no change" option
3429 end
3429 end
3430 end
3430 end
3431
3431
3432 def test_bulk_edit_should_propose_target_project_open_shared_versions
3432 def test_bulk_edit_should_propose_target_project_open_shared_versions
3433 @request.session[:user_id] = 2
3433 @request.session[:user_id] = 2
3434 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3434 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3435 assert_response :success
3435 assert_response :success
3436 assert_template 'bulk_edit'
3436 assert_template 'bulk_edit'
3437 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
3437 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
3438
3438
3439 assert_select 'select[name=?]', 'issue[fixed_version_id]' do
3439 assert_select 'select[name=?]', 'issue[fixed_version_id]' do
3440 assert_select 'option', :text => '2.0'
3440 assert_select 'option', :text => '2.0'
3441 end
3441 end
3442 end
3442 end
3443
3443
3444 def test_bulk_edit_should_propose_target_project_categories
3444 def test_bulk_edit_should_propose_target_project_categories
3445 @request.session[:user_id] = 2
3445 @request.session[:user_id] = 2
3446 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3446 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3447 assert_response :success
3447 assert_response :success
3448 assert_template 'bulk_edit'
3448 assert_template 'bulk_edit'
3449 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3449 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3450
3450
3451 assert_select 'select[name=?]', 'issue[category_id]' do
3451 assert_select 'select[name=?]', 'issue[category_id]' do
3452 assert_select 'option', :text => 'Recipes'
3452 assert_select 'option', :text => 'Recipes'
3453 end
3453 end
3454 end
3454 end
3455
3455
3456 def test_bulk_update
3456 def test_bulk_update
3457 @request.session[:user_id] = 2
3457 @request.session[:user_id] = 2
3458 # update issues priority
3458 # update issues priority
3459 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3459 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3460 :issue => {:priority_id => 7,
3460 :issue => {:priority_id => 7,
3461 :assigned_to_id => '',
3461 :assigned_to_id => '',
3462 :custom_field_values => {'2' => ''}}
3462 :custom_field_values => {'2' => ''}}
3463
3463
3464 assert_response 302
3464 assert_response 302
3465 # check that the issues were updated
3465 # check that the issues were updated
3466 assert_equal [7, 7], Issue.where(:id =>[1, 2]).collect {|i| i.priority.id}
3466 assert_equal [7, 7], Issue.where(:id =>[1, 2]).collect {|i| i.priority.id}
3467
3467
3468 issue = Issue.find(1)
3468 issue = Issue.find(1)
3469 journal = issue.journals.reorder('created_on DESC').first
3469 journal = issue.journals.reorder('created_on DESC').first
3470 assert_equal '125', issue.custom_value_for(2).value
3470 assert_equal '125', issue.custom_value_for(2).value
3471 assert_equal 'Bulk editing', journal.notes
3471 assert_equal 'Bulk editing', journal.notes
3472 assert_equal 1, journal.details.size
3472 assert_equal 1, journal.details.size
3473 end
3473 end
3474
3474
3475 def test_bulk_update_with_group_assignee
3475 def test_bulk_update_with_group_assignee
3476 group = Group.find(11)
3476 group = Group.find(11)
3477 project = Project.find(1)
3477 project = Project.find(1)
3478 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3478 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3479
3479
3480 @request.session[:user_id] = 2
3480 @request.session[:user_id] = 2
3481 # update issues assignee
3481 # update issues assignee
3482 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3482 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3483 :issue => {:priority_id => '',
3483 :issue => {:priority_id => '',
3484 :assigned_to_id => group.id,
3484 :assigned_to_id => group.id,
3485 :custom_field_values => {'2' => ''}}
3485 :custom_field_values => {'2' => ''}}
3486
3486
3487 assert_response 302
3487 assert_response 302
3488 assert_equal [group, group], Issue.where(:id => [1, 2]).collect {|i| i.assigned_to}
3488 assert_equal [group, group], Issue.where(:id => [1, 2]).collect {|i| i.assigned_to}
3489 end
3489 end
3490
3490
3491 def test_bulk_update_on_different_projects
3491 def test_bulk_update_on_different_projects
3492 @request.session[:user_id] = 2
3492 @request.session[:user_id] = 2
3493 # update issues priority
3493 # update issues priority
3494 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3494 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3495 :issue => {:priority_id => 7,
3495 :issue => {:priority_id => 7,
3496 :assigned_to_id => '',
3496 :assigned_to_id => '',
3497 :custom_field_values => {'2' => ''}}
3497 :custom_field_values => {'2' => ''}}
3498
3498
3499 assert_response 302
3499 assert_response 302
3500 # check that the issues were updated
3500 # check that the issues were updated
3501 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3501 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3502
3502
3503 issue = Issue.find(1)
3503 issue = Issue.find(1)
3504 journal = issue.journals.reorder('created_on DESC').first
3504 journal = issue.journals.reorder('created_on DESC').first
3505 assert_equal '125', issue.custom_value_for(2).value
3505 assert_equal '125', issue.custom_value_for(2).value
3506 assert_equal 'Bulk editing', journal.notes
3506 assert_equal 'Bulk editing', journal.notes
3507 assert_equal 1, journal.details.size
3507 assert_equal 1, journal.details.size
3508 end
3508 end
3509
3509
3510 def test_bulk_update_on_different_projects_without_rights
3510 def test_bulk_update_on_different_projects_without_rights
3511 @request.session[:user_id] = 3
3511 @request.session[:user_id] = 3
3512 user = User.find(3)
3512 user = User.find(3)
3513 action = { :controller => "issues", :action => "bulk_update" }
3513 action = { :controller => "issues", :action => "bulk_update" }
3514 assert user.allowed_to?(action, Issue.find(1).project)
3514 assert user.allowed_to?(action, Issue.find(1).project)
3515 assert ! user.allowed_to?(action, Issue.find(6).project)
3515 assert ! user.allowed_to?(action, Issue.find(6).project)
3516 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3516 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3517 :issue => {:priority_id => 7,
3517 :issue => {:priority_id => 7,
3518 :assigned_to_id => '',
3518 :assigned_to_id => '',
3519 :custom_field_values => {'2' => ''}}
3519 :custom_field_values => {'2' => ''}}
3520 assert_response 403
3520 assert_response 403
3521 assert_not_equal "Bulk should fail", Journal.last.notes
3521 assert_not_equal "Bulk should fail", Journal.last.notes
3522 end
3522 end
3523
3523
3524 def test_bullk_update_should_send_a_notification
3524 def test_bullk_update_should_send_a_notification
3525 @request.session[:user_id] = 2
3525 @request.session[:user_id] = 2
3526 ActionMailer::Base.deliveries.clear
3526 ActionMailer::Base.deliveries.clear
3527 post(:bulk_update,
3527 post(:bulk_update,
3528 {
3528 {
3529 :ids => [1, 2],
3529 :ids => [1, 2],
3530 :notes => 'Bulk editing',
3530 :notes => 'Bulk editing',
3531 :issue => {
3531 :issue => {
3532 :priority_id => 7,
3532 :priority_id => 7,
3533 :assigned_to_id => '',
3533 :assigned_to_id => '',
3534 :custom_field_values => {'2' => ''}
3534 :custom_field_values => {'2' => ''}
3535 }
3535 }
3536 })
3536 })
3537
3537
3538 assert_response 302
3538 assert_response 302
3539 assert_equal 2, ActionMailer::Base.deliveries.size
3539 assert_equal 2, ActionMailer::Base.deliveries.size
3540 end
3540 end
3541
3541
3542 def test_bulk_update_project
3542 def test_bulk_update_project
3543 @request.session[:user_id] = 2
3543 @request.session[:user_id] = 2
3544 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3544 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3545 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3545 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3546 # Issues moved to project 2
3546 # Issues moved to project 2
3547 assert_equal 2, Issue.find(1).project_id
3547 assert_equal 2, Issue.find(1).project_id
3548 assert_equal 2, Issue.find(2).project_id
3548 assert_equal 2, Issue.find(2).project_id
3549 # No tracker change
3549 # No tracker change
3550 assert_equal 1, Issue.find(1).tracker_id
3550 assert_equal 1, Issue.find(1).tracker_id
3551 assert_equal 2, Issue.find(2).tracker_id
3551 assert_equal 2, Issue.find(2).tracker_id
3552 end
3552 end
3553
3553
3554 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3554 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3555 @request.session[:user_id] = 2
3555 @request.session[:user_id] = 2
3556 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3556 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3557 assert_redirected_to '/issues/1'
3557 assert_redirected_to '/issues/1'
3558 end
3558 end
3559
3559
3560 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3560 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3561 @request.session[:user_id] = 2
3561 @request.session[:user_id] = 2
3562 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3562 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3563 assert_redirected_to '/projects/onlinestore/issues'
3563 assert_redirected_to '/projects/onlinestore/issues'
3564 end
3564 end
3565
3565
3566 def test_bulk_update_tracker
3566 def test_bulk_update_tracker
3567 @request.session[:user_id] = 2
3567 @request.session[:user_id] = 2
3568 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3568 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3569 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3569 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3570 assert_equal 2, Issue.find(1).tracker_id
3570 assert_equal 2, Issue.find(1).tracker_id
3571 assert_equal 2, Issue.find(2).tracker_id
3571 assert_equal 2, Issue.find(2).tracker_id
3572 end
3572 end
3573
3573
3574 def test_bulk_update_status
3574 def test_bulk_update_status
3575 @request.session[:user_id] = 2
3575 @request.session[:user_id] = 2
3576 # update issues priority
3576 # update issues priority
3577 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3577 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3578 :issue => {:priority_id => '',
3578 :issue => {:priority_id => '',
3579 :assigned_to_id => '',
3579 :assigned_to_id => '',
3580 :status_id => '5'}
3580 :status_id => '5'}
3581
3581
3582 assert_response 302
3582 assert_response 302
3583 issue = Issue.find(1)
3583 issue = Issue.find(1)
3584 assert issue.closed?
3584 assert issue.closed?
3585 end
3585 end
3586
3586
3587 def test_bulk_update_priority
3587 def test_bulk_update_priority
3588 @request.session[:user_id] = 2
3588 @request.session[:user_id] = 2
3589 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3589 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3590
3590
3591 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3591 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3592 assert_equal 6, Issue.find(1).priority_id
3592 assert_equal 6, Issue.find(1).priority_id
3593 assert_equal 6, Issue.find(2).priority_id
3593 assert_equal 6, Issue.find(2).priority_id
3594 end
3594 end
3595
3595
3596 def test_bulk_update_with_notes
3596 def test_bulk_update_with_notes
3597 @request.session[:user_id] = 2
3597 @request.session[:user_id] = 2
3598 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3598 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3599
3599
3600 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3600 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3601 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3601 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3602 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3602 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3603 end
3603 end
3604
3604
3605 def test_bulk_update_parent_id
3605 def test_bulk_update_parent_id
3606 IssueRelation.delete_all
3606 IssueRelation.delete_all
3607 @request.session[:user_id] = 2
3607 @request.session[:user_id] = 2
3608 post :bulk_update, :ids => [1, 3],
3608 post :bulk_update, :ids => [1, 3],
3609 :notes => 'Bulk editing parent',
3609 :notes => 'Bulk editing parent',
3610 :issue => {:priority_id => '', :assigned_to_id => '',
3610 :issue => {:priority_id => '', :assigned_to_id => '',
3611 :status_id => '', :parent_issue_id => '2'}
3611 :status_id => '', :parent_issue_id => '2'}
3612 assert_response 302
3612 assert_response 302
3613 parent = Issue.find(2)
3613 parent = Issue.find(2)
3614 assert_equal parent.id, Issue.find(1).parent_id
3614 assert_equal parent.id, Issue.find(1).parent_id
3615 assert_equal parent.id, Issue.find(3).parent_id
3615 assert_equal parent.id, Issue.find(3).parent_id
3616 assert_equal [1, 3], parent.children.collect(&:id).sort
3616 assert_equal [1, 3], parent.children.collect(&:id).sort
3617 end
3617 end
3618
3618
3619 def test_bulk_update_custom_field
3619 def test_bulk_update_custom_field
3620 @request.session[:user_id] = 2
3620 @request.session[:user_id] = 2
3621 # update issues priority
3621 # update issues priority
3622 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3622 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3623 :issue => {:priority_id => '',
3623 :issue => {:priority_id => '',
3624 :assigned_to_id => '',
3624 :assigned_to_id => '',
3625 :custom_field_values => {'2' => '777'}}
3625 :custom_field_values => {'2' => '777'}}
3626
3626
3627 assert_response 302
3627 assert_response 302
3628
3628
3629 issue = Issue.find(1)
3629 issue = Issue.find(1)
3630 journal = issue.journals.reorder('created_on DESC').first
3630 journal = issue.journals.reorder('created_on DESC').first
3631 assert_equal '777', issue.custom_value_for(2).value
3631 assert_equal '777', issue.custom_value_for(2).value
3632 assert_equal 1, journal.details.size
3632 assert_equal 1, journal.details.size
3633 assert_equal '125', journal.details.first.old_value
3633 assert_equal '125', journal.details.first.old_value
3634 assert_equal '777', journal.details.first.value
3634 assert_equal '777', journal.details.first.value
3635 end
3635 end
3636
3636
3637 def test_bulk_update_custom_field_to_blank
3637 def test_bulk_update_custom_field_to_blank
3638 @request.session[:user_id] = 2
3638 @request.session[:user_id] = 2
3639 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3639 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3640 :issue => {:priority_id => '',
3640 :issue => {:priority_id => '',
3641 :assigned_to_id => '',
3641 :assigned_to_id => '',
3642 :custom_field_values => {'1' => '__none__'}}
3642 :custom_field_values => {'1' => '__none__'}}
3643 assert_response 302
3643 assert_response 302
3644 assert_equal '', Issue.find(1).custom_field_value(1)
3644 assert_equal '', Issue.find(1).custom_field_value(1)
3645 assert_equal '', Issue.find(3).custom_field_value(1)
3645 assert_equal '', Issue.find(3).custom_field_value(1)
3646 end
3646 end
3647
3647
3648 def test_bulk_update_multi_custom_field
3648 def test_bulk_update_multi_custom_field
3649 field = CustomField.find(1)
3649 field = CustomField.find(1)
3650 field.update_attribute :multiple, true
3650 field.update_attribute :multiple, true
3651
3651
3652 @request.session[:user_id] = 2
3652 @request.session[:user_id] = 2
3653 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3653 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3654 :issue => {:priority_id => '',
3654 :issue => {:priority_id => '',
3655 :assigned_to_id => '',
3655 :assigned_to_id => '',
3656 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3656 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3657
3657
3658 assert_response 302
3658 assert_response 302
3659
3659
3660 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3660 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3661 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3661 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3662 # the custom field is not associated with the issue tracker
3662 # the custom field is not associated with the issue tracker
3663 assert_nil Issue.find(2).custom_field_value(1)
3663 assert_nil Issue.find(2).custom_field_value(1)
3664 end
3664 end
3665
3665
3666 def test_bulk_update_multi_custom_field_to_blank
3666 def test_bulk_update_multi_custom_field_to_blank
3667 field = CustomField.find(1)
3667 field = CustomField.find(1)
3668 field.update_attribute :multiple, true
3668 field.update_attribute :multiple, true
3669
3669
3670 @request.session[:user_id] = 2
3670 @request.session[:user_id] = 2
3671 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3671 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3672 :issue => {:priority_id => '',
3672 :issue => {:priority_id => '',
3673 :assigned_to_id => '',
3673 :assigned_to_id => '',
3674 :custom_field_values => {'1' => ['__none__']}}
3674 :custom_field_values => {'1' => ['__none__']}}
3675 assert_response 302
3675 assert_response 302
3676 assert_equal [''], Issue.find(1).custom_field_value(1)
3676 assert_equal [''], Issue.find(1).custom_field_value(1)
3677 assert_equal [''], Issue.find(3).custom_field_value(1)
3677 assert_equal [''], Issue.find(3).custom_field_value(1)
3678 end
3678 end
3679
3679
3680 def test_bulk_update_unassign
3680 def test_bulk_update_unassign
3681 assert_not_nil Issue.find(2).assigned_to
3681 assert_not_nil Issue.find(2).assigned_to
3682 @request.session[:user_id] = 2
3682 @request.session[:user_id] = 2
3683 # unassign issues
3683 # unassign issues
3684 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3684 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3685 assert_response 302
3685 assert_response 302
3686 # check that the issues were updated
3686 # check that the issues were updated
3687 assert_nil Issue.find(2).assigned_to
3687 assert_nil Issue.find(2).assigned_to
3688 end
3688 end
3689
3689
3690 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3690 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3691 @request.session[:user_id] = 2
3691 @request.session[:user_id] = 2
3692
3692
3693 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3693 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3694
3694
3695 assert_response :redirect
3695 assert_response :redirect
3696 issues = Issue.find([1,2])
3696 issues = Issue.find([1,2])
3697 issues.each do |issue|
3697 issues.each do |issue|
3698 assert_equal 4, issue.fixed_version_id
3698 assert_equal 4, issue.fixed_version_id
3699 assert_not_equal issue.project_id, issue.fixed_version.project_id
3699 assert_not_equal issue.project_id, issue.fixed_version.project_id
3700 end
3700 end
3701 end
3701 end
3702
3702
3703 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3703 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3704 @request.session[:user_id] = 2
3704 @request.session[:user_id] = 2
3705 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3705 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3706
3706
3707 assert_response :redirect
3707 assert_response :redirect
3708 assert_redirected_to '/issues'
3708 assert_redirected_to '/issues'
3709 end
3709 end
3710
3710
3711 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3711 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3712 @request.session[:user_id] = 2
3712 @request.session[:user_id] = 2
3713 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3713 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3714
3714
3715 assert_response :redirect
3715 assert_response :redirect
3716 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3716 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3717 end
3717 end
3718
3718
3719 def test_bulk_update_with_all_failures_should_show_errors
3719 def test_bulk_update_with_all_failures_should_show_errors
3720 @request.session[:user_id] = 2
3720 @request.session[:user_id] = 2
3721 post :bulk_update, :ids => [1, 2], :issue => {:start_date => 'foo'}
3721 post :bulk_update, :ids => [1, 2], :issue => {:start_date => 'foo'}
3722
3722
3723 assert_response :success
3723 assert_response :success
3724 assert_template 'bulk_edit'
3724 assert_template 'bulk_edit'
3725 assert_select '#errorExplanation span', :text => 'Failed to save 2 issue(s) on 2 selected: #1, #2.'
3725 assert_select '#errorExplanation span', :text => 'Failed to save 2 issue(s) on 2 selected: #1, #2.'
3726 assert_select '#errorExplanation ul li', :text => 'Start date is not a valid date: #1, #2'
3726 assert_select '#errorExplanation ul li', :text => 'Start date is not a valid date: #1, #2'
3727
3727
3728 assert_equal [1, 2], assigns[:issues].map(&:id)
3728 assert_equal [1, 2], assigns[:issues].map(&:id)
3729 end
3729 end
3730
3730
3731 def test_bulk_update_with_some_failures_should_show_errors
3731 def test_bulk_update_with_some_failures_should_show_errors
3732 issue1 = Issue.generate!(:start_date => '2013-05-12')
3732 issue1 = Issue.generate!(:start_date => '2013-05-12')
3733 issue2 = Issue.generate!(:start_date => '2013-05-15')
3733 issue2 = Issue.generate!(:start_date => '2013-05-15')
3734 issue3 = Issue.generate!
3734 issue3 = Issue.generate!
3735 @request.session[:user_id] = 2
3735 @request.session[:user_id] = 2
3736 post :bulk_update, :ids => [issue1.id, issue2.id, issue3.id],
3736 post :bulk_update, :ids => [issue1.id, issue2.id, issue3.id],
3737 :issue => {:due_date => '2013-05-01'}
3737 :issue => {:due_date => '2013-05-01'}
3738 assert_response :success
3738 assert_response :success
3739 assert_template 'bulk_edit'
3739 assert_template 'bulk_edit'
3740 assert_select '#errorExplanation span',
3740 assert_select '#errorExplanation span',
3741 :text => "Failed to save 2 issue(s) on 3 selected: ##{issue1.id}, ##{issue2.id}."
3741 :text => "Failed to save 2 issue(s) on 3 selected: ##{issue1.id}, ##{issue2.id}."
3742 assert_select '#errorExplanation ul li',
3742 assert_select '#errorExplanation ul li',
3743 :text => "Due date must be greater than start date: ##{issue1.id}, ##{issue2.id}"
3743 :text => "Due date must be greater than start date: ##{issue1.id}, ##{issue2.id}"
3744 assert_equal [issue1.id, issue2.id], assigns[:issues].map(&:id)
3744 assert_equal [issue1.id, issue2.id], assigns[:issues].map(&:id)
3745 end
3745 end
3746
3746
3747 def test_bulk_update_with_failure_should_preserved_form_values
3747 def test_bulk_update_with_failure_should_preserved_form_values
3748 @request.session[:user_id] = 2
3748 @request.session[:user_id] = 2
3749 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2', :start_date => 'foo'}
3749 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2', :start_date => 'foo'}
3750
3750
3751 assert_response :success
3751 assert_response :success
3752 assert_template 'bulk_edit'
3752 assert_template 'bulk_edit'
3753 assert_select 'select[name=?]', 'issue[tracker_id]' do
3753 assert_select 'select[name=?]', 'issue[tracker_id]' do
3754 assert_select 'option[value=2][selected=selected]'
3754 assert_select 'option[value=2][selected=selected]'
3755 end
3755 end
3756 assert_select 'input[name=?][value=?]', 'issue[start_date]', 'foo'
3756 assert_select 'input[name=?][value=?]', 'issue[start_date]', 'foo'
3757 end
3757 end
3758
3758
3759 def test_get_bulk_copy
3759 def test_get_bulk_copy
3760 @request.session[:user_id] = 2
3760 @request.session[:user_id] = 2
3761 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3761 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3762 assert_response :success
3762 assert_response :success
3763 assert_template 'bulk_edit'
3763 assert_template 'bulk_edit'
3764
3764
3765 issues = assigns(:issues)
3765 issues = assigns(:issues)
3766 assert_not_nil issues
3766 assert_not_nil issues
3767 assert_equal [1, 2, 3], issues.map(&:id).sort
3767 assert_equal [1, 2, 3], issues.map(&:id).sort
3768
3768
3769 assert_select 'input[name=copy_attachments]'
3769 assert_select 'input[name=copy_attachments]'
3770 end
3770 end
3771
3771
3772 def test_bulk_copy_to_another_project
3772 def test_bulk_copy_to_another_project
3773 @request.session[:user_id] = 2
3773 @request.session[:user_id] = 2
3774 assert_difference 'Issue.count', 2 do
3774 assert_difference 'Issue.count', 2 do
3775 assert_no_difference 'Project.find(1).issues.count' do
3775 assert_no_difference 'Project.find(1).issues.count' do
3776 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3776 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3777 end
3777 end
3778 end
3778 end
3779 assert_redirected_to '/projects/ecookbook/issues'
3779 assert_redirected_to '/projects/ecookbook/issues'
3780
3780
3781 copies = Issue.order('id DESC').limit(issues.size)
3781 copies = Issue.order('id DESC').limit(issues.size)
3782 copies.each do |copy|
3782 copies.each do |copy|
3783 assert_equal 2, copy.project_id
3783 assert_equal 2, copy.project_id
3784 end
3784 end
3785 end
3785 end
3786
3786
3787 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3787 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3788 @request.session[:user_id] = 2
3788 @request.session[:user_id] = 2
3789 issues = [
3789 issues = [
3790 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
3790 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
3791 :priority_id => 2, :subject => 'issue 1', :author_id => 1,
3791 :priority_id => 2, :subject => 'issue 1', :author_id => 1,
3792 :assigned_to_id => nil),
3792 :assigned_to_id => nil),
3793 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2,
3793 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2,
3794 :priority_id => 1, :subject => 'issue 2', :author_id => 2,
3794 :priority_id => 1, :subject => 'issue 2', :author_id => 2,
3795 :assigned_to_id => 3)
3795 :assigned_to_id => 3)
3796 ]
3796 ]
3797 assert_difference 'Issue.count', issues.size do
3797 assert_difference 'Issue.count', issues.size do
3798 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3798 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3799 :issue => {
3799 :issue => {
3800 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3800 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3801 :status_id => '', :start_date => '', :due_date => ''
3801 :status_id => '', :start_date => '', :due_date => ''
3802 }
3802 }
3803 end
3803 end
3804
3804
3805 copies = Issue.order('id DESC').limit(issues.size)
3805 copies = Issue.order('id DESC').limit(issues.size)
3806 issues.each do |orig|
3806 issues.each do |orig|
3807 copy = copies.detect {|c| c.subject == orig.subject}
3807 copy = copies.detect {|c| c.subject == orig.subject}
3808 assert_not_nil copy
3808 assert_not_nil copy
3809 assert_equal orig.project_id, copy.project_id
3809 assert_equal orig.project_id, copy.project_id
3810 assert_equal orig.tracker_id, copy.tracker_id
3810 assert_equal orig.tracker_id, copy.tracker_id
3811 assert_equal orig.status_id, copy.status_id
3811 assert_equal orig.status_id, copy.status_id
3812 assert_equal orig.assigned_to_id, copy.assigned_to_id
3812 assert_equal orig.assigned_to_id, copy.assigned_to_id
3813 assert_equal orig.priority_id, copy.priority_id
3813 assert_equal orig.priority_id, copy.priority_id
3814 end
3814 end
3815 end
3815 end
3816
3816
3817 def test_bulk_copy_should_allow_changing_the_issue_attributes
3817 def test_bulk_copy_should_allow_changing_the_issue_attributes
3818 # Fixes random test failure with Mysql
3818 # Fixes random test failure with Mysql
3819 # where Issue.where(:project_id => 2).limit(2).order('id desc')
3819 # where Issue.where(:project_id => 2).limit(2).order('id desc')
3820 # doesn't return the expected results
3820 # doesn't return the expected results
3821 Issue.delete_all("project_id=2")
3821 Issue.delete_all("project_id=2")
3822
3822
3823 @request.session[:user_id] = 2
3823 @request.session[:user_id] = 2
3824 assert_difference 'Issue.count', 2 do
3824 assert_difference 'Issue.count', 2 do
3825 assert_no_difference 'Project.find(1).issues.count' do
3825 assert_no_difference 'Project.find(1).issues.count' do
3826 post :bulk_update, :ids => [1, 2], :copy => '1',
3826 post :bulk_update, :ids => [1, 2], :copy => '1',
3827 :issue => {
3827 :issue => {
3828 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3828 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3829 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3829 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3830 }
3830 }
3831 end
3831 end
3832 end
3832 end
3833
3833
3834 copied_issues = Issue.where(:project_id => 2).limit(2).order('id desc').to_a
3834 copied_issues = Issue.where(:project_id => 2).limit(2).order('id desc').to_a
3835 assert_equal 2, copied_issues.size
3835 assert_equal 2, copied_issues.size
3836 copied_issues.each do |issue|
3836 copied_issues.each do |issue|
3837 assert_equal 2, issue.project_id, "Project is incorrect"
3837 assert_equal 2, issue.project_id, "Project is incorrect"
3838 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3838 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3839 assert_equal 1, issue.status_id, "Status is incorrect"
3839 assert_equal 1, issue.status_id, "Status is incorrect"
3840 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3840 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3841 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3841 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3842 end
3842 end
3843 end
3843 end
3844
3844
3845 def test_bulk_copy_should_allow_adding_a_note
3845 def test_bulk_copy_should_allow_adding_a_note
3846 @request.session[:user_id] = 2
3846 @request.session[:user_id] = 2
3847 assert_difference 'Issue.count', 1 do
3847 assert_difference 'Issue.count', 1 do
3848 post :bulk_update, :ids => [1], :copy => '1',
3848 post :bulk_update, :ids => [1], :copy => '1',
3849 :notes => 'Copying one issue',
3849 :notes => 'Copying one issue',
3850 :issue => {
3850 :issue => {
3851 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3851 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3852 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3852 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3853 }
3853 }
3854 end
3854 end
3855 issue = Issue.order('id DESC').first
3855 issue = Issue.order('id DESC').first
3856 assert_equal 1, issue.journals.size
3856 assert_equal 1, issue.journals.size
3857 journal = issue.journals.first
3857 journal = issue.journals.first
3858 assert_equal 1, journal.details.size
3858 assert_equal 1, journal.details.size
3859 assert_equal 'Copying one issue', journal.notes
3859 assert_equal 'Copying one issue', journal.notes
3860 end
3860 end
3861
3861
3862 def test_bulk_copy_should_allow_not_copying_the_attachments
3862 def test_bulk_copy_should_allow_not_copying_the_attachments
3863 attachment_count = Issue.find(3).attachments.size
3863 attachment_count = Issue.find(3).attachments.size
3864 assert attachment_count > 0
3864 assert attachment_count > 0
3865 @request.session[:user_id] = 2
3865 @request.session[:user_id] = 2
3866
3866
3867 assert_difference 'Issue.count', 1 do
3867 assert_difference 'Issue.count', 1 do
3868 assert_no_difference 'Attachment.count' do
3868 assert_no_difference 'Attachment.count' do
3869 post :bulk_update, :ids => [3], :copy => '1',
3869 post :bulk_update, :ids => [3], :copy => '1',
3870 :issue => {
3870 :issue => {
3871 :project_id => ''
3871 :project_id => ''
3872 }
3872 }
3873 end
3873 end
3874 end
3874 end
3875 end
3875 end
3876
3876
3877 def test_bulk_copy_should_allow_copying_the_attachments
3877 def test_bulk_copy_should_allow_copying_the_attachments
3878 attachment_count = Issue.find(3).attachments.size
3878 attachment_count = Issue.find(3).attachments.size
3879 assert attachment_count > 0
3879 assert attachment_count > 0
3880 @request.session[:user_id] = 2
3880 @request.session[:user_id] = 2
3881
3881
3882 assert_difference 'Issue.count', 1 do
3882 assert_difference 'Issue.count', 1 do
3883 assert_difference 'Attachment.count', attachment_count do
3883 assert_difference 'Attachment.count', attachment_count do
3884 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3884 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3885 :issue => {
3885 :issue => {
3886 :project_id => ''
3886 :project_id => ''
3887 }
3887 }
3888 end
3888 end
3889 end
3889 end
3890 end
3890 end
3891
3891
3892 def test_bulk_copy_should_add_relations_with_copied_issues
3892 def test_bulk_copy_should_add_relations_with_copied_issues
3893 @request.session[:user_id] = 2
3893 @request.session[:user_id] = 2
3894
3894
3895 assert_difference 'Issue.count', 2 do
3895 assert_difference 'Issue.count', 2 do
3896 assert_difference 'IssueRelation.count', 2 do
3896 assert_difference 'IssueRelation.count', 2 do
3897 post :bulk_update, :ids => [1, 3], :copy => '1',
3897 post :bulk_update, :ids => [1, 3], :copy => '1',
3898 :issue => {
3898 :issue => {
3899 :project_id => '1'
3899 :project_id => '1'
3900 }
3900 }
3901 end
3901 end
3902 end
3902 end
3903 end
3903 end
3904
3904
3905 def test_bulk_copy_should_allow_not_copying_the_subtasks
3905 def test_bulk_copy_should_allow_not_copying_the_subtasks
3906 issue = Issue.generate_with_descendants!
3906 issue = Issue.generate_with_descendants!
3907 @request.session[:user_id] = 2
3907 @request.session[:user_id] = 2
3908
3908
3909 assert_difference 'Issue.count', 1 do
3909 assert_difference 'Issue.count', 1 do
3910 post :bulk_update, :ids => [issue.id], :copy => '1',
3910 post :bulk_update, :ids => [issue.id], :copy => '1',
3911 :issue => {
3911 :issue => {
3912 :project_id => ''
3912 :project_id => ''
3913 }
3913 }
3914 end
3914 end
3915 end
3915 end
3916
3916
3917 def test_bulk_copy_should_allow_copying_the_subtasks
3917 def test_bulk_copy_should_allow_copying_the_subtasks
3918 issue = Issue.generate_with_descendants!
3918 issue = Issue.generate_with_descendants!
3919 count = issue.descendants.count
3919 count = issue.descendants.count
3920 @request.session[:user_id] = 2
3920 @request.session[:user_id] = 2
3921
3921
3922 assert_difference 'Issue.count', count+1 do
3922 assert_difference 'Issue.count', count+1 do
3923 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3923 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3924 :issue => {
3924 :issue => {
3925 :project_id => ''
3925 :project_id => ''
3926 }
3926 }
3927 end
3927 end
3928 copy = Issue.where(:parent_id => nil).order("id DESC").first
3928 copy = Issue.where(:parent_id => nil).order("id DESC").first
3929 assert_equal count, copy.descendants.count
3929 assert_equal count, copy.descendants.count
3930 end
3930 end
3931
3931
3932 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3932 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3933 issue = Issue.generate_with_descendants!
3933 issue = Issue.generate_with_descendants!
3934 count = issue.descendants.count
3934 count = issue.descendants.count
3935 @request.session[:user_id] = 2
3935 @request.session[:user_id] = 2
3936
3936
3937 assert_difference 'Issue.count', count+1 do
3937 assert_difference 'Issue.count', count+1 do
3938 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3938 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3939 :issue => {
3939 :issue => {
3940 :project_id => ''
3940 :project_id => ''
3941 }
3941 }
3942 end
3942 end
3943 copy = Issue.where(:parent_id => nil).order("id DESC").first
3943 copy = Issue.where(:parent_id => nil).order("id DESC").first
3944 assert_equal count, copy.descendants.count
3944 assert_equal count, copy.descendants.count
3945 end
3945 end
3946
3946
3947 def test_bulk_copy_to_another_project_should_follow_when_needed
3947 def test_bulk_copy_to_another_project_should_follow_when_needed
3948 @request.session[:user_id] = 2
3948 @request.session[:user_id] = 2
3949 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3949 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3950 issue = Issue.order('id DESC').first
3950 issue = Issue.order('id DESC').first
3951 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3951 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3952 end
3952 end
3953
3953
3954 def test_bulk_copy_with_all_failures_should_display_errors
3954 def test_bulk_copy_with_all_failures_should_display_errors
3955 @request.session[:user_id] = 2
3955 @request.session[:user_id] = 2
3956 post :bulk_update, :ids => [1, 2], :copy => '1', :issue => {:start_date => 'foo'}
3956 post :bulk_update, :ids => [1, 2], :copy => '1', :issue => {:start_date => 'foo'}
3957
3957
3958 assert_response :success
3958 assert_response :success
3959 end
3959 end
3960
3960
3961 def test_destroy_issue_with_no_time_entries
3961 def test_destroy_issue_with_no_time_entries
3962 assert_nil TimeEntry.find_by_issue_id(2)
3962 assert_nil TimeEntry.find_by_issue_id(2)
3963 @request.session[:user_id] = 2
3963 @request.session[:user_id] = 2
3964
3964
3965 assert_difference 'Issue.count', -1 do
3965 assert_difference 'Issue.count', -1 do
3966 delete :destroy, :id => 2
3966 delete :destroy, :id => 2
3967 end
3967 end
3968 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3968 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3969 assert_nil Issue.find_by_id(2)
3969 assert_nil Issue.find_by_id(2)
3970 end
3970 end
3971
3971
3972 def test_destroy_issues_with_time_entries
3972 def test_destroy_issues_with_time_entries
3973 @request.session[:user_id] = 2
3973 @request.session[:user_id] = 2
3974
3974
3975 assert_no_difference 'Issue.count' do
3975 assert_no_difference 'Issue.count' do
3976 delete :destroy, :ids => [1, 3]
3976 delete :destroy, :ids => [1, 3]
3977 end
3977 end
3978 assert_response :success
3978 assert_response :success
3979 assert_template 'destroy'
3979 assert_template 'destroy'
3980 assert_not_nil assigns(:hours)
3980 assert_not_nil assigns(:hours)
3981 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3981 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3982
3982
3983 assert_select 'form' do
3983 assert_select 'form' do
3984 assert_select 'input[name=_method][value=delete]'
3984 assert_select 'input[name=_method][value=delete]'
3985 end
3985 end
3986 end
3986 end
3987
3987
3988 def test_destroy_issues_and_destroy_time_entries
3988 def test_destroy_issues_and_destroy_time_entries
3989 @request.session[:user_id] = 2
3989 @request.session[:user_id] = 2
3990
3990
3991 assert_difference 'Issue.count', -2 do
3991 assert_difference 'Issue.count', -2 do
3992 assert_difference 'TimeEntry.count', -3 do
3992 assert_difference 'TimeEntry.count', -3 do
3993 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3993 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3994 end
3994 end
3995 end
3995 end
3996 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3996 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3997 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3997 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3998 assert_nil TimeEntry.find_by_id([1, 2])
3998 assert_nil TimeEntry.find_by_id([1, 2])
3999 end
3999 end
4000
4000
4001 def test_destroy_issues_and_assign_time_entries_to_project
4001 def test_destroy_issues_and_assign_time_entries_to_project
4002 @request.session[:user_id] = 2
4002 @request.session[:user_id] = 2
4003
4003
4004 assert_difference 'Issue.count', -2 do
4004 assert_difference 'Issue.count', -2 do
4005 assert_no_difference 'TimeEntry.count' do
4005 assert_no_difference 'TimeEntry.count' do
4006 delete :destroy, :ids => [1, 3], :todo => 'nullify'
4006 delete :destroy, :ids => [1, 3], :todo => 'nullify'
4007 end
4007 end
4008 end
4008 end
4009 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
4009 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
4010 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
4010 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
4011 assert_nil TimeEntry.find(1).issue_id
4011 assert_nil TimeEntry.find(1).issue_id
4012 assert_nil TimeEntry.find(2).issue_id
4012 assert_nil TimeEntry.find(2).issue_id
4013 end
4013 end
4014
4014
4015 def test_destroy_issues_and_reassign_time_entries_to_another_issue
4015 def test_destroy_issues_and_reassign_time_entries_to_another_issue
4016 @request.session[:user_id] = 2
4016 @request.session[:user_id] = 2
4017
4017
4018 assert_difference 'Issue.count', -2 do
4018 assert_difference 'Issue.count', -2 do
4019 assert_no_difference 'TimeEntry.count' do
4019 assert_no_difference 'TimeEntry.count' do
4020 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
4020 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
4021 end
4021 end
4022 end
4022 end
4023 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
4023 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
4024 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
4024 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
4025 assert_equal 2, TimeEntry.find(1).issue_id
4025 assert_equal 2, TimeEntry.find(1).issue_id
4026 assert_equal 2, TimeEntry.find(2).issue_id
4026 assert_equal 2, TimeEntry.find(2).issue_id
4027 end
4027 end
4028
4028
4029 def test_destroy_issues_from_different_projects
4029 def test_destroy_issues_from_different_projects
4030 @request.session[:user_id] = 2
4030 @request.session[:user_id] = 2
4031
4031
4032 assert_difference 'Issue.count', -3 do
4032 assert_difference 'Issue.count', -3 do
4033 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
4033 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
4034 end
4034 end
4035 assert_redirected_to :controller => 'issues', :action => 'index'
4035 assert_redirected_to :controller => 'issues', :action => 'index'
4036 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
4036 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
4037 end
4037 end
4038
4038
4039 def test_destroy_parent_and_child_issues
4039 def test_destroy_parent_and_child_issues
4040 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
4040 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
4041 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
4041 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
4042 assert child.is_descendant_of?(parent.reload)
4042 assert child.is_descendant_of?(parent.reload)
4043
4043
4044 @request.session[:user_id] = 2
4044 @request.session[:user_id] = 2
4045 assert_difference 'Issue.count', -2 do
4045 assert_difference 'Issue.count', -2 do
4046 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
4046 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
4047 end
4047 end
4048 assert_response 302
4048 assert_response 302
4049 end
4049 end
4050
4050
4051 def test_destroy_invalid_should_respond_with_404
4051 def test_destroy_invalid_should_respond_with_404
4052 @request.session[:user_id] = 2
4052 @request.session[:user_id] = 2
4053 assert_no_difference 'Issue.count' do
4053 assert_no_difference 'Issue.count' do
4054 delete :destroy, :id => 999
4054 delete :destroy, :id => 999
4055 end
4055 end
4056 assert_response 404
4056 assert_response 404
4057 end
4057 end
4058
4058
4059 def test_default_search_scope
4059 def test_default_search_scope
4060 get :index
4060 get :index
4061
4061
4062 assert_select 'div#quick-search form' do
4062 assert_select 'div#quick-search form' do
4063 assert_select 'input[name=issues][value=1][type=hidden]'
4063 assert_select 'input[name=issues][value=1][type=hidden]'
4064 end
4064 end
4065 end
4065 end
4066 end
4066 end
@@ -1,94 +1,85
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 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">Alpha</a>', link_to_version(Version.find(5))
50 assert_equal '<a href="/versions/5" title="07/01/2006">OnlineStore - 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">Alpha</a>', link_to_version(version)
57 assert_equal '<a href="/versions/5">OnlineStore - 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 'Alpha', link_to_version(Version.find(5))
61 assert_equal 'OnlineStore - 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 "0.1", format_version_name(Version.find(1))
74 assert_equal "eCookbook - 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)
82 end
75 end
83
76
84 def test_format_version_name_for_shared_version_should_display_project_name
77 def test_format_version_name_for_system_version
85 version = Version.find(1)
78 assert_equal "OnlineStore - Systemwide visible version", format_version_name(Version.find(7))
86 version.sharing = 'system'
87 assert_equal "eCookbook - 0.1", format_version_name(version)
88 end
79 end
89
80
90 def test_version_options_for_select_with_no_versions
81 def test_version_options_for_select_with_no_versions
91 assert_equal '', version_options_for_select([])
82 assert_equal '', version_options_for_select([])
92 assert_equal '', version_options_for_select([], Version.find(1))
83 assert_equal '', version_options_for_select([], Version.find(1))
93 end
84 end
94 end
85 end
General Comments 0
You need to be logged in to leave comments. Login now