##// END OF EJS Templates
Merged r15510 (#22123)....
Jean-Philippe Lang -
r15133:3bd4124ab2b0
parent child
Show More
@@ -1,276 +1,277
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module QueriesHelper
20 module QueriesHelper
21 include ApplicationHelper
21 include ApplicationHelper
22
22
23 def filters_options_for_select(query)
23 def filters_options_for_select(query)
24 ungrouped = []
24 ungrouped = []
25 grouped = {}
25 grouped = {}
26 query.available_filters.map do |field, field_options|
26 query.available_filters.map do |field, field_options|
27 if [:tree, :relation].include?(field_options[:type])
27 if [:tree, :relation].include?(field_options[:type])
28 group = :label_related_issues
28 group = :label_related_issues
29 elsif field =~ /^(.+)\./
29 elsif field =~ /^(.+)\./
30 # association filters
30 # association filters
31 group = "field_#{$1}"
31 group = "field_#{$1}"
32 elsif %w(member_of_group assigned_to_role).include?(field)
32 elsif %w(member_of_group assigned_to_role).include?(field)
33 group = :field_assigned_to
33 group = :field_assigned_to
34 elsif field_options[:type] == :date_past || field_options[:type] == :date
34 elsif field_options[:type] == :date_past || field_options[:type] == :date
35 group = :label_date
35 group = :label_date
36 end
36 end
37 if group
37 if group
38 (grouped[group] ||= []) << [field_options[:name], field]
38 (grouped[group] ||= []) << [field_options[:name], field]
39 else
39 else
40 ungrouped << [field_options[:name], field]
40 ungrouped << [field_options[:name], field]
41 end
41 end
42 end
42 end
43 # Don't group dates if there's only one (eg. time entries filters)
43 # Don't group dates if there's only one (eg. time entries filters)
44 if grouped[:label_date].try(:size) == 1
44 if grouped[:label_date].try(:size) == 1
45 ungrouped << grouped.delete(:label_date).first
45 ungrouped << grouped.delete(:label_date).first
46 end
46 end
47 s = options_for_select([[]] + ungrouped)
47 s = options_for_select([[]] + ungrouped)
48 if grouped.present?
48 if grouped.present?
49 localized_grouped = grouped.map {|k,v| [l(k), v]}
49 localized_grouped = grouped.map {|k,v| [l(k), v]}
50 s << grouped_options_for_select(localized_grouped)
50 s << grouped_options_for_select(localized_grouped)
51 end
51 end
52 s
52 s
53 end
53 end
54
54
55 def query_filters_hidden_tags(query)
55 def query_filters_hidden_tags(query)
56 tags = ''.html_safe
56 tags = ''.html_safe
57 query.filters.each do |field, options|
57 query.filters.each do |field, options|
58 tags << hidden_field_tag("f[]", field, :id => nil)
58 tags << hidden_field_tag("f[]", field, :id => nil)
59 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
59 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
60 options[:values].each do |value|
60 options[:values].each do |value|
61 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
61 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
62 end
62 end
63 end
63 end
64 tags
64 tags
65 end
65 end
66
66
67 def query_columns_hidden_tags(query)
67 def query_columns_hidden_tags(query)
68 tags = ''.html_safe
68 tags = ''.html_safe
69 query.columns.each do |column|
69 query.columns.each do |column|
70 tags << hidden_field_tag("c[]", column.name, :id => nil)
70 tags << hidden_field_tag("c[]", column.name, :id => nil)
71 end
71 end
72 tags
72 tags
73 end
73 end
74
74
75 def query_hidden_tags(query)
75 def query_hidden_tags(query)
76 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
76 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
77 end
77 end
78
78
79 def available_block_columns_tags(query)
79 def available_block_columns_tags(query)
80 tags = ''.html_safe
80 tags = ''.html_safe
81 query.available_block_columns.each do |column|
81 query.available_block_columns.each do |column|
82 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
82 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
83 end
83 end
84 tags
84 tags
85 end
85 end
86
86
87 def available_totalable_columns_tags(query)
87 def available_totalable_columns_tags(query)
88 tags = ''.html_safe
88 tags = ''.html_safe
89 query.available_totalable_columns.each do |column|
89 query.available_totalable_columns.each do |column|
90 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
90 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
91 end
91 end
92 tags << hidden_field_tag('t[]', '')
92 tags
93 tags
93 end
94 end
94
95
95 def query_available_inline_columns_options(query)
96 def query_available_inline_columns_options(query)
96 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
97 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
97 end
98 end
98
99
99 def query_selected_inline_columns_options(query)
100 def query_selected_inline_columns_options(query)
100 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
101 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
101 end
102 end
102
103
103 def render_query_columns_selection(query, options={})
104 def render_query_columns_selection(query, options={})
104 tag_name = (options[:name] || 'c') + '[]'
105 tag_name = (options[:name] || 'c') + '[]'
105 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
106 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
106 end
107 end
107
108
108 def render_query_totals(query)
109 def render_query_totals(query)
109 return unless query.totalable_columns.present?
110 return unless query.totalable_columns.present?
110 totals = query.totalable_columns.map do |column|
111 totals = query.totalable_columns.map do |column|
111 total_tag(column, query.total_for(column))
112 total_tag(column, query.total_for(column))
112 end
113 end
113 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
114 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
114 end
115 end
115
116
116 def total_tag(column, value)
117 def total_tag(column, value)
117 label = content_tag('span', "#{column.caption}:")
118 label = content_tag('span', "#{column.caption}:")
118 value = content_tag('span', format_object(value), :class => 'value')
119 value = content_tag('span', format_object(value), :class => 'value')
119 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
120 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
120 end
121 end
121
122
122 def column_header(column)
123 def column_header(column)
123 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
124 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
124 :default_order => column.default_order) :
125 :default_order => column.default_order) :
125 content_tag('th', h(column.caption))
126 content_tag('th', h(column.caption))
126 end
127 end
127
128
128 def column_content(column, issue)
129 def column_content(column, issue)
129 value = column.value_object(issue)
130 value = column.value_object(issue)
130 if value.is_a?(Array)
131 if value.is_a?(Array)
131 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
132 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
132 else
133 else
133 column_value(column, issue, value)
134 column_value(column, issue, value)
134 end
135 end
135 end
136 end
136
137
137 def column_value(column, issue, value)
138 def column_value(column, issue, value)
138 case column.name
139 case column.name
139 when :id
140 when :id
140 link_to value, issue_path(issue)
141 link_to value, issue_path(issue)
141 when :subject
142 when :subject
142 link_to value, issue_path(issue)
143 link_to value, issue_path(issue)
143 when :parent
144 when :parent
144 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
145 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
145 when :description
146 when :description
146 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
147 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
147 when :done_ratio
148 when :done_ratio
148 progress_bar(value)
149 progress_bar(value)
149 when :relations
150 when :relations
150 content_tag('span',
151 content_tag('span',
151 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
152 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
152 :class => value.css_classes_for(issue))
153 :class => value.css_classes_for(issue))
153 else
154 else
154 format_object(value)
155 format_object(value)
155 end
156 end
156 end
157 end
157
158
158 def csv_content(column, issue)
159 def csv_content(column, issue)
159 value = column.value_object(issue)
160 value = column.value_object(issue)
160 if value.is_a?(Array)
161 if value.is_a?(Array)
161 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
162 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
162 else
163 else
163 csv_value(column, issue, value)
164 csv_value(column, issue, value)
164 end
165 end
165 end
166 end
166
167
167 def csv_value(column, object, value)
168 def csv_value(column, object, value)
168 format_object(value, false) do |value|
169 format_object(value, false) do |value|
169 case value.class.name
170 case value.class.name
170 when 'Float'
171 when 'Float'
171 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
172 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
172 when 'IssueRelation'
173 when 'IssueRelation'
173 value.to_s(object)
174 value.to_s(object)
174 when 'Issue'
175 when 'Issue'
175 if object.is_a?(TimeEntry)
176 if object.is_a?(TimeEntry)
176 "#{value.tracker} ##{value.id}: #{value.subject}"
177 "#{value.tracker} ##{value.id}: #{value.subject}"
177 else
178 else
178 value.id
179 value.id
179 end
180 end
180 else
181 else
181 value
182 value
182 end
183 end
183 end
184 end
184 end
185 end
185
186
186 def query_to_csv(items, query, options={})
187 def query_to_csv(items, query, options={})
187 options ||= {}
188 options ||= {}
188 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
189 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
189 query.available_block_columns.each do |column|
190 query.available_block_columns.each do |column|
190 if options[column.name].present?
191 if options[column.name].present?
191 columns << column
192 columns << column
192 end
193 end
193 end
194 end
194
195
195 Redmine::Export::CSV.generate do |csv|
196 Redmine::Export::CSV.generate do |csv|
196 # csv header fields
197 # csv header fields
197 csv << columns.map {|c| c.caption.to_s}
198 csv << columns.map {|c| c.caption.to_s}
198 # csv lines
199 # csv lines
199 items.each do |item|
200 items.each do |item|
200 csv << columns.map {|c| csv_content(c, item)}
201 csv << columns.map {|c| csv_content(c, item)}
201 end
202 end
202 end
203 end
203 end
204 end
204
205
205 # Retrieve query from session or build a new query
206 # Retrieve query from session or build a new query
206 def retrieve_query
207 def retrieve_query
207 if !params[:query_id].blank?
208 if !params[:query_id].blank?
208 cond = "project_id IS NULL"
209 cond = "project_id IS NULL"
209 cond << " OR project_id = #{@project.id}" if @project
210 cond << " OR project_id = #{@project.id}" if @project
210 @query = IssueQuery.where(cond).find(params[:query_id])
211 @query = IssueQuery.where(cond).find(params[:query_id])
211 raise ::Unauthorized unless @query.visible?
212 raise ::Unauthorized unless @query.visible?
212 @query.project = @project
213 @query.project = @project
213 session[:query] = {:id => @query.id, :project_id => @query.project_id}
214 session[:query] = {:id => @query.id, :project_id => @query.project_id}
214 sort_clear
215 sort_clear
215 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
216 elsif api_request? || params[:set_filter] || session[:query].nil? || session[:query][:project_id] != (@project ? @project.id : nil)
216 # Give it a name, required to be valid
217 # Give it a name, required to be valid
217 @query = IssueQuery.new(:name => "_")
218 @query = IssueQuery.new(:name => "_")
218 @query.project = @project
219 @query.project = @project
219 @query.build_from_params(params)
220 @query.build_from_params(params)
220 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names}
221 session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names}
221 else
222 else
222 # retrieve from session
223 # retrieve from session
223 @query = nil
224 @query = nil
224 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
225 @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id]
225 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
226 @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
226 @query.project = @project
227 @query.project = @project
227 end
228 end
228 end
229 end
229
230
230 def retrieve_query_from_session
231 def retrieve_query_from_session
231 if session[:query]
232 if session[:query]
232 if session[:query][:id]
233 if session[:query][:id]
233 @query = IssueQuery.find_by_id(session[:query][:id])
234 @query = IssueQuery.find_by_id(session[:query][:id])
234 return unless @query
235 return unless @query
235 else
236 else
236 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
237 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
237 end
238 end
238 if session[:query].has_key?(:project_id)
239 if session[:query].has_key?(:project_id)
239 @query.project_id = session[:query][:project_id]
240 @query.project_id = session[:query][:project_id]
240 else
241 else
241 @query.project = @project
242 @query.project = @project
242 end
243 end
243 @query
244 @query
244 end
245 end
245 end
246 end
246
247
247 # Returns the query definition as hidden field tags
248 # Returns the query definition as hidden field tags
248 def query_as_hidden_field_tags(query)
249 def query_as_hidden_field_tags(query)
249 tags = hidden_field_tag("set_filter", "1", :id => nil)
250 tags = hidden_field_tag("set_filter", "1", :id => nil)
250
251
251 if query.filters.present?
252 if query.filters.present?
252 query.filters.each do |field, filter|
253 query.filters.each do |field, filter|
253 tags << hidden_field_tag("f[]", field, :id => nil)
254 tags << hidden_field_tag("f[]", field, :id => nil)
254 tags << hidden_field_tag("op[#{field}]", filter[:operator], :id => nil)
255 tags << hidden_field_tag("op[#{field}]", filter[:operator], :id => nil)
255 filter[:values].each do |value|
256 filter[:values].each do |value|
256 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
257 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
257 end
258 end
258 end
259 end
259 end
260 end
260 if query.column_names.present?
261 if query.column_names.present?
261 query.column_names.each do |name|
262 query.column_names.each do |name|
262 tags << hidden_field_tag("c[]", name, :id => nil)
263 tags << hidden_field_tag("c[]", name, :id => nil)
263 end
264 end
264 end
265 end
265 if query.totalable_names.present?
266 if query.totalable_names.present?
266 query.totalable_names.each do |name|
267 query.totalable_names.each do |name|
267 tags << hidden_field_tag("t[]", name, :id => nil)
268 tags << hidden_field_tag("t[]", name, :id => nil)
268 end
269 end
269 end
270 end
270 if query.group_by.present?
271 if query.group_by.present?
271 tags << hidden_field_tag("group_by", query.group_by, :id => nil)
272 tags << hidden_field_tag("group_by", query.group_by, :id => nil)
272 end
273 end
273
274
274 tags
275 tags
275 end
276 end
276 end
277 end
@@ -1,293 +1,313
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../base', __FILE__)
18 require File.expand_path('../base', __FILE__)
19
19
20 class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base
20 class Redmine::UiTest::IssuesTest < Redmine::UiTest::Base
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
21 fixtures :projects, :users, :email_addresses, :roles, :members, :member_roles,
22 :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
22 :trackers, :projects_trackers, :enabled_modules, :issue_statuses, :issues,
23 :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
23 :enumerations, :custom_fields, :custom_values, :custom_fields_trackers,
24 :watchers, :journals, :journal_details
24 :watchers, :journals, :journal_details
25
25
26 def test_create_issue
26 def test_create_issue
27 log_user('jsmith', 'jsmith')
27 log_user('jsmith', 'jsmith')
28 visit '/projects/ecookbook/issues/new'
28 visit '/projects/ecookbook/issues/new'
29 within('form#issue-form') do
29 within('form#issue-form') do
30 select 'Bug', :from => 'Tracker'
30 select 'Bug', :from => 'Tracker'
31 select 'Low', :from => 'Priority'
31 select 'Low', :from => 'Priority'
32 fill_in 'Subject', :with => 'new test issue'
32 fill_in 'Subject', :with => 'new test issue'
33 fill_in 'Description', :with => 'new issue'
33 fill_in 'Description', :with => 'new issue'
34 select '0 %', :from => 'Done'
34 select '0 %', :from => 'Done'
35 fill_in 'Due date', :with => ''
35 fill_in 'Due date', :with => ''
36 fill_in 'Searchable field', :with => 'Value for field 2'
36 fill_in 'Searchable field', :with => 'Value for field 2'
37 # click_button 'Create' would match both 'Create' and 'Create and continue' buttons
37 # click_button 'Create' would match both 'Create' and 'Create and continue' buttons
38 find('input[name=commit]').click
38 find('input[name=commit]').click
39 end
39 end
40
40
41 # find created issue
41 # find created issue
42 issue = Issue.find_by_subject("new test issue")
42 issue = Issue.find_by_subject("new test issue")
43 assert_kind_of Issue, issue
43 assert_kind_of Issue, issue
44
44
45 # check redirection
45 # check redirection
46 find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created."
46 find 'div#flash_notice', :visible => true, :text => "Issue \##{issue.id} created."
47 assert_equal issue_path(:id => issue), current_path
47 assert_equal issue_path(:id => issue), current_path
48
48
49 # check issue attributes
49 # check issue attributes
50 assert_equal 'jsmith', issue.author.login
50 assert_equal 'jsmith', issue.author.login
51 assert_equal 1, issue.project.id
51 assert_equal 1, issue.project.id
52 assert_equal IssueStatus.find_by_name('New'), issue.status
52 assert_equal IssueStatus.find_by_name('New'), issue.status
53 assert_equal Tracker.find_by_name('Bug'), issue.tracker
53 assert_equal Tracker.find_by_name('Bug'), issue.tracker
54 assert_equal IssuePriority.find_by_name('Low'), issue.priority
54 assert_equal IssuePriority.find_by_name('Low'), issue.priority
55 assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field'))
55 assert_equal 'Value for field 2', issue.custom_field_value(CustomField.find_by_name('Searchable field'))
56 end
56 end
57
57
58 def test_create_issue_with_form_update
58 def test_create_issue_with_form_update
59 field1 = IssueCustomField.create!(
59 field1 = IssueCustomField.create!(
60 :field_format => 'string',
60 :field_format => 'string',
61 :name => 'Field1',
61 :name => 'Field1',
62 :is_for_all => true,
62 :is_for_all => true,
63 :trackers => Tracker.where(:id => [1, 2])
63 :trackers => Tracker.where(:id => [1, 2])
64 )
64 )
65 field2 = IssueCustomField.create!(
65 field2 = IssueCustomField.create!(
66 :field_format => 'string',
66 :field_format => 'string',
67 :name => 'Field2',
67 :name => 'Field2',
68 :is_for_all => true,
68 :is_for_all => true,
69 :trackers => Tracker.where(:id => 2)
69 :trackers => Tracker.where(:id => 2)
70 )
70 )
71
71
72 Role.non_member.add_permission! :add_issues
72 Role.non_member.add_permission! :add_issues
73 Role.non_member.remove_permission! :edit_issues, :add_issue_notes
73 Role.non_member.remove_permission! :edit_issues, :add_issue_notes
74
74
75 log_user('someone', 'foo')
75 log_user('someone', 'foo')
76 visit '/projects/ecookbook/issues/new'
76 visit '/projects/ecookbook/issues/new'
77 assert page.has_no_content?(field2.name)
77 assert page.has_no_content?(field2.name)
78 assert page.has_content?(field1.name)
78 assert page.has_content?(field1.name)
79
79
80 fill_in 'Subject', :with => 'New test issue'
80 fill_in 'Subject', :with => 'New test issue'
81 fill_in 'Description', :with => 'New test issue description'
81 fill_in 'Description', :with => 'New test issue description'
82 fill_in field1.name, :with => 'CF1 value'
82 fill_in field1.name, :with => 'CF1 value'
83 select 'Low', :from => 'Priority'
83 select 'Low', :from => 'Priority'
84
84
85 # field2 should show up when changing tracker
85 # field2 should show up when changing tracker
86 select 'Feature request', :from => 'Tracker'
86 select 'Feature request', :from => 'Tracker'
87 assert page.has_content?(field2.name)
87 assert page.has_content?(field2.name)
88 assert page.has_content?(field1.name)
88 assert page.has_content?(field1.name)
89
89
90 fill_in field2.name, :with => 'CF2 value'
90 fill_in field2.name, :with => 'CF2 value'
91 assert_difference 'Issue.count' do
91 assert_difference 'Issue.count' do
92 page.first(:button, 'Create').click
92 page.first(:button, 'Create').click
93 end
93 end
94
94
95 issue = Issue.order('id desc').first
95 issue = Issue.order('id desc').first
96 assert_equal 'New test issue', issue.subject
96 assert_equal 'New test issue', issue.subject
97 assert_equal 'New test issue description', issue.description
97 assert_equal 'New test issue description', issue.description
98 assert_equal 'Low', issue.priority.name
98 assert_equal 'Low', issue.priority.name
99 assert_equal 'CF1 value', issue.custom_field_value(field1)
99 assert_equal 'CF1 value', issue.custom_field_value(field1)
100 assert_equal 'CF2 value', issue.custom_field_value(field2)
100 assert_equal 'CF2 value', issue.custom_field_value(field2)
101 end
101 end
102
102
103 def test_create_issue_with_watchers
103 def test_create_issue_with_watchers
104 user = User.generate!(:firstname => 'Some', :lastname => 'Watcher')
104 user = User.generate!(:firstname => 'Some', :lastname => 'Watcher')
105 assert_equal 'Some Watcher', user.name
105 assert_equal 'Some Watcher', user.name
106 log_user('jsmith', 'jsmith')
106 log_user('jsmith', 'jsmith')
107 visit '/projects/ecookbook/issues/new'
107 visit '/projects/ecookbook/issues/new'
108 fill_in 'Subject', :with => 'Issue with watchers'
108 fill_in 'Subject', :with => 'Issue with watchers'
109 # Add a project member as watcher
109 # Add a project member as watcher
110 check 'Dave Lopper'
110 check 'Dave Lopper'
111 # Search for another user
111 # Search for another user
112 assert page.has_no_css?('form#new-watcher-form')
112 assert page.has_no_css?('form#new-watcher-form')
113 assert page.has_no_content?('Some Watcher')
113 assert page.has_no_content?('Some Watcher')
114 click_link 'Search for watchers to add'
114 click_link 'Search for watchers to add'
115 within('form#new-watcher-form') do
115 within('form#new-watcher-form') do
116 fill_in 'user_search', :with => 'watch'
116 fill_in 'user_search', :with => 'watch'
117 assert page.has_content?('Some Watcher')
117 assert page.has_content?('Some Watcher')
118 check 'Some Watcher'
118 check 'Some Watcher'
119 click_button 'Add'
119 click_button 'Add'
120 end
120 end
121 assert page.has_css?('form#issue-form')
121 assert page.has_css?('form#issue-form')
122 assert page.has_css?('p#watchers_form')
122 assert page.has_css?('p#watchers_form')
123 using_wait_time(30) do
123 using_wait_time(30) do
124 within('span#watchers_inputs') do
124 within('span#watchers_inputs') do
125 within("label#issue_watcher_user_ids_#{user.id}") do
125 within("label#issue_watcher_user_ids_#{user.id}") do
126 assert has_content?('Some Watcher'), "No watcher content"
126 assert has_content?('Some Watcher'), "No watcher content"
127 end
127 end
128 end
128 end
129 end
129 end
130 assert_difference 'Issue.count' do
130 assert_difference 'Issue.count' do
131 find('input[name=commit]').click
131 find('input[name=commit]').click
132 end
132 end
133
133
134 issue = Issue.order('id desc').first
134 issue = Issue.order('id desc').first
135 assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort
135 assert_equal ['Dave Lopper', 'Some Watcher'], issue.watcher_users.map(&:name).sort
136 end
136 end
137
137
138 def test_create_issue_start_due_date
138 def test_create_issue_start_due_date
139 with_settings :default_issue_start_date_to_creation_date => 0 do
139 with_settings :default_issue_start_date_to_creation_date => 0 do
140 log_user('jsmith', 'jsmith')
140 log_user('jsmith', 'jsmith')
141 visit '/projects/ecookbook/issues/new'
141 visit '/projects/ecookbook/issues/new'
142 assert_equal "", page.find('input#issue_start_date').value
142 assert_equal "", page.find('input#issue_start_date').value
143 assert_equal "", page.find('input#issue_due_date').value
143 assert_equal "", page.find('input#issue_due_date').value
144 page.first('p#start_date_area img').click
144 page.first('p#start_date_area img').click
145 page.first("td.ui-datepicker-days-cell-over a").click
145 page.first("td.ui-datepicker-days-cell-over a").click
146 assert_equal Date.today.to_s, page.find('input#issue_start_date').value
146 assert_equal Date.today.to_s, page.find('input#issue_start_date').value
147 page.first('p#due_date_area img').click
147 page.first('p#due_date_area img').click
148 page.first("td.ui-datepicker-days-cell-over a").click
148 page.first("td.ui-datepicker-days-cell-over a").click
149 assert_equal Date.today.to_s, page.find('input#issue_due_date').value
149 assert_equal Date.today.to_s, page.find('input#issue_due_date').value
150 end
150 end
151 end
151 end
152
152
153 def test_default_due_date_proposed_in_date_picker
153 def test_default_due_date_proposed_in_date_picker
154 log_user('jsmith', 'jsmith')
154 log_user('jsmith', 'jsmith')
155 visit '/projects/ecookbook/issues/new'
155 visit '/projects/ecookbook/issues/new'
156
156
157 # Future start date: due date should default to start date
157 # Future start date: due date should default to start date
158 fill_in 'Start date', :with => '2027-04-01'
158 fill_in 'Start date', :with => '2027-04-01'
159 fill_in 'Due date', :with => ''
159 fill_in 'Due date', :with => ''
160 page.first('p#due_date_area img').click
160 page.first('p#due_date_area img').click
161 page.first("td.ui-datepicker-days-cell-over a").click
161 page.first("td.ui-datepicker-days-cell-over a").click
162 assert_equal '2027-04-01', page.find('input#issue_due_date').value
162 assert_equal '2027-04-01', page.find('input#issue_due_date').value
163
163
164 # Passed start date: due date should default to today
164 # Passed start date: due date should default to today
165 fill_in 'Start date', :with => '2012-04-01'
165 fill_in 'Start date', :with => '2012-04-01'
166 fill_in 'Due date', :with => ''
166 fill_in 'Due date', :with => ''
167 page.first('p#due_date_area img').click
167 page.first('p#due_date_area img').click
168 page.first("td.ui-datepicker-days-cell-over a").click
168 page.first("td.ui-datepicker-days-cell-over a").click
169 assert_equal Date.today.to_s, page.find('input#issue_due_date').value
169 assert_equal Date.today.to_s, page.find('input#issue_due_date').value
170 end
170 end
171
171
172 def test_default_start_date_proposed_in_date_picker
172 def test_default_start_date_proposed_in_date_picker
173 log_user('jsmith', 'jsmith')
173 log_user('jsmith', 'jsmith')
174 visit '/projects/ecookbook/issues/new'
174 visit '/projects/ecookbook/issues/new'
175
175
176 # Passed due date: start date should default to due date
176 # Passed due date: start date should default to due date
177 fill_in 'Start date', :with => ''
177 fill_in 'Start date', :with => ''
178 fill_in 'Due date', :with => '2012-04-01'
178 fill_in 'Due date', :with => '2012-04-01'
179 page.first('p#start_date_area img').click
179 page.first('p#start_date_area img').click
180 page.first("td.ui-datepicker-days-cell-over a").click
180 page.first("td.ui-datepicker-days-cell-over a").click
181 assert_equal '2012-04-01', page.find('input#issue_start_date').value
181 assert_equal '2012-04-01', page.find('input#issue_start_date').value
182 end
182 end
183
183
184 def test_preview_issue_description
184 def test_preview_issue_description
185 log_user('jsmith', 'jsmith')
185 log_user('jsmith', 'jsmith')
186 visit '/projects/ecookbook/issues/new'
186 visit '/projects/ecookbook/issues/new'
187 within('form#issue-form') do
187 within('form#issue-form') do
188 fill_in 'Subject', :with => 'new issue subject'
188 fill_in 'Subject', :with => 'new issue subject'
189 fill_in 'Description', :with => 'new issue description'
189 fill_in 'Description', :with => 'new issue description'
190 click_link 'Preview'
190 click_link 'Preview'
191 end
191 end
192 find 'div#preview fieldset', :visible => true, :text => 'new issue description'
192 find 'div#preview fieldset', :visible => true, :text => 'new issue description'
193 assert_difference 'Issue.count' do
193 assert_difference 'Issue.count' do
194 find('input[name=commit]').click
194 find('input[name=commit]').click
195 end
195 end
196
196
197 issue = Issue.order('id desc').first
197 issue = Issue.order('id desc').first
198 assert_equal 'new issue description', issue.description
198 assert_equal 'new issue description', issue.description
199 end
199 end
200
200
201 def test_update_issue_with_form_update
201 def test_update_issue_with_form_update
202 field = IssueCustomField.create!(
202 field = IssueCustomField.create!(
203 :field_format => 'string',
203 :field_format => 'string',
204 :name => 'Form update CF',
204 :name => 'Form update CF',
205 :is_for_all => true,
205 :is_for_all => true,
206 :trackers => Tracker.where(:name => 'Feature request')
206 :trackers => Tracker.where(:name => 'Feature request')
207 )
207 )
208
208
209 Role.non_member.add_permission! :edit_issues
209 Role.non_member.add_permission! :edit_issues
210 Role.non_member.remove_permission! :add_issues, :add_issue_notes
210 Role.non_member.remove_permission! :add_issues, :add_issue_notes
211
211
212 log_user('someone', 'foo')
212 log_user('someone', 'foo')
213 visit '/issues/1'
213 visit '/issues/1'
214 assert page.has_no_content?('Form update CF')
214 assert page.has_no_content?('Form update CF')
215
215
216 page.first(:link, 'Edit').click
216 page.first(:link, 'Edit').click
217 # the custom field should show up when changing tracker
217 # the custom field should show up when changing tracker
218 select 'Feature request', :from => 'Tracker'
218 select 'Feature request', :from => 'Tracker'
219 assert page.has_content?('Form update CF')
219 assert page.has_content?('Form update CF')
220
220
221 fill_in 'Form update', :with => 'CF value'
221 fill_in 'Form update', :with => 'CF value'
222 assert_no_difference 'Issue.count' do
222 assert_no_difference 'Issue.count' do
223 page.first(:button, 'Submit').click
223 page.first(:button, 'Submit').click
224 end
224 end
225
225
226 issue = Issue.find(1)
226 issue = Issue.find(1)
227 assert_equal 'CF value', issue.custom_field_value(field)
227 assert_equal 'CF value', issue.custom_field_value(field)
228 end
228 end
229
229
230 def test_remove_issue_watcher_from_sidebar
230 def test_remove_issue_watcher_from_sidebar
231 user = User.find(3)
231 user = User.find(3)
232 Watcher.create!(:watchable => Issue.find(1), :user => user)
232 Watcher.create!(:watchable => Issue.find(1), :user => user)
233
233
234 log_user('jsmith', 'jsmith')
234 log_user('jsmith', 'jsmith')
235 visit '/issues/1'
235 visit '/issues/1'
236 assert page.first('#sidebar').has_content?('Watchers (1)')
236 assert page.first('#sidebar').has_content?('Watchers (1)')
237 assert page.first('#sidebar').has_content?(user.name)
237 assert page.first('#sidebar').has_content?(user.name)
238 assert_difference 'Watcher.count', -1 do
238 assert_difference 'Watcher.count', -1 do
239 page.first('ul.watchers .user-3 a.delete').click
239 page.first('ul.watchers .user-3 a.delete').click
240 assert page.first('#sidebar').has_content?('Watchers (0)')
240 assert page.first('#sidebar').has_content?('Watchers (0)')
241 end
241 end
242 assert page.first('#sidebar').has_no_content?(user.name)
242 assert page.first('#sidebar').has_no_content?(user.name)
243 end
243 end
244
244
245 def test_watch_should_update_watchers_list
245 def test_watch_should_update_watchers_list
246 user = User.find(2)
246 user = User.find(2)
247 log_user('jsmith', 'jsmith')
247 log_user('jsmith', 'jsmith')
248 visit '/issues/1'
248 visit '/issues/1'
249 assert page.first('#sidebar').has_content?('Watchers (0)')
249 assert page.first('#sidebar').has_content?('Watchers (0)')
250
250
251 page.first('a.issue-1-watcher').click
251 page.first('a.issue-1-watcher').click
252 assert page.first('#sidebar').has_content?('Watchers (1)')
252 assert page.first('#sidebar').has_content?('Watchers (1)')
253 assert page.first('#sidebar').has_content?(user.name)
253 assert page.first('#sidebar').has_content?(user.name)
254 end
254 end
255
255
256 def test_watch_issue_via_context_menu
256 def test_watch_issue_via_context_menu
257 log_user('jsmith', 'jsmith')
257 log_user('jsmith', 'jsmith')
258 visit '/issues'
258 visit '/issues'
259 assert page.has_css?('tr#issue-1')
259 assert page.has_css?('tr#issue-1')
260 find('tr#issue-1 td.updated_on').click
260 find('tr#issue-1 td.updated_on').click
261 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
261 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
262 assert_difference 'Watcher.count' do
262 assert_difference 'Watcher.count' do
263 within('#context-menu') do
263 within('#context-menu') do
264 click_link 'Watch'
264 click_link 'Watch'
265 end
265 end
266 # wait for ajax response
266 # wait for ajax response
267 assert page.has_css?('#context-menu .issue-1-watcher.icon-fav')
267 assert page.has_css?('#context-menu .issue-1-watcher.icon-fav')
268 assert page.has_css?('tr#issue-1')
268 assert page.has_css?('tr#issue-1')
269 end
269 end
270 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
270 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
271 end
271 end
272
272
273 def test_bulk_watch_issues_via_context_menu
273 def test_bulk_watch_issues_via_context_menu
274 log_user('jsmith', 'jsmith')
274 log_user('jsmith', 'jsmith')
275 visit '/issues'
275 visit '/issues'
276 assert page.has_css?('tr#issue-1')
276 assert page.has_css?('tr#issue-1')
277 assert page.has_css?('tr#issue-4')
277 assert page.has_css?('tr#issue-4')
278 find('tr#issue-1 input[type=checkbox]').click
278 find('tr#issue-1 input[type=checkbox]').click
279 find('tr#issue-4 input[type=checkbox]').click
279 find('tr#issue-4 input[type=checkbox]').click
280 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
280 page.execute_script "$('tr#issue-1 td.updated_on').trigger('contextmenu');"
281 assert_difference 'Watcher.count', 2 do
281 assert_difference 'Watcher.count', 2 do
282 within('#context-menu') do
282 within('#context-menu') do
283 click_link 'Watch'
283 click_link 'Watch'
284 end
284 end
285 # wait for ajax response
285 # wait for ajax response
286 assert page.has_css?('#context-menu .issue-bulk-watcher.icon-fav')
286 assert page.has_css?('#context-menu .issue-bulk-watcher.icon-fav')
287 assert page.has_css?('tr#issue-1')
287 assert page.has_css?('tr#issue-1')
288 assert page.has_css?('tr#issue-4')
288 assert page.has_css?('tr#issue-4')
289 end
289 end
290 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
290 assert Issue.find(1).watched_by?(User.find_by_login('jsmith'))
291 assert Issue.find(4).watched_by?(User.find_by_login('jsmith'))
291 assert Issue.find(4).watched_by?(User.find_by_login('jsmith'))
292 end
292 end
293
294 def test_issue_list_with_default_totalable_columns
295 log_user('admin', 'admin')
296 with_settings :issue_list_default_totals => ['estimated_hours'] do
297 visit '/projects/ecookbook/issues'
298 # Check that the page shows the Estimated hours total
299 assert page.has_css?('p.query-totals')
300 assert page.has_css?('span.total-for-estimated-hours')
301 # Open the Options of the form (necessary for having the totalable columns options clickable)
302 page.all('legend')[1].click
303 # Deselect the default totalable column (none should be left)
304 page.first('input[name="t[]"][value="estimated_hours"]').click
305 within('#query_form') do
306 click_link 'Apply'
307 end
308 # Check that Totals are not present in the reloaded page
309 assert !page.has_css?('p.query-totals')
310 assert !page.has_css?('span.total-for-estimated-hours')
311 end
312 end
293 end
313 end
General Comments 0
You need to be logged in to leave comments. Login now