##// END OF EJS Templates
Adds a sortable "Project" column to the issue list....
Jean-Philippe Lang -
r2498:03572ec5692c
parent child
Show More
@@ -1,55 +1,55
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module QueriesHelper
18 module QueriesHelper
19
19
20 def operators_for_select(filter_type)
20 def operators_for_select(filter_type)
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
21 Query.operators_by_filter_type[filter_type].collect {|o| [l(Query.operators[o]), o]}
22 end
22 end
23
23
24 def column_header(column)
24 def column_header(column)
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
25 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
26 :default_order => column.default_order) :
26 :default_order => column.default_order) :
27 content_tag('th', column.caption)
27 content_tag('th', column.caption)
28 end
28 end
29
29
30 def column_content(column, issue)
30 def column_content(column, issue)
31 if column.is_a?(QueryCustomFieldColumn)
31 if column.is_a?(QueryCustomFieldColumn)
32 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
32 cv = issue.custom_values.detect {|v| v.custom_field_id == column.custom_field.id}
33 show_value(cv)
33 show_value(cv)
34 else
34 else
35 value = issue.send(column.name)
35 value = issue.send(column.name)
36 if value.is_a?(Date)
36 if value.is_a?(Date)
37 format_date(value)
37 format_date(value)
38 elsif value.is_a?(Time)
38 elsif value.is_a?(Time)
39 format_time(value)
39 format_time(value)
40 else
40 else
41 case column.name
41 case column.name
42 when :subject
42 when :subject
43 h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') +
43 h((!@project.nil? && @project != issue.project) ? "#{issue.project.name} - " : '') +
44 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
44 link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
45 when :done_ratio
45 when :done_ratio
46 progress_bar(value, :width => '80px')
46 progress_bar(value, :width => '80px')
47 when :fixed_version
47 when :fixed_version
48 link_to(h(value), { :controller => 'versions', :action => 'show', :id => issue.fixed_version_id })
48 link_to(h(value), { :controller => 'versions', :action => 'show', :id => issue.fixed_version_id })
49 else
49 else
50 h(value)
50 h(value)
51 end
51 end
52 end
52 end
53 end
53 end
54 end
54 end
55 end
55 end
@@ -1,411 +1,415
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :default_order
19 attr_accessor :name, :sortable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.default_order = options[:default_order]
25 self.default_order = options[:default_order]
26 end
26 end
27
27
28 def caption
28 def caption
29 l("field_#{name}")
29 l("field_#{name}")
30 end
30 end
31 end
31 end
32
32
33 class QueryCustomFieldColumn < QueryColumn
33 class QueryCustomFieldColumn < QueryColumn
34
34
35 def initialize(custom_field)
35 def initialize(custom_field)
36 self.name = "cf_#{custom_field.id}".to_sym
36 self.name = "cf_#{custom_field.id}".to_sym
37 self.sortable = custom_field.order_statement || false
37 self.sortable = custom_field.order_statement || false
38 @cf = custom_field
38 @cf = custom_field
39 end
39 end
40
40
41 def caption
41 def caption
42 @cf.name
42 @cf.name
43 end
43 end
44
44
45 def custom_field
45 def custom_field
46 @cf
46 @cf
47 end
47 end
48 end
48 end
49
49
50 class Query < ActiveRecord::Base
50 class Query < ActiveRecord::Base
51 belongs_to :project
51 belongs_to :project
52 belongs_to :user
52 belongs_to :user
53 serialize :filters
53 serialize :filters
54 serialize :column_names
54 serialize :column_names
55
55
56 attr_protected :project_id, :user_id
56 attr_protected :project_id, :user_id
57
57
58 validates_presence_of :name, :on => :save
58 validates_presence_of :name, :on => :save
59 validates_length_of :name, :maximum => 255
59 validates_length_of :name, :maximum => 255
60
60
61 @@operators = { "=" => :label_equals,
61 @@operators = { "=" => :label_equals,
62 "!" => :label_not_equals,
62 "!" => :label_not_equals,
63 "o" => :label_open_issues,
63 "o" => :label_open_issues,
64 "c" => :label_closed_issues,
64 "c" => :label_closed_issues,
65 "!*" => :label_none,
65 "!*" => :label_none,
66 "*" => :label_all,
66 "*" => :label_all,
67 ">=" => '>=',
67 ">=" => '>=',
68 "<=" => '<=',
68 "<=" => '<=',
69 "<t+" => :label_in_less_than,
69 "<t+" => :label_in_less_than,
70 ">t+" => :label_in_more_than,
70 ">t+" => :label_in_more_than,
71 "t+" => :label_in,
71 "t+" => :label_in,
72 "t" => :label_today,
72 "t" => :label_today,
73 "w" => :label_this_week,
73 "w" => :label_this_week,
74 ">t-" => :label_less_than_ago,
74 ">t-" => :label_less_than_ago,
75 "<t-" => :label_more_than_ago,
75 "<t-" => :label_more_than_ago,
76 "t-" => :label_ago,
76 "t-" => :label_ago,
77 "~" => :label_contains,
77 "~" => :label_contains,
78 "!~" => :label_not_contains }
78 "!~" => :label_not_contains }
79
79
80 cattr_reader :operators
80 cattr_reader :operators
81
81
82 @@operators_by_filter_type = { :list => [ "=", "!" ],
82 @@operators_by_filter_type = { :list => [ "=", "!" ],
83 :list_status => [ "o", "=", "!", "c", "*" ],
83 :list_status => [ "o", "=", "!", "c", "*" ],
84 :list_optional => [ "=", "!", "!*", "*" ],
84 :list_optional => [ "=", "!", "!*", "*" ],
85 :list_subprojects => [ "*", "!*", "=" ],
85 :list_subprojects => [ "*", "!*", "=" ],
86 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
86 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
87 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
87 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
88 :string => [ "=", "~", "!", "!~" ],
88 :string => [ "=", "~", "!", "!~" ],
89 :text => [ "~", "!~" ],
89 :text => [ "~", "!~" ],
90 :integer => [ "=", ">=", "<=", "!*", "*" ] }
90 :integer => [ "=", ">=", "<=", "!*", "*" ] }
91
91
92 cattr_reader :operators_by_filter_type
92 cattr_reader :operators_by_filter_type
93
93
94 @@available_columns = [
94 @@available_columns = [
95 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name"),
95 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
96 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
96 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
97 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
97 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
98 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
98 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
99 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
99 QueryColumn.new(:author),
100 QueryColumn.new(:author),
100 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname"]),
101 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname"]),
101 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
102 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
102 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
103 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
103 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc'),
104 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc'),
104 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
105 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
105 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
106 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
106 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
107 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
107 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
108 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
108 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
109 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
109 ]
110 ]
110 cattr_reader :available_columns
111 cattr_reader :available_columns
111
112
112 def initialize(attributes = nil)
113 def initialize(attributes = nil)
113 super attributes
114 super attributes
114 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
115 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
115 end
116 end
116
117
117 def after_initialize
118 def after_initialize
118 # Store the fact that project is nil (used in #editable_by?)
119 # Store the fact that project is nil (used in #editable_by?)
119 @is_for_all = project.nil?
120 @is_for_all = project.nil?
120 end
121 end
121
122
122 def validate
123 def validate
123 filters.each_key do |field|
124 filters.each_key do |field|
124 errors.add label_for(field), :blank unless
125 errors.add label_for(field), :blank unless
125 # filter requires one or more values
126 # filter requires one or more values
126 (values_for(field) and !values_for(field).first.blank?) or
127 (values_for(field) and !values_for(field).first.blank?) or
127 # filter doesn't require any value
128 # filter doesn't require any value
128 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
129 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
129 end if filters
130 end if filters
130 end
131 end
131
132
132 def editable_by?(user)
133 def editable_by?(user)
133 return false unless user
134 return false unless user
134 # Admin can edit them all and regular users can edit their private queries
135 # Admin can edit them all and regular users can edit their private queries
135 return true if user.admin? || (!is_public && self.user_id == user.id)
136 return true if user.admin? || (!is_public && self.user_id == user.id)
136 # Members can not edit public queries that are for all project (only admin is allowed to)
137 # Members can not edit public queries that are for all project (only admin is allowed to)
137 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
138 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
138 end
139 end
139
140
140 def available_filters
141 def available_filters
141 return @available_filters if @available_filters
142 return @available_filters if @available_filters
142
143
143 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
144 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
144
145
145 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
146 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
146 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
147 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
147 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
148 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
148 "subject" => { :type => :text, :order => 8 },
149 "subject" => { :type => :text, :order => 8 },
149 "created_on" => { :type => :date_past, :order => 9 },
150 "created_on" => { :type => :date_past, :order => 9 },
150 "updated_on" => { :type => :date_past, :order => 10 },
151 "updated_on" => { :type => :date_past, :order => 10 },
151 "start_date" => { :type => :date, :order => 11 },
152 "start_date" => { :type => :date, :order => 11 },
152 "due_date" => { :type => :date, :order => 12 },
153 "due_date" => { :type => :date, :order => 12 },
153 "estimated_hours" => { :type => :integer, :order => 13 },
154 "estimated_hours" => { :type => :integer, :order => 13 },
154 "done_ratio" => { :type => :integer, :order => 14 }}
155 "done_ratio" => { :type => :integer, :order => 14 }}
155
156
156 user_values = []
157 user_values = []
157 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
158 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
158 if project
159 if project
159 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
160 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
160 else
161 else
161 # members of the user's projects
162 # members of the user's projects
162 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
163 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
163 end
164 end
164 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
165 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
165 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
166 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
166
167
167 if User.current.logged?
168 if User.current.logged?
168 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
169 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
169 end
170 end
170
171
171 if project
172 if project
172 # project specific filters
173 # project specific filters
173 unless @project.issue_categories.empty?
174 unless @project.issue_categories.empty?
174 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
175 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
175 end
176 end
176 unless @project.versions.empty?
177 unless @project.versions.empty?
177 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
178 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
178 end
179 end
179 unless @project.descendants.active.empty?
180 unless @project.descendants.active.empty?
180 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
181 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
181 end
182 end
182 add_custom_fields_filters(@project.all_issue_custom_fields)
183 add_custom_fields_filters(@project.all_issue_custom_fields)
183 else
184 else
184 # global filters for cross project issue list
185 # global filters for cross project issue list
185 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
186 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
186 end
187 end
187 @available_filters
188 @available_filters
188 end
189 end
189
190
190 def add_filter(field, operator, values)
191 def add_filter(field, operator, values)
191 # values must be an array
192 # values must be an array
192 return unless values and values.is_a? Array # and !values.first.empty?
193 return unless values and values.is_a? Array # and !values.first.empty?
193 # check if field is defined as an available filter
194 # check if field is defined as an available filter
194 if available_filters.has_key? field
195 if available_filters.has_key? field
195 filter_options = available_filters[field]
196 filter_options = available_filters[field]
196 # check if operator is allowed for that filter
197 # check if operator is allowed for that filter
197 #if @@operators_by_filter_type[filter_options[:type]].include? operator
198 #if @@operators_by_filter_type[filter_options[:type]].include? operator
198 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
199 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
199 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
200 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
200 #end
201 #end
201 filters[field] = {:operator => operator, :values => values }
202 filters[field] = {:operator => operator, :values => values }
202 end
203 end
203 end
204 end
204
205
205 def add_short_filter(field, expression)
206 def add_short_filter(field, expression)
206 return unless expression
207 return unless expression
207 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
208 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
208 add_filter field, (parms[0] || "="), [parms[1] || ""]
209 add_filter field, (parms[0] || "="), [parms[1] || ""]
209 end
210 end
210
211
211 def has_filter?(field)
212 def has_filter?(field)
212 filters and filters[field]
213 filters and filters[field]
213 end
214 end
214
215
215 def operator_for(field)
216 def operator_for(field)
216 has_filter?(field) ? filters[field][:operator] : nil
217 has_filter?(field) ? filters[field][:operator] : nil
217 end
218 end
218
219
219 def values_for(field)
220 def values_for(field)
220 has_filter?(field) ? filters[field][:values] : nil
221 has_filter?(field) ? filters[field][:values] : nil
221 end
222 end
222
223
223 def label_for(field)
224 def label_for(field)
224 label = available_filters[field][:name] if available_filters.has_key?(field)
225 label = available_filters[field][:name] if available_filters.has_key?(field)
225 label ||= field.gsub(/\_id$/, "")
226 label ||= field.gsub(/\_id$/, "")
226 end
227 end
227
228
228 def available_columns
229 def available_columns
229 return @available_columns if @available_columns
230 return @available_columns if @available_columns
230 @available_columns = Query.available_columns
231 @available_columns = Query.available_columns
231 @available_columns += (project ?
232 @available_columns += (project ?
232 project.all_issue_custom_fields :
233 project.all_issue_custom_fields :
233 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
234 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
234 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
235 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
235 end
236 end
236
237
237 def columns
238 def columns
238 if has_default_columns?
239 if has_default_columns?
239 available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
240 available_columns.select do |c|
241 # Adds the project column by default for cross-project lists
242 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
243 end
240 else
244 else
241 # preserve the column_names order
245 # preserve the column_names order
242 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
246 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
243 end
247 end
244 end
248 end
245
249
246 def column_names=(names)
250 def column_names=(names)
247 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
251 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
248 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
252 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
249 write_attribute(:column_names, names)
253 write_attribute(:column_names, names)
250 end
254 end
251
255
252 def has_column?(column)
256 def has_column?(column)
253 column_names && column_names.include?(column.name)
257 column_names && column_names.include?(column.name)
254 end
258 end
255
259
256 def has_default_columns?
260 def has_default_columns?
257 column_names.nil? || column_names.empty?
261 column_names.nil? || column_names.empty?
258 end
262 end
259
263
260 def project_statement
264 def project_statement
261 project_clauses = []
265 project_clauses = []
262 if project && !@project.descendants.active.empty?
266 if project && !@project.descendants.active.empty?
263 ids = [project.id]
267 ids = [project.id]
264 if has_filter?("subproject_id")
268 if has_filter?("subproject_id")
265 case operator_for("subproject_id")
269 case operator_for("subproject_id")
266 when '='
270 when '='
267 # include the selected subprojects
271 # include the selected subprojects
268 ids += values_for("subproject_id").each(&:to_i)
272 ids += values_for("subproject_id").each(&:to_i)
269 when '!*'
273 when '!*'
270 # main project only
274 # main project only
271 else
275 else
272 # all subprojects
276 # all subprojects
273 ids += project.descendants.collect(&:id)
277 ids += project.descendants.collect(&:id)
274 end
278 end
275 elsif Setting.display_subprojects_issues?
279 elsif Setting.display_subprojects_issues?
276 ids += project.descendants.collect(&:id)
280 ids += project.descendants.collect(&:id)
277 end
281 end
278 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
282 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
279 elsif project
283 elsif project
280 project_clauses << "#{Project.table_name}.id = %d" % project.id
284 project_clauses << "#{Project.table_name}.id = %d" % project.id
281 end
285 end
282 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
286 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
283 project_clauses.join(' AND ')
287 project_clauses.join(' AND ')
284 end
288 end
285
289
286 def statement
290 def statement
287 # filters clauses
291 # filters clauses
288 filters_clauses = []
292 filters_clauses = []
289 filters.each_key do |field|
293 filters.each_key do |field|
290 next if field == "subproject_id"
294 next if field == "subproject_id"
291 v = values_for(field).clone
295 v = values_for(field).clone
292 next unless v and !v.empty?
296 next unless v and !v.empty?
293 operator = operator_for(field)
297 operator = operator_for(field)
294
298
295 # "me" value subsitution
299 # "me" value subsitution
296 if %w(assigned_to_id author_id watcher_id).include?(field)
300 if %w(assigned_to_id author_id watcher_id).include?(field)
297 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
301 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
298 end
302 end
299
303
300 sql = ''
304 sql = ''
301 if field =~ /^cf_(\d+)$/
305 if field =~ /^cf_(\d+)$/
302 # custom field
306 # custom field
303 db_table = CustomValue.table_name
307 db_table = CustomValue.table_name
304 db_field = 'value'
308 db_field = 'value'
305 is_custom_filter = true
309 is_custom_filter = true
306 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
310 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
307 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
311 sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
308 elsif field == 'watcher_id'
312 elsif field == 'watcher_id'
309 db_table = Watcher.table_name
313 db_table = Watcher.table_name
310 db_field = 'user_id'
314 db_field = 'user_id'
311 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
315 sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
312 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
316 sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
313 else
317 else
314 # regular field
318 # regular field
315 db_table = Issue.table_name
319 db_table = Issue.table_name
316 db_field = field
320 db_field = field
317 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
321 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
318 end
322 end
319 filters_clauses << sql
323 filters_clauses << sql
320
324
321 end if filters and valid?
325 end if filters and valid?
322
326
323 (filters_clauses << project_statement).join(' AND ')
327 (filters_clauses << project_statement).join(' AND ')
324 end
328 end
325
329
326 private
330 private
327
331
328 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
332 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
329 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
333 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
330 sql = ''
334 sql = ''
331 case operator
335 case operator
332 when "="
336 when "="
333 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
337 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
334 when "!"
338 when "!"
335 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
339 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
336 when "!*"
340 when "!*"
337 sql = "#{db_table}.#{db_field} IS NULL"
341 sql = "#{db_table}.#{db_field} IS NULL"
338 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
342 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
339 when "*"
343 when "*"
340 sql = "#{db_table}.#{db_field} IS NOT NULL"
344 sql = "#{db_table}.#{db_field} IS NOT NULL"
341 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
345 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
342 when ">="
346 when ">="
343 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
347 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
344 when "<="
348 when "<="
345 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
349 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
346 when "o"
350 when "o"
347 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
351 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
348 when "c"
352 when "c"
349 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
353 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
350 when ">t-"
354 when ">t-"
351 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
355 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
352 when "<t-"
356 when "<t-"
353 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
357 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
354 when "t-"
358 when "t-"
355 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
359 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
356 when ">t+"
360 when ">t+"
357 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
361 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
358 when "<t+"
362 when "<t+"
359 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
363 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
360 when "t+"
364 when "t+"
361 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
365 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
362 when "t"
366 when "t"
363 sql = date_range_clause(db_table, db_field, 0, 0)
367 sql = date_range_clause(db_table, db_field, 0, 0)
364 when "w"
368 when "w"
365 from = l(:general_first_day_of_week) == '7' ?
369 from = l(:general_first_day_of_week) == '7' ?
366 # week starts on sunday
370 # week starts on sunday
367 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
371 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
368 # week starts on monday (Rails default)
372 # week starts on monday (Rails default)
369 Time.now.at_beginning_of_week
373 Time.now.at_beginning_of_week
370 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
374 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
371 when "~"
375 when "~"
372 sql = "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(value.first)}%'"
376 sql = "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(value.first)}%'"
373 when "!~"
377 when "!~"
374 sql = "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(value.first)}%'"
378 sql = "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(value.first)}%'"
375 end
379 end
376
380
377 return sql
381 return sql
378 end
382 end
379
383
380 def add_custom_fields_filters(custom_fields)
384 def add_custom_fields_filters(custom_fields)
381 @available_filters ||= {}
385 @available_filters ||= {}
382
386
383 custom_fields.select(&:is_filter?).each do |field|
387 custom_fields.select(&:is_filter?).each do |field|
384 case field.field_format
388 case field.field_format
385 when "text"
389 when "text"
386 options = { :type => :text, :order => 20 }
390 options = { :type => :text, :order => 20 }
387 when "list"
391 when "list"
388 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
392 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
389 when "date"
393 when "date"
390 options = { :type => :date, :order => 20 }
394 options = { :type => :date, :order => 20 }
391 when "bool"
395 when "bool"
392 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
396 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
393 else
397 else
394 options = { :type => :string, :order => 20 }
398 options = { :type => :string, :order => 20 }
395 end
399 end
396 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
400 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
397 end
401 end
398 end
402 end
399
403
400 # Returns a SQL clause for a date or datetime field.
404 # Returns a SQL clause for a date or datetime field.
401 def date_range_clause(table, field, from, to)
405 def date_range_clause(table, field, from, to)
402 s = []
406 s = []
403 if from
407 if from
404 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
408 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
405 end
409 end
406 if to
410 if to
407 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
411 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
408 end
412 end
409 s.join(' AND ')
413 s.join(' AND ')
410 end
414 end
411 end
415 end
@@ -1,985 +1,989
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :versions,
31 :versions,
32 :trackers,
32 :trackers,
33 :projects_trackers,
33 :projects_trackers,
34 :issue_categories,
34 :issue_categories,
35 :enabled_modules,
35 :enabled_modules,
36 :enumerations,
36 :enumerations,
37 :attachments,
37 :attachments,
38 :workflows,
38 :workflows,
39 :custom_fields,
39 :custom_fields,
40 :custom_values,
40 :custom_values,
41 :custom_fields_trackers,
41 :custom_fields_trackers,
42 :time_entries,
42 :time_entries,
43 :journals,
43 :journals,
44 :journal_details
44 :journal_details
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index_routing
53 def test_index_routing
54 assert_routing(
54 assert_routing(
55 {:method => :get, :path => '/issues'},
55 {:method => :get, :path => '/issues'},
56 :controller => 'issues', :action => 'index'
56 :controller => 'issues', :action => 'index'
57 )
57 )
58 end
58 end
59
59
60 def test_index
60 def test_index
61 Setting.default_language = 'en'
62
61 get :index
63 get :index
62 assert_response :success
64 assert_response :success
63 assert_template 'index.rhtml'
65 assert_template 'index.rhtml'
64 assert_not_nil assigns(:issues)
66 assert_not_nil assigns(:issues)
65 assert_nil assigns(:project)
67 assert_nil assigns(:project)
66 assert_tag :tag => 'a', :content => /Can't print recipes/
68 assert_tag :tag => 'a', :content => /Can't print recipes/
67 assert_tag :tag => 'a', :content => /Subproject issue/
69 assert_tag :tag => 'a', :content => /Subproject issue/
68 # private projects hidden
70 # private projects hidden
69 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
71 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
70 assert_no_tag :tag => 'a', :content => /Issue on project 2/
72 assert_no_tag :tag => 'a', :content => /Issue on project 2/
73 # project column
74 assert_tag :tag => 'th', :content => /Project/
71 end
75 end
72
76
73 def test_index_should_not_list_issues_when_module_disabled
77 def test_index_should_not_list_issues_when_module_disabled
74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
78 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 get :index
79 get :index
76 assert_response :success
80 assert_response :success
77 assert_template 'index.rhtml'
81 assert_template 'index.rhtml'
78 assert_not_nil assigns(:issues)
82 assert_not_nil assigns(:issues)
79 assert_nil assigns(:project)
83 assert_nil assigns(:project)
80 assert_no_tag :tag => 'a', :content => /Can't print recipes/
84 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 assert_tag :tag => 'a', :content => /Subproject issue/
85 assert_tag :tag => 'a', :content => /Subproject issue/
82 end
86 end
83
87
84 def test_index_with_project_routing
88 def test_index_with_project_routing
85 assert_routing(
89 assert_routing(
86 {:method => :get, :path => '/projects/23/issues'},
90 {:method => :get, :path => '/projects/23/issues'},
87 :controller => 'issues', :action => 'index', :project_id => '23'
91 :controller => 'issues', :action => 'index', :project_id => '23'
88 )
92 )
89 end
93 end
90
94
91 def test_index_should_not_list_issues_when_module_disabled
95 def test_index_should_not_list_issues_when_module_disabled
92 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
96 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
93 get :index
97 get :index
94 assert_response :success
98 assert_response :success
95 assert_template 'index.rhtml'
99 assert_template 'index.rhtml'
96 assert_not_nil assigns(:issues)
100 assert_not_nil assigns(:issues)
97 assert_nil assigns(:project)
101 assert_nil assigns(:project)
98 assert_no_tag :tag => 'a', :content => /Can't print recipes/
102 assert_no_tag :tag => 'a', :content => /Can't print recipes/
99 assert_tag :tag => 'a', :content => /Subproject issue/
103 assert_tag :tag => 'a', :content => /Subproject issue/
100 end
104 end
101
105
102 def test_index_with_project_routing
106 def test_index_with_project_routing
103 assert_routing(
107 assert_routing(
104 {:method => :get, :path => 'projects/23/issues'},
108 {:method => :get, :path => 'projects/23/issues'},
105 :controller => 'issues', :action => 'index', :project_id => '23'
109 :controller => 'issues', :action => 'index', :project_id => '23'
106 )
110 )
107 end
111 end
108
112
109 def test_index_with_project
113 def test_index_with_project
110 Setting.display_subprojects_issues = 0
114 Setting.display_subprojects_issues = 0
111 get :index, :project_id => 1
115 get :index, :project_id => 1
112 assert_response :success
116 assert_response :success
113 assert_template 'index.rhtml'
117 assert_template 'index.rhtml'
114 assert_not_nil assigns(:issues)
118 assert_not_nil assigns(:issues)
115 assert_tag :tag => 'a', :content => /Can't print recipes/
119 assert_tag :tag => 'a', :content => /Can't print recipes/
116 assert_no_tag :tag => 'a', :content => /Subproject issue/
120 assert_no_tag :tag => 'a', :content => /Subproject issue/
117 end
121 end
118
122
119 def test_index_with_project_and_subprojects
123 def test_index_with_project_and_subprojects
120 Setting.display_subprojects_issues = 1
124 Setting.display_subprojects_issues = 1
121 get :index, :project_id => 1
125 get :index, :project_id => 1
122 assert_response :success
126 assert_response :success
123 assert_template 'index.rhtml'
127 assert_template 'index.rhtml'
124 assert_not_nil assigns(:issues)
128 assert_not_nil assigns(:issues)
125 assert_tag :tag => 'a', :content => /Can't print recipes/
129 assert_tag :tag => 'a', :content => /Can't print recipes/
126 assert_tag :tag => 'a', :content => /Subproject issue/
130 assert_tag :tag => 'a', :content => /Subproject issue/
127 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
131 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
128 end
132 end
129
133
130 def test_index_with_project_and_subprojects_should_show_private_subprojects
134 def test_index_with_project_and_subprojects_should_show_private_subprojects
131 @request.session[:user_id] = 2
135 @request.session[:user_id] = 2
132 Setting.display_subprojects_issues = 1
136 Setting.display_subprojects_issues = 1
133 get :index, :project_id => 1
137 get :index, :project_id => 1
134 assert_response :success
138 assert_response :success
135 assert_template 'index.rhtml'
139 assert_template 'index.rhtml'
136 assert_not_nil assigns(:issues)
140 assert_not_nil assigns(:issues)
137 assert_tag :tag => 'a', :content => /Can't print recipes/
141 assert_tag :tag => 'a', :content => /Can't print recipes/
138 assert_tag :tag => 'a', :content => /Subproject issue/
142 assert_tag :tag => 'a', :content => /Subproject issue/
139 assert_tag :tag => 'a', :content => /Issue of a private subproject/
143 assert_tag :tag => 'a', :content => /Issue of a private subproject/
140 end
144 end
141
145
142 def test_index_with_project_routing_formatted
146 def test_index_with_project_routing_formatted
143 assert_routing(
147 assert_routing(
144 {:method => :get, :path => 'projects/23/issues.pdf'},
148 {:method => :get, :path => 'projects/23/issues.pdf'},
145 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
146 )
150 )
147 assert_routing(
151 assert_routing(
148 {:method => :get, :path => 'projects/23/issues.atom'},
152 {:method => :get, :path => 'projects/23/issues.atom'},
149 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
153 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
150 )
154 )
151 end
155 end
152
156
153 def test_index_with_project_and_filter
157 def test_index_with_project_and_filter
154 get :index, :project_id => 1, :set_filter => 1
158 get :index, :project_id => 1, :set_filter => 1
155 assert_response :success
159 assert_response :success
156 assert_template 'index.rhtml'
160 assert_template 'index.rhtml'
157 assert_not_nil assigns(:issues)
161 assert_not_nil assigns(:issues)
158 end
162 end
159
163
160 def test_index_csv_with_project
164 def test_index_csv_with_project
161 get :index, :format => 'csv'
165 get :index, :format => 'csv'
162 assert_response :success
166 assert_response :success
163 assert_not_nil assigns(:issues)
167 assert_not_nil assigns(:issues)
164 assert_equal 'text/csv', @response.content_type
168 assert_equal 'text/csv', @response.content_type
165
169
166 get :index, :project_id => 1, :format => 'csv'
170 get :index, :project_id => 1, :format => 'csv'
167 assert_response :success
171 assert_response :success
168 assert_not_nil assigns(:issues)
172 assert_not_nil assigns(:issues)
169 assert_equal 'text/csv', @response.content_type
173 assert_equal 'text/csv', @response.content_type
170 end
174 end
171
175
172 def test_index_formatted
176 def test_index_formatted
173 assert_routing(
177 assert_routing(
174 {:method => :get, :path => 'issues.pdf'},
178 {:method => :get, :path => 'issues.pdf'},
175 :controller => 'issues', :action => 'index', :format => 'pdf'
179 :controller => 'issues', :action => 'index', :format => 'pdf'
176 )
180 )
177 assert_routing(
181 assert_routing(
178 {:method => :get, :path => 'issues.atom'},
182 {:method => :get, :path => 'issues.atom'},
179 :controller => 'issues', :action => 'index', :format => 'atom'
183 :controller => 'issues', :action => 'index', :format => 'atom'
180 )
184 )
181 end
185 end
182
186
183 def test_index_pdf
187 def test_index_pdf
184 get :index, :format => 'pdf'
188 get :index, :format => 'pdf'
185 assert_response :success
189 assert_response :success
186 assert_not_nil assigns(:issues)
190 assert_not_nil assigns(:issues)
187 assert_equal 'application/pdf', @response.content_type
191 assert_equal 'application/pdf', @response.content_type
188
192
189 get :index, :project_id => 1, :format => 'pdf'
193 get :index, :project_id => 1, :format => 'pdf'
190 assert_response :success
194 assert_response :success
191 assert_not_nil assigns(:issues)
195 assert_not_nil assigns(:issues)
192 assert_equal 'application/pdf', @response.content_type
196 assert_equal 'application/pdf', @response.content_type
193 end
197 end
194
198
195 def test_index_sort
199 def test_index_sort
196 get :index, :sort_key => 'tracker'
200 get :index, :sort_key => 'tracker'
197 assert_response :success
201 assert_response :success
198
202
199 sort_params = @request.session['issuesindex_sort']
203 sort_params = @request.session['issuesindex_sort']
200 assert sort_params.is_a?(Hash)
204 assert sort_params.is_a?(Hash)
201 assert_equal 'tracker', sort_params[:key]
205 assert_equal 'tracker', sort_params[:key]
202 assert_equal 'ASC', sort_params[:order]
206 assert_equal 'ASC', sort_params[:order]
203 end
207 end
204
208
205 def test_gantt
209 def test_gantt
206 get :gantt, :project_id => 1
210 get :gantt, :project_id => 1
207 assert_response :success
211 assert_response :success
208 assert_template 'gantt.rhtml'
212 assert_template 'gantt.rhtml'
209 assert_not_nil assigns(:gantt)
213 assert_not_nil assigns(:gantt)
210 events = assigns(:gantt).events
214 events = assigns(:gantt).events
211 assert_not_nil events
215 assert_not_nil events
212 # Issue with start and due dates
216 # Issue with start and due dates
213 i = Issue.find(1)
217 i = Issue.find(1)
214 assert_not_nil i.due_date
218 assert_not_nil i.due_date
215 assert events.include?(Issue.find(1))
219 assert events.include?(Issue.find(1))
216 # Issue with without due date but targeted to a version with date
220 # Issue with without due date but targeted to a version with date
217 i = Issue.find(2)
221 i = Issue.find(2)
218 assert_nil i.due_date
222 assert_nil i.due_date
219 assert events.include?(i)
223 assert events.include?(i)
220 end
224 end
221
225
222 def test_cross_project_gantt
226 def test_cross_project_gantt
223 get :gantt
227 get :gantt
224 assert_response :success
228 assert_response :success
225 assert_template 'gantt.rhtml'
229 assert_template 'gantt.rhtml'
226 assert_not_nil assigns(:gantt)
230 assert_not_nil assigns(:gantt)
227 events = assigns(:gantt).events
231 events = assigns(:gantt).events
228 assert_not_nil events
232 assert_not_nil events
229 end
233 end
230
234
231 def test_gantt_export_to_pdf
235 def test_gantt_export_to_pdf
232 get :gantt, :project_id => 1, :format => 'pdf'
236 get :gantt, :project_id => 1, :format => 'pdf'
233 assert_response :success
237 assert_response :success
234 assert_equal 'application/pdf', @response.content_type
238 assert_equal 'application/pdf', @response.content_type
235 assert @response.body.starts_with?('%PDF')
239 assert @response.body.starts_with?('%PDF')
236 assert_not_nil assigns(:gantt)
240 assert_not_nil assigns(:gantt)
237 end
241 end
238
242
239 def test_cross_project_gantt_export_to_pdf
243 def test_cross_project_gantt_export_to_pdf
240 get :gantt, :format => 'pdf'
244 get :gantt, :format => 'pdf'
241 assert_response :success
245 assert_response :success
242 assert_equal 'application/pdf', @response.content_type
246 assert_equal 'application/pdf', @response.content_type
243 assert @response.body.starts_with?('%PDF')
247 assert @response.body.starts_with?('%PDF')
244 assert_not_nil assigns(:gantt)
248 assert_not_nil assigns(:gantt)
245 end
249 end
246
250
247 if Object.const_defined?(:Magick)
251 if Object.const_defined?(:Magick)
248 def test_gantt_image
252 def test_gantt_image
249 get :gantt, :project_id => 1, :format => 'png'
253 get :gantt, :project_id => 1, :format => 'png'
250 assert_response :success
254 assert_response :success
251 assert_equal 'image/png', @response.content_type
255 assert_equal 'image/png', @response.content_type
252 end
256 end
253 else
257 else
254 puts "RMagick not installed. Skipping tests !!!"
258 puts "RMagick not installed. Skipping tests !!!"
255 end
259 end
256
260
257 def test_calendar
261 def test_calendar
258 get :calendar, :project_id => 1
262 get :calendar, :project_id => 1
259 assert_response :success
263 assert_response :success
260 assert_template 'calendar'
264 assert_template 'calendar'
261 assert_not_nil assigns(:calendar)
265 assert_not_nil assigns(:calendar)
262 end
266 end
263
267
264 def test_cross_project_calendar
268 def test_cross_project_calendar
265 get :calendar
269 get :calendar
266 assert_response :success
270 assert_response :success
267 assert_template 'calendar'
271 assert_template 'calendar'
268 assert_not_nil assigns(:calendar)
272 assert_not_nil assigns(:calendar)
269 end
273 end
270
274
271 def test_changes
275 def test_changes
272 get :changes, :project_id => 1
276 get :changes, :project_id => 1
273 assert_response :success
277 assert_response :success
274 assert_not_nil assigns(:journals)
278 assert_not_nil assigns(:journals)
275 assert_equal 'application/atom+xml', @response.content_type
279 assert_equal 'application/atom+xml', @response.content_type
276 end
280 end
277
281
278 def test_show_routing
282 def test_show_routing
279 assert_routing(
283 assert_routing(
280 {:method => :get, :path => '/issues/64'},
284 {:method => :get, :path => '/issues/64'},
281 :controller => 'issues', :action => 'show', :id => '64'
285 :controller => 'issues', :action => 'show', :id => '64'
282 )
286 )
283 end
287 end
284
288
285 def test_show_routing_formatted
289 def test_show_routing_formatted
286 assert_routing(
290 assert_routing(
287 {:method => :get, :path => '/issues/2332.pdf'},
291 {:method => :get, :path => '/issues/2332.pdf'},
288 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
292 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
289 )
293 )
290 assert_routing(
294 assert_routing(
291 {:method => :get, :path => '/issues/23123.atom'},
295 {:method => :get, :path => '/issues/23123.atom'},
292 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
296 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
293 )
297 )
294 end
298 end
295
299
296 def test_show_by_anonymous
300 def test_show_by_anonymous
297 get :show, :id => 1
301 get :show, :id => 1
298 assert_response :success
302 assert_response :success
299 assert_template 'show.rhtml'
303 assert_template 'show.rhtml'
300 assert_not_nil assigns(:issue)
304 assert_not_nil assigns(:issue)
301 assert_equal Issue.find(1), assigns(:issue)
305 assert_equal Issue.find(1), assigns(:issue)
302
306
303 # anonymous role is allowed to add a note
307 # anonymous role is allowed to add a note
304 assert_tag :tag => 'form',
308 assert_tag :tag => 'form',
305 :descendant => { :tag => 'fieldset',
309 :descendant => { :tag => 'fieldset',
306 :child => { :tag => 'legend',
310 :child => { :tag => 'legend',
307 :content => /Notes/ } }
311 :content => /Notes/ } }
308 end
312 end
309
313
310 def test_show_by_manager
314 def test_show_by_manager
311 @request.session[:user_id] = 2
315 @request.session[:user_id] = 2
312 get :show, :id => 1
316 get :show, :id => 1
313 assert_response :success
317 assert_response :success
314
318
315 assert_tag :tag => 'form',
319 assert_tag :tag => 'form',
316 :descendant => { :tag => 'fieldset',
320 :descendant => { :tag => 'fieldset',
317 :child => { :tag => 'legend',
321 :child => { :tag => 'legend',
318 :content => /Change properties/ } },
322 :content => /Change properties/ } },
319 :descendant => { :tag => 'fieldset',
323 :descendant => { :tag => 'fieldset',
320 :child => { :tag => 'legend',
324 :child => { :tag => 'legend',
321 :content => /Log time/ } },
325 :content => /Log time/ } },
322 :descendant => { :tag => 'fieldset',
326 :descendant => { :tag => 'fieldset',
323 :child => { :tag => 'legend',
327 :child => { :tag => 'legend',
324 :content => /Notes/ } }
328 :content => /Notes/ } }
325 end
329 end
326
330
327 def test_show_should_not_disclose_relations_to_invisible_issues
331 def test_show_should_not_disclose_relations_to_invisible_issues
328 Setting.cross_project_issue_relations = '1'
332 Setting.cross_project_issue_relations = '1'
329 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
333 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
330 # Relation to a private project issue
334 # Relation to a private project issue
331 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
335 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
332
336
333 get :show, :id => 1
337 get :show, :id => 1
334 assert_response :success
338 assert_response :success
335
339
336 assert_tag :div, :attributes => { :id => 'relations' },
340 assert_tag :div, :attributes => { :id => 'relations' },
337 :descendant => { :tag => 'a', :content => /#2$/ }
341 :descendant => { :tag => 'a', :content => /#2$/ }
338 assert_no_tag :div, :attributes => { :id => 'relations' },
342 assert_no_tag :div, :attributes => { :id => 'relations' },
339 :descendant => { :tag => 'a', :content => /#4$/ }
343 :descendant => { :tag => 'a', :content => /#4$/ }
340 end
344 end
341
345
342 def test_new_routing
346 def test_new_routing
343 assert_routing(
347 assert_routing(
344 {:method => :get, :path => '/projects/1/issues/new'},
348 {:method => :get, :path => '/projects/1/issues/new'},
345 :controller => 'issues', :action => 'new', :project_id => '1'
349 :controller => 'issues', :action => 'new', :project_id => '1'
346 )
350 )
347 assert_recognizes(
351 assert_recognizes(
348 {:controller => 'issues', :action => 'new', :project_id => '1'},
352 {:controller => 'issues', :action => 'new', :project_id => '1'},
349 {:method => :post, :path => '/projects/1/issues'}
353 {:method => :post, :path => '/projects/1/issues'}
350 )
354 )
351 end
355 end
352
356
353 def test_show_export_to_pdf
357 def test_show_export_to_pdf
354 get :show, :id => 3, :format => 'pdf'
358 get :show, :id => 3, :format => 'pdf'
355 assert_response :success
359 assert_response :success
356 assert_equal 'application/pdf', @response.content_type
360 assert_equal 'application/pdf', @response.content_type
357 assert @response.body.starts_with?('%PDF')
361 assert @response.body.starts_with?('%PDF')
358 assert_not_nil assigns(:issue)
362 assert_not_nil assigns(:issue)
359 end
363 end
360
364
361 def test_get_new
365 def test_get_new
362 @request.session[:user_id] = 2
366 @request.session[:user_id] = 2
363 get :new, :project_id => 1, :tracker_id => 1
367 get :new, :project_id => 1, :tracker_id => 1
364 assert_response :success
368 assert_response :success
365 assert_template 'new'
369 assert_template 'new'
366
370
367 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
371 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
368 :value => 'Default string' }
372 :value => 'Default string' }
369 end
373 end
370
374
371 def test_get_new_without_tracker_id
375 def test_get_new_without_tracker_id
372 @request.session[:user_id] = 2
376 @request.session[:user_id] = 2
373 get :new, :project_id => 1
377 get :new, :project_id => 1
374 assert_response :success
378 assert_response :success
375 assert_template 'new'
379 assert_template 'new'
376
380
377 issue = assigns(:issue)
381 issue = assigns(:issue)
378 assert_not_nil issue
382 assert_not_nil issue
379 assert_equal Project.find(1).trackers.first, issue.tracker
383 assert_equal Project.find(1).trackers.first, issue.tracker
380 end
384 end
381
385
382 def test_get_new_with_no_default_status_should_display_an_error
386 def test_get_new_with_no_default_status_should_display_an_error
383 @request.session[:user_id] = 2
387 @request.session[:user_id] = 2
384 IssueStatus.delete_all
388 IssueStatus.delete_all
385
389
386 get :new, :project_id => 1
390 get :new, :project_id => 1
387 assert_response 500
391 assert_response 500
388 assert_not_nil flash[:error]
392 assert_not_nil flash[:error]
389 assert_tag :tag => 'div', :attributes => { :class => /error/ },
393 assert_tag :tag => 'div', :attributes => { :class => /error/ },
390 :content => /No default issue/
394 :content => /No default issue/
391 end
395 end
392
396
393 def test_get_new_with_no_tracker_should_display_an_error
397 def test_get_new_with_no_tracker_should_display_an_error
394 @request.session[:user_id] = 2
398 @request.session[:user_id] = 2
395 Tracker.delete_all
399 Tracker.delete_all
396
400
397 get :new, :project_id => 1
401 get :new, :project_id => 1
398 assert_response 500
402 assert_response 500
399 assert_not_nil flash[:error]
403 assert_not_nil flash[:error]
400 assert_tag :tag => 'div', :attributes => { :class => /error/ },
404 assert_tag :tag => 'div', :attributes => { :class => /error/ },
401 :content => /No tracker/
405 :content => /No tracker/
402 end
406 end
403
407
404 def test_update_new_form
408 def test_update_new_form
405 @request.session[:user_id] = 2
409 @request.session[:user_id] = 2
406 xhr :post, :new, :project_id => 1,
410 xhr :post, :new, :project_id => 1,
407 :issue => {:tracker_id => 2,
411 :issue => {:tracker_id => 2,
408 :subject => 'This is the test_new issue',
412 :subject => 'This is the test_new issue',
409 :description => 'This is the description',
413 :description => 'This is the description',
410 :priority_id => 5}
414 :priority_id => 5}
411 assert_response :success
415 assert_response :success
412 assert_template 'new'
416 assert_template 'new'
413 end
417 end
414
418
415 def test_post_new
419 def test_post_new
416 @request.session[:user_id] = 2
420 @request.session[:user_id] = 2
417 post :new, :project_id => 1,
421 post :new, :project_id => 1,
418 :issue => {:tracker_id => 3,
422 :issue => {:tracker_id => 3,
419 :subject => 'This is the test_new issue',
423 :subject => 'This is the test_new issue',
420 :description => 'This is the description',
424 :description => 'This is the description',
421 :priority_id => 5,
425 :priority_id => 5,
422 :estimated_hours => '',
426 :estimated_hours => '',
423 :custom_field_values => {'2' => 'Value for field 2'}}
427 :custom_field_values => {'2' => 'Value for field 2'}}
424 assert_redirected_to :action => 'show'
428 assert_redirected_to :action => 'show'
425
429
426 issue = Issue.find_by_subject('This is the test_new issue')
430 issue = Issue.find_by_subject('This is the test_new issue')
427 assert_not_nil issue
431 assert_not_nil issue
428 assert_equal 2, issue.author_id
432 assert_equal 2, issue.author_id
429 assert_equal 3, issue.tracker_id
433 assert_equal 3, issue.tracker_id
430 assert_nil issue.estimated_hours
434 assert_nil issue.estimated_hours
431 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
435 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
432 assert_not_nil v
436 assert_not_nil v
433 assert_equal 'Value for field 2', v.value
437 assert_equal 'Value for field 2', v.value
434 end
438 end
435
439
436 def test_post_new_and_continue
440 def test_post_new_and_continue
437 @request.session[:user_id] = 2
441 @request.session[:user_id] = 2
438 post :new, :project_id => 1,
442 post :new, :project_id => 1,
439 :issue => {:tracker_id => 3,
443 :issue => {:tracker_id => 3,
440 :subject => 'This is first issue',
444 :subject => 'This is first issue',
441 :priority_id => 5},
445 :priority_id => 5},
442 :continue => ''
446 :continue => ''
443 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
447 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
444 end
448 end
445
449
446 def test_post_new_without_custom_fields_param
450 def test_post_new_without_custom_fields_param
447 @request.session[:user_id] = 2
451 @request.session[:user_id] = 2
448 post :new, :project_id => 1,
452 post :new, :project_id => 1,
449 :issue => {:tracker_id => 1,
453 :issue => {:tracker_id => 1,
450 :subject => 'This is the test_new issue',
454 :subject => 'This is the test_new issue',
451 :description => 'This is the description',
455 :description => 'This is the description',
452 :priority_id => 5}
456 :priority_id => 5}
453 assert_redirected_to :action => 'show'
457 assert_redirected_to :action => 'show'
454 end
458 end
455
459
456 def test_post_new_with_required_custom_field_and_without_custom_fields_param
460 def test_post_new_with_required_custom_field_and_without_custom_fields_param
457 field = IssueCustomField.find_by_name('Database')
461 field = IssueCustomField.find_by_name('Database')
458 field.update_attribute(:is_required, true)
462 field.update_attribute(:is_required, true)
459
463
460 @request.session[:user_id] = 2
464 @request.session[:user_id] = 2
461 post :new, :project_id => 1,
465 post :new, :project_id => 1,
462 :issue => {:tracker_id => 1,
466 :issue => {:tracker_id => 1,
463 :subject => 'This is the test_new issue',
467 :subject => 'This is the test_new issue',
464 :description => 'This is the description',
468 :description => 'This is the description',
465 :priority_id => 5}
469 :priority_id => 5}
466 assert_response :success
470 assert_response :success
467 assert_template 'new'
471 assert_template 'new'
468 issue = assigns(:issue)
472 issue = assigns(:issue)
469 assert_not_nil issue
473 assert_not_nil issue
470 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
474 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
471 end
475 end
472
476
473 def test_post_new_with_watchers
477 def test_post_new_with_watchers
474 @request.session[:user_id] = 2
478 @request.session[:user_id] = 2
475 ActionMailer::Base.deliveries.clear
479 ActionMailer::Base.deliveries.clear
476
480
477 assert_difference 'Watcher.count', 2 do
481 assert_difference 'Watcher.count', 2 do
478 post :new, :project_id => 1,
482 post :new, :project_id => 1,
479 :issue => {:tracker_id => 1,
483 :issue => {:tracker_id => 1,
480 :subject => 'This is a new issue with watchers',
484 :subject => 'This is a new issue with watchers',
481 :description => 'This is the description',
485 :description => 'This is the description',
482 :priority_id => 5,
486 :priority_id => 5,
483 :watcher_user_ids => ['2', '3']}
487 :watcher_user_ids => ['2', '3']}
484 end
488 end
485 issue = Issue.find_by_subject('This is a new issue with watchers')
489 issue = Issue.find_by_subject('This is a new issue with watchers')
486 assert_not_nil issue
490 assert_not_nil issue
487 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
491 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
488
492
489 # Watchers added
493 # Watchers added
490 assert_equal [2, 3], issue.watcher_user_ids.sort
494 assert_equal [2, 3], issue.watcher_user_ids.sort
491 assert issue.watched_by?(User.find(3))
495 assert issue.watched_by?(User.find(3))
492 # Watchers notified
496 # Watchers notified
493 mail = ActionMailer::Base.deliveries.last
497 mail = ActionMailer::Base.deliveries.last
494 assert_kind_of TMail::Mail, mail
498 assert_kind_of TMail::Mail, mail
495 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
499 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
496 end
500 end
497
501
498 def test_post_should_preserve_fields_values_on_validation_failure
502 def test_post_should_preserve_fields_values_on_validation_failure
499 @request.session[:user_id] = 2
503 @request.session[:user_id] = 2
500 post :new, :project_id => 1,
504 post :new, :project_id => 1,
501 :issue => {:tracker_id => 1,
505 :issue => {:tracker_id => 1,
502 # empty subject
506 # empty subject
503 :subject => '',
507 :subject => '',
504 :description => 'This is a description',
508 :description => 'This is a description',
505 :priority_id => 6,
509 :priority_id => 6,
506 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
510 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
507 assert_response :success
511 assert_response :success
508 assert_template 'new'
512 assert_template 'new'
509
513
510 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
514 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
511 :content => 'This is a description'
515 :content => 'This is a description'
512 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
516 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
513 :child => { :tag => 'option', :attributes => { :selected => 'selected',
517 :child => { :tag => 'option', :attributes => { :selected => 'selected',
514 :value => '6' },
518 :value => '6' },
515 :content => 'High' }
519 :content => 'High' }
516 # Custom fields
520 # Custom fields
517 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
521 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
518 :child => { :tag => 'option', :attributes => { :selected => 'selected',
522 :child => { :tag => 'option', :attributes => { :selected => 'selected',
519 :value => 'Oracle' },
523 :value => 'Oracle' },
520 :content => 'Oracle' }
524 :content => 'Oracle' }
521 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
525 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
522 :value => 'Value for field 2'}
526 :value => 'Value for field 2'}
523 end
527 end
524
528
525 def test_copy_routing
529 def test_copy_routing
526 assert_routing(
530 assert_routing(
527 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
531 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
528 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
532 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
529 )
533 )
530 end
534 end
531
535
532 def test_copy_issue
536 def test_copy_issue
533 @request.session[:user_id] = 2
537 @request.session[:user_id] = 2
534 get :new, :project_id => 1, :copy_from => 1
538 get :new, :project_id => 1, :copy_from => 1
535 assert_template 'new'
539 assert_template 'new'
536 assert_not_nil assigns(:issue)
540 assert_not_nil assigns(:issue)
537 orig = Issue.find(1)
541 orig = Issue.find(1)
538 assert_equal orig.subject, assigns(:issue).subject
542 assert_equal orig.subject, assigns(:issue).subject
539 end
543 end
540
544
541 def test_edit_routing
545 def test_edit_routing
542 assert_routing(
546 assert_routing(
543 {:method => :get, :path => '/issues/1/edit'},
547 {:method => :get, :path => '/issues/1/edit'},
544 :controller => 'issues', :action => 'edit', :id => '1'
548 :controller => 'issues', :action => 'edit', :id => '1'
545 )
549 )
546 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
550 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
547 {:controller => 'issues', :action => 'edit', :id => '1'},
551 {:controller => 'issues', :action => 'edit', :id => '1'},
548 {:method => :post, :path => '/issues/1/edit'}
552 {:method => :post, :path => '/issues/1/edit'}
549 )
553 )
550 end
554 end
551
555
552 def test_get_edit
556 def test_get_edit
553 @request.session[:user_id] = 2
557 @request.session[:user_id] = 2
554 get :edit, :id => 1
558 get :edit, :id => 1
555 assert_response :success
559 assert_response :success
556 assert_template 'edit'
560 assert_template 'edit'
557 assert_not_nil assigns(:issue)
561 assert_not_nil assigns(:issue)
558 assert_equal Issue.find(1), assigns(:issue)
562 assert_equal Issue.find(1), assigns(:issue)
559 end
563 end
560
564
561 def test_get_edit_with_params
565 def test_get_edit_with_params
562 @request.session[:user_id] = 2
566 @request.session[:user_id] = 2
563 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
567 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
564 assert_response :success
568 assert_response :success
565 assert_template 'edit'
569 assert_template 'edit'
566
570
567 issue = assigns(:issue)
571 issue = assigns(:issue)
568 assert_not_nil issue
572 assert_not_nil issue
569
573
570 assert_equal 5, issue.status_id
574 assert_equal 5, issue.status_id
571 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
575 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
572 :child => { :tag => 'option',
576 :child => { :tag => 'option',
573 :content => 'Closed',
577 :content => 'Closed',
574 :attributes => { :selected => 'selected' } }
578 :attributes => { :selected => 'selected' } }
575
579
576 assert_equal 7, issue.priority_id
580 assert_equal 7, issue.priority_id
577 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
581 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
578 :child => { :tag => 'option',
582 :child => { :tag => 'option',
579 :content => 'Urgent',
583 :content => 'Urgent',
580 :attributes => { :selected => 'selected' } }
584 :attributes => { :selected => 'selected' } }
581 end
585 end
582
586
583 def test_reply_routing
587 def test_reply_routing
584 assert_routing(
588 assert_routing(
585 {:method => :post, :path => '/issues/1/quoted'},
589 {:method => :post, :path => '/issues/1/quoted'},
586 :controller => 'issues', :action => 'reply', :id => '1'
590 :controller => 'issues', :action => 'reply', :id => '1'
587 )
591 )
588 end
592 end
589
593
590 def test_reply_to_issue
594 def test_reply_to_issue
591 @request.session[:user_id] = 2
595 @request.session[:user_id] = 2
592 get :reply, :id => 1
596 get :reply, :id => 1
593 assert_response :success
597 assert_response :success
594 assert_select_rjs :show, "update"
598 assert_select_rjs :show, "update"
595 end
599 end
596
600
597 def test_reply_to_note
601 def test_reply_to_note
598 @request.session[:user_id] = 2
602 @request.session[:user_id] = 2
599 get :reply, :id => 1, :journal_id => 2
603 get :reply, :id => 1, :journal_id => 2
600 assert_response :success
604 assert_response :success
601 assert_select_rjs :show, "update"
605 assert_select_rjs :show, "update"
602 end
606 end
603
607
604 def test_post_edit_without_custom_fields_param
608 def test_post_edit_without_custom_fields_param
605 @request.session[:user_id] = 2
609 @request.session[:user_id] = 2
606 ActionMailer::Base.deliveries.clear
610 ActionMailer::Base.deliveries.clear
607
611
608 issue = Issue.find(1)
612 issue = Issue.find(1)
609 assert_equal '125', issue.custom_value_for(2).value
613 assert_equal '125', issue.custom_value_for(2).value
610 old_subject = issue.subject
614 old_subject = issue.subject
611 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
615 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
612
616
613 assert_difference('Journal.count') do
617 assert_difference('Journal.count') do
614 assert_difference('JournalDetail.count', 2) do
618 assert_difference('JournalDetail.count', 2) do
615 post :edit, :id => 1, :issue => {:subject => new_subject,
619 post :edit, :id => 1, :issue => {:subject => new_subject,
616 :priority_id => '6',
620 :priority_id => '6',
617 :category_id => '1' # no change
621 :category_id => '1' # no change
618 }
622 }
619 end
623 end
620 end
624 end
621 assert_redirected_to :action => 'show', :id => '1'
625 assert_redirected_to :action => 'show', :id => '1'
622 issue.reload
626 issue.reload
623 assert_equal new_subject, issue.subject
627 assert_equal new_subject, issue.subject
624 # Make sure custom fields were not cleared
628 # Make sure custom fields were not cleared
625 assert_equal '125', issue.custom_value_for(2).value
629 assert_equal '125', issue.custom_value_for(2).value
626
630
627 mail = ActionMailer::Base.deliveries.last
631 mail = ActionMailer::Base.deliveries.last
628 assert_kind_of TMail::Mail, mail
632 assert_kind_of TMail::Mail, mail
629 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
633 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
630 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
634 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
631 end
635 end
632
636
633 def test_post_edit_with_custom_field_change
637 def test_post_edit_with_custom_field_change
634 @request.session[:user_id] = 2
638 @request.session[:user_id] = 2
635 issue = Issue.find(1)
639 issue = Issue.find(1)
636 assert_equal '125', issue.custom_value_for(2).value
640 assert_equal '125', issue.custom_value_for(2).value
637
641
638 assert_difference('Journal.count') do
642 assert_difference('Journal.count') do
639 assert_difference('JournalDetail.count', 3) do
643 assert_difference('JournalDetail.count', 3) do
640 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
644 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
641 :priority_id => '6',
645 :priority_id => '6',
642 :category_id => '1', # no change
646 :category_id => '1', # no change
643 :custom_field_values => { '2' => 'New custom value' }
647 :custom_field_values => { '2' => 'New custom value' }
644 }
648 }
645 end
649 end
646 end
650 end
647 assert_redirected_to :action => 'show', :id => '1'
651 assert_redirected_to :action => 'show', :id => '1'
648 issue.reload
652 issue.reload
649 assert_equal 'New custom value', issue.custom_value_for(2).value
653 assert_equal 'New custom value', issue.custom_value_for(2).value
650
654
651 mail = ActionMailer::Base.deliveries.last
655 mail = ActionMailer::Base.deliveries.last
652 assert_kind_of TMail::Mail, mail
656 assert_kind_of TMail::Mail, mail
653 assert mail.body.include?("Searchable field changed from 125 to New custom value")
657 assert mail.body.include?("Searchable field changed from 125 to New custom value")
654 end
658 end
655
659
656 def test_post_edit_with_status_and_assignee_change
660 def test_post_edit_with_status_and_assignee_change
657 issue = Issue.find(1)
661 issue = Issue.find(1)
658 assert_equal 1, issue.status_id
662 assert_equal 1, issue.status_id
659 @request.session[:user_id] = 2
663 @request.session[:user_id] = 2
660 assert_difference('TimeEntry.count', 0) do
664 assert_difference('TimeEntry.count', 0) do
661 post :edit,
665 post :edit,
662 :id => 1,
666 :id => 1,
663 :issue => { :status_id => 2, :assigned_to_id => 3 },
667 :issue => { :status_id => 2, :assigned_to_id => 3 },
664 :notes => 'Assigned to dlopper',
668 :notes => 'Assigned to dlopper',
665 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
669 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.activities.first }
666 end
670 end
667 assert_redirected_to :action => 'show', :id => '1'
671 assert_redirected_to :action => 'show', :id => '1'
668 issue.reload
672 issue.reload
669 assert_equal 2, issue.status_id
673 assert_equal 2, issue.status_id
670 j = issue.journals.find(:first, :order => 'id DESC')
674 j = issue.journals.find(:first, :order => 'id DESC')
671 assert_equal 'Assigned to dlopper', j.notes
675 assert_equal 'Assigned to dlopper', j.notes
672 assert_equal 2, j.details.size
676 assert_equal 2, j.details.size
673
677
674 mail = ActionMailer::Base.deliveries.last
678 mail = ActionMailer::Base.deliveries.last
675 assert mail.body.include?("Status changed from New to Assigned")
679 assert mail.body.include?("Status changed from New to Assigned")
676 end
680 end
677
681
678 def test_post_edit_with_note_only
682 def test_post_edit_with_note_only
679 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
683 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
680 # anonymous user
684 # anonymous user
681 post :edit,
685 post :edit,
682 :id => 1,
686 :id => 1,
683 :notes => notes
687 :notes => notes
684 assert_redirected_to :action => 'show', :id => '1'
688 assert_redirected_to :action => 'show', :id => '1'
685 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
689 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
686 assert_equal notes, j.notes
690 assert_equal notes, j.notes
687 assert_equal 0, j.details.size
691 assert_equal 0, j.details.size
688 assert_equal User.anonymous, j.user
692 assert_equal User.anonymous, j.user
689
693
690 mail = ActionMailer::Base.deliveries.last
694 mail = ActionMailer::Base.deliveries.last
691 assert mail.body.include?(notes)
695 assert mail.body.include?(notes)
692 end
696 end
693
697
694 def test_post_edit_with_note_and_spent_time
698 def test_post_edit_with_note_and_spent_time
695 @request.session[:user_id] = 2
699 @request.session[:user_id] = 2
696 spent_hours_before = Issue.find(1).spent_hours
700 spent_hours_before = Issue.find(1).spent_hours
697 assert_difference('TimeEntry.count') do
701 assert_difference('TimeEntry.count') do
698 post :edit,
702 post :edit,
699 :id => 1,
703 :id => 1,
700 :notes => '2.5 hours added',
704 :notes => '2.5 hours added',
701 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
705 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.activities.first }
702 end
706 end
703 assert_redirected_to :action => 'show', :id => '1'
707 assert_redirected_to :action => 'show', :id => '1'
704
708
705 issue = Issue.find(1)
709 issue = Issue.find(1)
706
710
707 j = issue.journals.find(:first, :order => 'id DESC')
711 j = issue.journals.find(:first, :order => 'id DESC')
708 assert_equal '2.5 hours added', j.notes
712 assert_equal '2.5 hours added', j.notes
709 assert_equal 0, j.details.size
713 assert_equal 0, j.details.size
710
714
711 t = issue.time_entries.find(:first, :order => 'id DESC')
715 t = issue.time_entries.find(:first, :order => 'id DESC')
712 assert_not_nil t
716 assert_not_nil t
713 assert_equal 2.5, t.hours
717 assert_equal 2.5, t.hours
714 assert_equal spent_hours_before + 2.5, issue.spent_hours
718 assert_equal spent_hours_before + 2.5, issue.spent_hours
715 end
719 end
716
720
717 def test_post_edit_with_attachment_only
721 def test_post_edit_with_attachment_only
718 set_tmp_attachments_directory
722 set_tmp_attachments_directory
719
723
720 # Delete all fixtured journals, a race condition can occur causing the wrong
724 # Delete all fixtured journals, a race condition can occur causing the wrong
721 # journal to get fetched in the next find.
725 # journal to get fetched in the next find.
722 Journal.delete_all
726 Journal.delete_all
723
727
724 # anonymous user
728 # anonymous user
725 post :edit,
729 post :edit,
726 :id => 1,
730 :id => 1,
727 :notes => '',
731 :notes => '',
728 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
732 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
729 assert_redirected_to :action => 'show', :id => '1'
733 assert_redirected_to :action => 'show', :id => '1'
730 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
734 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
731 assert j.notes.blank?
735 assert j.notes.blank?
732 assert_equal 1, j.details.size
736 assert_equal 1, j.details.size
733 assert_equal 'testfile.txt', j.details.first.value
737 assert_equal 'testfile.txt', j.details.first.value
734 assert_equal User.anonymous, j.user
738 assert_equal User.anonymous, j.user
735
739
736 mail = ActionMailer::Base.deliveries.last
740 mail = ActionMailer::Base.deliveries.last
737 assert mail.body.include?('testfile.txt')
741 assert mail.body.include?('testfile.txt')
738 end
742 end
739
743
740 def test_post_edit_with_no_change
744 def test_post_edit_with_no_change
741 issue = Issue.find(1)
745 issue = Issue.find(1)
742 issue.journals.clear
746 issue.journals.clear
743 ActionMailer::Base.deliveries.clear
747 ActionMailer::Base.deliveries.clear
744
748
745 post :edit,
749 post :edit,
746 :id => 1,
750 :id => 1,
747 :notes => ''
751 :notes => ''
748 assert_redirected_to :action => 'show', :id => '1'
752 assert_redirected_to :action => 'show', :id => '1'
749
753
750 issue.reload
754 issue.reload
751 assert issue.journals.empty?
755 assert issue.journals.empty?
752 # No email should be sent
756 # No email should be sent
753 assert ActionMailer::Base.deliveries.empty?
757 assert ActionMailer::Base.deliveries.empty?
754 end
758 end
755
759
756 def test_post_edit_with_invalid_spent_time
760 def test_post_edit_with_invalid_spent_time
757 @request.session[:user_id] = 2
761 @request.session[:user_id] = 2
758 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
762 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
759
763
760 assert_no_difference('Journal.count') do
764 assert_no_difference('Journal.count') do
761 post :edit,
765 post :edit,
762 :id => 1,
766 :id => 1,
763 :notes => notes,
767 :notes => notes,
764 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
768 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
765 end
769 end
766 assert_response :success
770 assert_response :success
767 assert_template 'edit'
771 assert_template 'edit'
768
772
769 assert_tag :textarea, :attributes => { :name => 'notes' },
773 assert_tag :textarea, :attributes => { :name => 'notes' },
770 :content => notes
774 :content => notes
771 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
775 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
772 end
776 end
773
777
774 def test_bulk_edit
778 def test_bulk_edit
775 @request.session[:user_id] = 2
779 @request.session[:user_id] = 2
776 # update issues priority
780 # update issues priority
777 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
781 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
778 :assigned_to_id => '',
782 :assigned_to_id => '',
779 :custom_field_values => {'2' => ''},
783 :custom_field_values => {'2' => ''},
780 :notes => 'Bulk editing'
784 :notes => 'Bulk editing'
781 assert_response 302
785 assert_response 302
782 # check that the issues were updated
786 # check that the issues were updated
783 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
787 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
784
788
785 issue = Issue.find(1)
789 issue = Issue.find(1)
786 journal = issue.journals.find(:first, :order => 'created_on DESC')
790 journal = issue.journals.find(:first, :order => 'created_on DESC')
787 assert_equal '125', issue.custom_value_for(2).value
791 assert_equal '125', issue.custom_value_for(2).value
788 assert_equal 'Bulk editing', journal.notes
792 assert_equal 'Bulk editing', journal.notes
789 assert_equal 1, journal.details.size
793 assert_equal 1, journal.details.size
790 end
794 end
791
795
792 def test_bulk_edit_custom_field
796 def test_bulk_edit_custom_field
793 @request.session[:user_id] = 2
797 @request.session[:user_id] = 2
794 # update issues priority
798 # update issues priority
795 post :bulk_edit, :ids => [1, 2], :priority_id => '',
799 post :bulk_edit, :ids => [1, 2], :priority_id => '',
796 :assigned_to_id => '',
800 :assigned_to_id => '',
797 :custom_field_values => {'2' => '777'},
801 :custom_field_values => {'2' => '777'},
798 :notes => 'Bulk editing custom field'
802 :notes => 'Bulk editing custom field'
799 assert_response 302
803 assert_response 302
800
804
801 issue = Issue.find(1)
805 issue = Issue.find(1)
802 journal = issue.journals.find(:first, :order => 'created_on DESC')
806 journal = issue.journals.find(:first, :order => 'created_on DESC')
803 assert_equal '777', issue.custom_value_for(2).value
807 assert_equal '777', issue.custom_value_for(2).value
804 assert_equal 1, journal.details.size
808 assert_equal 1, journal.details.size
805 assert_equal '125', journal.details.first.old_value
809 assert_equal '125', journal.details.first.old_value
806 assert_equal '777', journal.details.first.value
810 assert_equal '777', journal.details.first.value
807 end
811 end
808
812
809 def test_bulk_unassign
813 def test_bulk_unassign
810 assert_not_nil Issue.find(2).assigned_to
814 assert_not_nil Issue.find(2).assigned_to
811 @request.session[:user_id] = 2
815 @request.session[:user_id] = 2
812 # unassign issues
816 # unassign issues
813 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
817 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
814 assert_response 302
818 assert_response 302
815 # check that the issues were updated
819 # check that the issues were updated
816 assert_nil Issue.find(2).assigned_to
820 assert_nil Issue.find(2).assigned_to
817 end
821 end
818
822
819 def test_move_routing
823 def test_move_routing
820 assert_routing(
824 assert_routing(
821 {:method => :get, :path => '/issues/1/move'},
825 {:method => :get, :path => '/issues/1/move'},
822 :controller => 'issues', :action => 'move', :id => '1'
826 :controller => 'issues', :action => 'move', :id => '1'
823 )
827 )
824 assert_recognizes(
828 assert_recognizes(
825 {:controller => 'issues', :action => 'move', :id => '1'},
829 {:controller => 'issues', :action => 'move', :id => '1'},
826 {:method => :post, :path => '/issues/1/move'}
830 {:method => :post, :path => '/issues/1/move'}
827 )
831 )
828 end
832 end
829
833
830 def test_move_one_issue_to_another_project
834 def test_move_one_issue_to_another_project
831 @request.session[:user_id] = 1
835 @request.session[:user_id] = 1
832 post :move, :id => 1, :new_project_id => 2
836 post :move, :id => 1, :new_project_id => 2
833 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
837 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
834 assert_equal 2, Issue.find(1).project_id
838 assert_equal 2, Issue.find(1).project_id
835 end
839 end
836
840
837 def test_bulk_move_to_another_project
841 def test_bulk_move_to_another_project
838 @request.session[:user_id] = 1
842 @request.session[:user_id] = 1
839 post :move, :ids => [1, 2], :new_project_id => 2
843 post :move, :ids => [1, 2], :new_project_id => 2
840 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
844 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
841 # Issues moved to project 2
845 # Issues moved to project 2
842 assert_equal 2, Issue.find(1).project_id
846 assert_equal 2, Issue.find(1).project_id
843 assert_equal 2, Issue.find(2).project_id
847 assert_equal 2, Issue.find(2).project_id
844 # No tracker change
848 # No tracker change
845 assert_equal 1, Issue.find(1).tracker_id
849 assert_equal 1, Issue.find(1).tracker_id
846 assert_equal 2, Issue.find(2).tracker_id
850 assert_equal 2, Issue.find(2).tracker_id
847 end
851 end
848
852
849 def test_bulk_move_to_another_tracker
853 def test_bulk_move_to_another_tracker
850 @request.session[:user_id] = 1
854 @request.session[:user_id] = 1
851 post :move, :ids => [1, 2], :new_tracker_id => 2
855 post :move, :ids => [1, 2], :new_tracker_id => 2
852 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
856 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
853 assert_equal 2, Issue.find(1).tracker_id
857 assert_equal 2, Issue.find(1).tracker_id
854 assert_equal 2, Issue.find(2).tracker_id
858 assert_equal 2, Issue.find(2).tracker_id
855 end
859 end
856
860
857 def test_bulk_copy_to_another_project
861 def test_bulk_copy_to_another_project
858 @request.session[:user_id] = 1
862 @request.session[:user_id] = 1
859 assert_difference 'Issue.count', 2 do
863 assert_difference 'Issue.count', 2 do
860 assert_no_difference 'Project.find(1).issues.count' do
864 assert_no_difference 'Project.find(1).issues.count' do
861 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
865 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
862 end
866 end
863 end
867 end
864 assert_redirected_to 'projects/ecookbook/issues'
868 assert_redirected_to 'projects/ecookbook/issues'
865 end
869 end
866
870
867 def test_context_menu_one_issue
871 def test_context_menu_one_issue
868 @request.session[:user_id] = 2
872 @request.session[:user_id] = 2
869 get :context_menu, :ids => [1]
873 get :context_menu, :ids => [1]
870 assert_response :success
874 assert_response :success
871 assert_template 'context_menu'
875 assert_template 'context_menu'
872 assert_tag :tag => 'a', :content => 'Edit',
876 assert_tag :tag => 'a', :content => 'Edit',
873 :attributes => { :href => '/issues/1/edit',
877 :attributes => { :href => '/issues/1/edit',
874 :class => 'icon-edit' }
878 :class => 'icon-edit' }
875 assert_tag :tag => 'a', :content => 'Closed',
879 assert_tag :tag => 'a', :content => 'Closed',
876 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
880 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
877 :class => '' }
881 :class => '' }
878 assert_tag :tag => 'a', :content => 'Immediate',
882 assert_tag :tag => 'a', :content => 'Immediate',
879 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
883 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
880 :class => '' }
884 :class => '' }
881 assert_tag :tag => 'a', :content => 'Dave Lopper',
885 assert_tag :tag => 'a', :content => 'Dave Lopper',
882 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
886 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
883 :class => '' }
887 :class => '' }
884 assert_tag :tag => 'a', :content => 'Copy',
888 assert_tag :tag => 'a', :content => 'Copy',
885 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
889 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
886 :class => 'icon-copy' }
890 :class => 'icon-copy' }
887 assert_tag :tag => 'a', :content => 'Move',
891 assert_tag :tag => 'a', :content => 'Move',
888 :attributes => { :href => '/issues/move?ids%5B%5D=1',
892 :attributes => { :href => '/issues/move?ids%5B%5D=1',
889 :class => 'icon-move' }
893 :class => 'icon-move' }
890 assert_tag :tag => 'a', :content => 'Delete',
894 assert_tag :tag => 'a', :content => 'Delete',
891 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
895 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
892 :class => 'icon-del' }
896 :class => 'icon-del' }
893 end
897 end
894
898
895 def test_context_menu_one_issue_by_anonymous
899 def test_context_menu_one_issue_by_anonymous
896 get :context_menu, :ids => [1]
900 get :context_menu, :ids => [1]
897 assert_response :success
901 assert_response :success
898 assert_template 'context_menu'
902 assert_template 'context_menu'
899 assert_tag :tag => 'a', :content => 'Delete',
903 assert_tag :tag => 'a', :content => 'Delete',
900 :attributes => { :href => '#',
904 :attributes => { :href => '#',
901 :class => 'icon-del disabled' }
905 :class => 'icon-del disabled' }
902 end
906 end
903
907
904 def test_context_menu_multiple_issues_of_same_project
908 def test_context_menu_multiple_issues_of_same_project
905 @request.session[:user_id] = 2
909 @request.session[:user_id] = 2
906 get :context_menu, :ids => [1, 2]
910 get :context_menu, :ids => [1, 2]
907 assert_response :success
911 assert_response :success
908 assert_template 'context_menu'
912 assert_template 'context_menu'
909 assert_tag :tag => 'a', :content => 'Edit',
913 assert_tag :tag => 'a', :content => 'Edit',
910 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
914 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
911 :class => 'icon-edit' }
915 :class => 'icon-edit' }
912 assert_tag :tag => 'a', :content => 'Immediate',
916 assert_tag :tag => 'a', :content => 'Immediate',
913 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
917 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
914 :class => '' }
918 :class => '' }
915 assert_tag :tag => 'a', :content => 'Dave Lopper',
919 assert_tag :tag => 'a', :content => 'Dave Lopper',
916 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
920 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
917 :class => '' }
921 :class => '' }
918 assert_tag :tag => 'a', :content => 'Move',
922 assert_tag :tag => 'a', :content => 'Move',
919 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
923 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
920 :class => 'icon-move' }
924 :class => 'icon-move' }
921 assert_tag :tag => 'a', :content => 'Delete',
925 assert_tag :tag => 'a', :content => 'Delete',
922 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
926 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
923 :class => 'icon-del' }
927 :class => 'icon-del' }
924 end
928 end
925
929
926 def test_context_menu_multiple_issues_of_different_project
930 def test_context_menu_multiple_issues_of_different_project
927 @request.session[:user_id] = 2
931 @request.session[:user_id] = 2
928 get :context_menu, :ids => [1, 2, 4]
932 get :context_menu, :ids => [1, 2, 4]
929 assert_response :success
933 assert_response :success
930 assert_template 'context_menu'
934 assert_template 'context_menu'
931 assert_tag :tag => 'a', :content => 'Delete',
935 assert_tag :tag => 'a', :content => 'Delete',
932 :attributes => { :href => '#',
936 :attributes => { :href => '#',
933 :class => 'icon-del disabled' }
937 :class => 'icon-del disabled' }
934 end
938 end
935
939
936 def test_destroy_routing
940 def test_destroy_routing
937 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
941 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
938 {:controller => 'issues', :action => 'destroy', :id => '1'},
942 {:controller => 'issues', :action => 'destroy', :id => '1'},
939 {:method => :post, :path => '/issues/1/destroy'}
943 {:method => :post, :path => '/issues/1/destroy'}
940 )
944 )
941 end
945 end
942
946
943 def test_destroy_issue_with_no_time_entries
947 def test_destroy_issue_with_no_time_entries
944 assert_nil TimeEntry.find_by_issue_id(2)
948 assert_nil TimeEntry.find_by_issue_id(2)
945 @request.session[:user_id] = 2
949 @request.session[:user_id] = 2
946 post :destroy, :id => 2
950 post :destroy, :id => 2
947 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
951 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
948 assert_nil Issue.find_by_id(2)
952 assert_nil Issue.find_by_id(2)
949 end
953 end
950
954
951 def test_destroy_issues_with_time_entries
955 def test_destroy_issues_with_time_entries
952 @request.session[:user_id] = 2
956 @request.session[:user_id] = 2
953 post :destroy, :ids => [1, 3]
957 post :destroy, :ids => [1, 3]
954 assert_response :success
958 assert_response :success
955 assert_template 'destroy'
959 assert_template 'destroy'
956 assert_not_nil assigns(:hours)
960 assert_not_nil assigns(:hours)
957 assert Issue.find_by_id(1) && Issue.find_by_id(3)
961 assert Issue.find_by_id(1) && Issue.find_by_id(3)
958 end
962 end
959
963
960 def test_destroy_issues_and_destroy_time_entries
964 def test_destroy_issues_and_destroy_time_entries
961 @request.session[:user_id] = 2
965 @request.session[:user_id] = 2
962 post :destroy, :ids => [1, 3], :todo => 'destroy'
966 post :destroy, :ids => [1, 3], :todo => 'destroy'
963 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
967 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
964 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
968 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
965 assert_nil TimeEntry.find_by_id([1, 2])
969 assert_nil TimeEntry.find_by_id([1, 2])
966 end
970 end
967
971
968 def test_destroy_issues_and_assign_time_entries_to_project
972 def test_destroy_issues_and_assign_time_entries_to_project
969 @request.session[:user_id] = 2
973 @request.session[:user_id] = 2
970 post :destroy, :ids => [1, 3], :todo => 'nullify'
974 post :destroy, :ids => [1, 3], :todo => 'nullify'
971 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
975 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
972 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
976 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
973 assert_nil TimeEntry.find(1).issue_id
977 assert_nil TimeEntry.find(1).issue_id
974 assert_nil TimeEntry.find(2).issue_id
978 assert_nil TimeEntry.find(2).issue_id
975 end
979 end
976
980
977 def test_destroy_issues_and_reassign_time_entries_to_another_issue
981 def test_destroy_issues_and_reassign_time_entries_to_another_issue
978 @request.session[:user_id] = 2
982 @request.session[:user_id] = 2
979 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
983 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
980 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
984 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
981 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
985 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
982 assert_equal 2, TimeEntry.find(1).issue_id
986 assert_equal 2, TimeEntry.find(1).issue_id
983 assert_equal 2, TimeEntry.find(2).issue_id
987 assert_equal 2, TimeEntry.find(2).issue_id
984 end
988 end
985 end
989 end
General Comments 0
You need to be logged in to leave comments. Login now