##// END OF EJS Templates
Merged r14242 (#18580)....
Jean-Philippe Lang -
r13864:20f56ce0aed9
parent child
Show More
@@ -1,97 +1,97
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class ContextMenusController < ApplicationController
18 class ContextMenusController < ApplicationController
19 helper :watchers
19 helper :watchers
20 helper :issues
20 helper :issues
21
21
22 before_filter :find_issues, :only => :issues
22 before_filter :find_issues, :only => :issues
23
23
24 def issues
24 def issues
25 if (@issues.size == 1)
25 if (@issues.size == 1)
26 @issue = @issues.first
26 @issue = @issues.first
27 end
27 end
28 @issue_ids = @issues.map(&:id).sort
28 @issue_ids = @issues.map(&:id).sort
29
29
30 @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
30 @allowed_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
31
31
32 @can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
32 @can = {:edit => User.current.allowed_to?(:edit_issues, @projects),
33 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
33 :log_time => (@project && User.current.allowed_to?(:log_time, @project)),
34 :copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
34 :copy => User.current.allowed_to?(:copy_issues, @projects) && Issue.allowed_target_projects.any?,
35 :delete => User.current.allowed_to?(:delete_issues, @projects)
35 :delete => User.current.allowed_to?(:delete_issues, @projects)
36 }
36 }
37 if @project
37 if @project
38 if @issue
38 if @issue
39 @assignables = @issue.assignable_users
39 @assignables = @issue.assignable_users
40 else
40 else
41 @assignables = @project.assignable_users
41 @assignables = @project.assignable_users
42 end
42 end
43 @trackers = @project.trackers
43 @trackers = @project.trackers
44 else
44 else
45 #when multiple projects, we only keep the intersection of each set
45 #when multiple projects, we only keep the intersection of each set
46 @assignables = @projects.map(&:assignable_users).reduce(:&)
46 @assignables = @projects.map(&:assignable_users).reduce(:&)
47 @trackers = @projects.map(&:trackers).reduce(:&)
47 @trackers = @projects.map(&:trackers).reduce(:&)
48 end
48 end
49 @versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
49 @versions = @projects.map {|p| p.shared_versions.open}.reduce(:&)
50
50
51 @priorities = IssuePriority.active.reverse
51 @priorities = IssuePriority.active.reverse
52 @back = back_url
52 @back = back_url
53
53
54 @options_by_custom_field = {}
54 @options_by_custom_field = {}
55 if @can[:edit]
55 if @can[:edit]
56 custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
56 custom_fields = @issues.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
57 custom_fields.each do |field|
57 custom_fields.each do |field|
58 values = field.possible_values_options(@projects)
58 values = field.possible_values_options(@projects)
59 if values.present?
59 if values.present?
60 @options_by_custom_field[field] = values
60 @options_by_custom_field[field] = values
61 end
61 end
62 end
62 end
63 end
63 end
64
64
65 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
65 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
66 render :layout => false
66 render :layout => false
67 end
67 end
68
68
69 def time_entries
69 def time_entries
70 @time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a
70 @time_entries = TimeEntry.where(:id => params[:ids]).preload(:project).to_a
71 (render_404; return) unless @time_entries.present?
71 (render_404; return) unless @time_entries.present?
72 if (@time_entries.size == 1)
72 if (@time_entries.size == 1)
73 @time_entry = @time_entries.first
73 @time_entry = @time_entries.first
74 end
74 end
75
75
76 @projects = @time_entries.collect(&:project).compact.uniq
76 @projects = @time_entries.collect(&:project).compact.uniq
77 @project = @projects.first if @projects.size == 1
77 @project = @projects.first if @projects.size == 1
78 @activities = TimeEntryActivity.shared.active
78 @activities = TimeEntryActivity.shared.active
79 @can = {:edit => User.current.allowed_to?(:edit_time_entries, @projects),
79
80 :delete => User.current.allowed_to?(:edit_time_entries, @projects)
80 edit_allowed = @time_entries.all? {|t| t.editable_by?(User.current)}
81 }
81 @can = {:edit => edit_allowed, :delete => edit_allowed}
82 @back = back_url
82 @back = back_url
83
83
84 @options_by_custom_field = {}
84 @options_by_custom_field = {}
85 if @can[:edit]
85 if @can[:edit]
86 custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
86 custom_fields = @time_entries.map(&:editable_custom_fields).reduce(:&).reject(&:multiple?)
87 custom_fields.each do |field|
87 custom_fields.each do |field|
88 values = field.possible_values_options(@projects)
88 values = field.possible_values_options(@projects)
89 if values.present?
89 if values.present?
90 @options_by_custom_field[field] = values
90 @options_by_custom_field[field] = values
91 end
91 end
92 end
92 end
93 end
93 end
94
94
95 render :layout => false
95 render :layout => false
96 end
96 end
97 end
97 end
@@ -1,280 +1,281
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class TimelogController < ApplicationController
18 class TimelogController < ApplicationController
19 menu_item :issues
19 menu_item :issues
20
20
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
21 before_filter :find_time_entry, :only => [:show, :edit, :update]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
22 before_filter :find_time_entries, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :authorize, :only => [:show, :edit, :update, :bulk_edit, :bulk_update, :destroy]
23 before_filter :authorize, :only => [:show, :edit, :update, :bulk_edit, :bulk_update, :destroy]
24
24
25 before_filter :find_optional_project, :only => [:new, :create, :index, :report]
25 before_filter :find_optional_project, :only => [:new, :create, :index, :report]
26 before_filter :authorize_global, :only => [:new, :create, :index, :report]
26 before_filter :authorize_global, :only => [:new, :create, :index, :report]
27
27
28 accept_rss_auth :index
28 accept_rss_auth :index
29 accept_api_auth :index, :show, :create, :update, :destroy
29 accept_api_auth :index, :show, :create, :update, :destroy
30
30
31 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
31 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32
32
33 helper :sort
33 helper :sort
34 include SortHelper
34 include SortHelper
35 helper :issues
35 helper :issues
36 include TimelogHelper
36 include TimelogHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :queries
39 helper :queries
40 include QueriesHelper
40 include QueriesHelper
41
41
42 def index
42 def index
43 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
43 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
44
44
45 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
45 sort_init(@query.sort_criteria.empty? ? [['spent_on', 'desc']] : @query.sort_criteria)
46 sort_update(@query.sortable_columns)
46 sort_update(@query.sortable_columns)
47 scope = time_entry_scope(:order => sort_clause).
47 scope = time_entry_scope(:order => sort_clause).
48 includes(:project, :user, :issue).
48 includes(:project, :user, :issue).
49 preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
49 preload(:issue => [:project, :tracker, :status, :assigned_to, :priority])
50
50
51 respond_to do |format|
51 respond_to do |format|
52 format.html {
52 format.html {
53 @entry_count = scope.count
53 @entry_count = scope.count
54 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
54 @entry_pages = Paginator.new @entry_count, per_page_option, params['page']
55 @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
55 @entries = scope.offset(@entry_pages.offset).limit(@entry_pages.per_page).to_a
56 @total_hours = scope.sum(:hours).to_f
56 @total_hours = scope.sum(:hours).to_f
57
57
58 render :layout => !request.xhr?
58 render :layout => !request.xhr?
59 }
59 }
60 format.api {
60 format.api {
61 @entry_count = scope.count
61 @entry_count = scope.count
62 @offset, @limit = api_offset_and_limit
62 @offset, @limit = api_offset_and_limit
63 @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).to_a
63 @entries = scope.offset(@offset).limit(@limit).preload(:custom_values => :custom_field).to_a
64 }
64 }
65 format.atom {
65 format.atom {
66 entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").to_a
66 entries = scope.limit(Setting.feeds_limit.to_i).reorder("#{TimeEntry.table_name}.created_on DESC").to_a
67 render_feed(entries, :title => l(:label_spent_time))
67 render_feed(entries, :title => l(:label_spent_time))
68 }
68 }
69 format.csv {
69 format.csv {
70 # Export all entries
70 # Export all entries
71 @entries = scope.to_a
71 @entries = scope.to_a
72 send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
72 send_data(query_to_csv(@entries, @query, params), :type => 'text/csv; header=present', :filename => 'timelog.csv')
73 }
73 }
74 end
74 end
75 end
75 end
76
76
77 def report
77 def report
78 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
78 @query = TimeEntryQuery.build_from_params(params, :project => @project, :name => '_')
79 scope = time_entry_scope
79 scope = time_entry_scope
80
80
81 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
81 @report = Redmine::Helpers::TimeReport.new(@project, @issue, params[:criteria], params[:columns], scope)
82
82
83 respond_to do |format|
83 respond_to do |format|
84 format.html { render :layout => !request.xhr? }
84 format.html { render :layout => !request.xhr? }
85 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
85 format.csv { send_data(report_to_csv(@report), :type => 'text/csv; header=present', :filename => 'timelog.csv') }
86 end
86 end
87 end
87 end
88
88
89 def show
89 def show
90 respond_to do |format|
90 respond_to do |format|
91 # TODO: Implement html response
91 # TODO: Implement html response
92 format.html { render :nothing => true, :status => 406 }
92 format.html { render :nothing => true, :status => 406 }
93 format.api
93 format.api
94 end
94 end
95 end
95 end
96
96
97 def new
97 def new
98 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
98 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
99 @time_entry.safe_attributes = params[:time_entry]
99 @time_entry.safe_attributes = params[:time_entry]
100 end
100 end
101
101
102 def create
102 def create
103 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
103 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => User.current.today)
104 @time_entry.safe_attributes = params[:time_entry]
104 @time_entry.safe_attributes = params[:time_entry]
105 if @time_entry.project && !User.current.allowed_to?(:log_time, @time_entry.project)
105 if @time_entry.project && !User.current.allowed_to?(:log_time, @time_entry.project)
106 render_403
106 render_403
107 return
107 return
108 end
108 end
109
109
110 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
110 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
111
111
112 if @time_entry.save
112 if @time_entry.save
113 respond_to do |format|
113 respond_to do |format|
114 format.html {
114 format.html {
115 flash[:notice] = l(:notice_successful_create)
115 flash[:notice] = l(:notice_successful_create)
116 if params[:continue]
116 if params[:continue]
117 options = {
117 options = {
118 :time_entry => {
118 :time_entry => {
119 :project_id => params[:time_entry][:project_id],
119 :project_id => params[:time_entry][:project_id],
120 :issue_id => @time_entry.issue_id,
120 :issue_id => @time_entry.issue_id,
121 :activity_id => @time_entry.activity_id
121 :activity_id => @time_entry.activity_id
122 },
122 },
123 :back_url => params[:back_url]
123 :back_url => params[:back_url]
124 }
124 }
125 if params[:project_id] && @time_entry.project
125 if params[:project_id] && @time_entry.project
126 redirect_to new_project_time_entry_path(@time_entry.project, options)
126 redirect_to new_project_time_entry_path(@time_entry.project, options)
127 elsif params[:issue_id] && @time_entry.issue
127 elsif params[:issue_id] && @time_entry.issue
128 redirect_to new_issue_time_entry_path(@time_entry.issue, options)
128 redirect_to new_issue_time_entry_path(@time_entry.issue, options)
129 else
129 else
130 redirect_to new_time_entry_path(options)
130 redirect_to new_time_entry_path(options)
131 end
131 end
132 else
132 else
133 redirect_back_or_default project_time_entries_path(@time_entry.project)
133 redirect_back_or_default project_time_entries_path(@time_entry.project)
134 end
134 end
135 }
135 }
136 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
136 format.api { render :action => 'show', :status => :created, :location => time_entry_url(@time_entry) }
137 end
137 end
138 else
138 else
139 respond_to do |format|
139 respond_to do |format|
140 format.html { render :action => 'new' }
140 format.html { render :action => 'new' }
141 format.api { render_validation_errors(@time_entry) }
141 format.api { render_validation_errors(@time_entry) }
142 end
142 end
143 end
143 end
144 end
144 end
145
145
146 def edit
146 def edit
147 @time_entry.safe_attributes = params[:time_entry]
147 @time_entry.safe_attributes = params[:time_entry]
148 end
148 end
149
149
150 def update
150 def update
151 @time_entry.safe_attributes = params[:time_entry]
151 @time_entry.safe_attributes = params[:time_entry]
152
152
153 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
153 call_hook(:controller_timelog_edit_before_save, { :params => params, :time_entry => @time_entry })
154
154
155 if @time_entry.save
155 if @time_entry.save
156 respond_to do |format|
156 respond_to do |format|
157 format.html {
157 format.html {
158 flash[:notice] = l(:notice_successful_update)
158 flash[:notice] = l(:notice_successful_update)
159 redirect_back_or_default project_time_entries_path(@time_entry.project)
159 redirect_back_or_default project_time_entries_path(@time_entry.project)
160 }
160 }
161 format.api { render_api_ok }
161 format.api { render_api_ok }
162 end
162 end
163 else
163 else
164 respond_to do |format|
164 respond_to do |format|
165 format.html { render :action => 'edit' }
165 format.html { render :action => 'edit' }
166 format.api { render_validation_errors(@time_entry) }
166 format.api { render_validation_errors(@time_entry) }
167 end
167 end
168 end
168 end
169 end
169 end
170
170
171 def bulk_edit
171 def bulk_edit
172 @available_activities = TimeEntryActivity.shared.active
172 @available_activities = TimeEntryActivity.shared.active
173 @custom_fields = TimeEntry.first.available_custom_fields
173 @custom_fields = TimeEntry.first.available_custom_fields
174 end
174 end
175
175
176 def bulk_update
176 def bulk_update
177 attributes = parse_params_for_bulk_time_entry_attributes(params)
177 attributes = parse_params_for_bulk_time_entry_attributes(params)
178
178
179 unsaved_time_entry_ids = []
179 unsaved_time_entry_ids = []
180 @time_entries.each do |time_entry|
180 @time_entries.each do |time_entry|
181 time_entry.reload
181 time_entry.reload
182 time_entry.safe_attributes = attributes
182 time_entry.safe_attributes = attributes
183 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
183 call_hook(:controller_time_entries_bulk_edit_before_save, { :params => params, :time_entry => time_entry })
184 unless time_entry.save
184 unless time_entry.save
185 logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info?
185 logger.info "time entry could not be updated: #{time_entry.errors.full_messages}" if logger && logger.info?
186 # Keep unsaved time_entry ids to display them in flash error
186 # Keep unsaved time_entry ids to display them in flash error
187 unsaved_time_entry_ids << time_entry.id
187 unsaved_time_entry_ids << time_entry.id
188 end
188 end
189 end
189 end
190 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
190 set_flash_from_bulk_time_entry_save(@time_entries, unsaved_time_entry_ids)
191 redirect_back_or_default project_time_entries_path(@projects.first)
191 redirect_back_or_default project_time_entries_path(@projects.first)
192 end
192 end
193
193
194 def destroy
194 def destroy
195 destroyed = TimeEntry.transaction do
195 destroyed = TimeEntry.transaction do
196 @time_entries.each do |t|
196 @time_entries.each do |t|
197 unless t.destroy && t.destroyed?
197 unless t.destroy && t.destroyed?
198 raise ActiveRecord::Rollback
198 raise ActiveRecord::Rollback
199 end
199 end
200 end
200 end
201 end
201 end
202
202
203 respond_to do |format|
203 respond_to do |format|
204 format.html {
204 format.html {
205 if destroyed
205 if destroyed
206 flash[:notice] = l(:notice_successful_delete)
206 flash[:notice] = l(:notice_successful_delete)
207 else
207 else
208 flash[:error] = l(:notice_unable_delete_time_entry)
208 flash[:error] = l(:notice_unable_delete_time_entry)
209 end
209 end
210 redirect_back_or_default project_time_entries_path(@projects.first)
210 redirect_back_or_default project_time_entries_path(@projects.first)
211 }
211 }
212 format.api {
212 format.api {
213 if destroyed
213 if destroyed
214 render_api_ok
214 render_api_ok
215 else
215 else
216 render_validation_errors(@time_entries)
216 render_validation_errors(@time_entries)
217 end
217 end
218 }
218 }
219 end
219 end
220 end
220 end
221
221
222 private
222 private
223 def find_time_entry
223 def find_time_entry
224 @time_entry = TimeEntry.find(params[:id])
224 @time_entry = TimeEntry.find(params[:id])
225 unless @time_entry.editable_by?(User.current)
225 unless @time_entry.editable_by?(User.current)
226 render_403
226 render_403
227 return false
227 return false
228 end
228 end
229 @project = @time_entry.project
229 @project = @time_entry.project
230 rescue ActiveRecord::RecordNotFound
230 rescue ActiveRecord::RecordNotFound
231 render_404
231 render_404
232 end
232 end
233
233
234 def find_time_entries
234 def find_time_entries
235 @time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).to_a
235 @time_entries = TimeEntry.where(:id => params[:id] || params[:ids]).to_a
236 raise ActiveRecord::RecordNotFound if @time_entries.empty?
236 raise ActiveRecord::RecordNotFound if @time_entries.empty?
237 raise Unauthorized unless @time_entries.all? {|t| t.editable_by?(User.current)}
237 @projects = @time_entries.collect(&:project).compact.uniq
238 @projects = @time_entries.collect(&:project).compact.uniq
238 @project = @projects.first if @projects.size == 1
239 @project = @projects.first if @projects.size == 1
239 rescue ActiveRecord::RecordNotFound
240 rescue ActiveRecord::RecordNotFound
240 render_404
241 render_404
241 end
242 end
242
243
243 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
244 def set_flash_from_bulk_time_entry_save(time_entries, unsaved_time_entry_ids)
244 if unsaved_time_entry_ids.empty?
245 if unsaved_time_entry_ids.empty?
245 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
246 flash[:notice] = l(:notice_successful_update) unless time_entries.empty?
246 else
247 else
247 flash[:error] = l(:notice_failed_to_save_time_entries,
248 flash[:error] = l(:notice_failed_to_save_time_entries,
248 :count => unsaved_time_entry_ids.size,
249 :count => unsaved_time_entry_ids.size,
249 :total => time_entries.size,
250 :total => time_entries.size,
250 :ids => '#' + unsaved_time_entry_ids.join(', #'))
251 :ids => '#' + unsaved_time_entry_ids.join(', #'))
251 end
252 end
252 end
253 end
253
254
254 def find_optional_project
255 def find_optional_project
255 if params[:issue_id].present?
256 if params[:issue_id].present?
256 @issue = Issue.find(params[:issue_id])
257 @issue = Issue.find(params[:issue_id])
257 @project = @issue.project
258 @project = @issue.project
258 elsif params[:project_id].present?
259 elsif params[:project_id].present?
259 @project = Project.find(params[:project_id])
260 @project = Project.find(params[:project_id])
260 end
261 end
261 rescue ActiveRecord::RecordNotFound
262 rescue ActiveRecord::RecordNotFound
262 render_404
263 render_404
263 end
264 end
264
265
265 # Returns the TimeEntry scope for index and report actions
266 # Returns the TimeEntry scope for index and report actions
266 def time_entry_scope(options={})
267 def time_entry_scope(options={})
267 scope = @query.results_scope(options)
268 scope = @query.results_scope(options)
268 if @issue
269 if @issue
269 scope = scope.on_issue(@issue)
270 scope = scope.on_issue(@issue)
270 end
271 end
271 scope
272 scope
272 end
273 end
273
274
274 def parse_params_for_bulk_time_entry_attributes(params)
275 def parse_params_for_bulk_time_entry_attributes(params)
275 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
276 attributes = (params[:time_entry] || {}).reject {|k,v| v.blank?}
276 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
277 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
277 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
278 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
278 attributes
279 attributes
279 end
280 end
280 end
281 end
@@ -1,288 +1,300
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
2 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class ContextMenusControllerTest < ActionController::TestCase
20 class ContextMenusControllerTest < ActionController::TestCase
21 fixtures :projects,
21 fixtures :projects,
22 :trackers,
22 :trackers,
23 :projects_trackers,
23 :projects_trackers,
24 :roles,
24 :roles,
25 :member_roles,
25 :member_roles,
26 :members,
26 :members,
27 :enabled_modules,
27 :enabled_modules,
28 :workflows,
28 :workflows,
29 :journals, :journal_details,
29 :journals, :journal_details,
30 :versions,
30 :versions,
31 :issues, :issue_statuses, :issue_categories,
31 :issues, :issue_statuses, :issue_categories,
32 :users,
32 :users,
33 :enumerations,
33 :enumerations,
34 :time_entries
34 :time_entries
35
35
36 def test_context_menu_one_issue
36 def test_context_menu_one_issue
37 @request.session[:user_id] = 2
37 @request.session[:user_id] = 2
38 get :issues, :ids => [1]
38 get :issues, :ids => [1]
39 assert_response :success
39 assert_response :success
40 assert_template 'context_menus/issues'
40 assert_template 'context_menus/issues'
41
41
42 assert_select 'a.icon-edit[href=?]', '/issues/1/edit', :text => 'Edit'
42 assert_select 'a.icon-edit[href=?]', '/issues/1/edit', :text => 'Edit'
43 assert_select 'a.icon-copy[href=?]', '/projects/ecookbook/issues/1/copy', :text => 'Copy'
43 assert_select 'a.icon-copy[href=?]', '/projects/ecookbook/issues/1/copy', :text => 'Copy'
44 assert_select 'a.icon-del[href=?]', '/issues?ids%5B%5D=1', :text => 'Delete'
44 assert_select 'a.icon-del[href=?]', '/issues?ids%5B%5D=1', :text => 'Delete'
45
45
46 # Statuses
46 # Statuses
47 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', :text => 'Closed'
47 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bstatus_id%5D=5', :text => 'Closed'
48 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', :text => 'Immediate'
48 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bpriority_id%5D=8', :text => 'Immediate'
49 # No inactive priorities
49 # No inactive priorities
50 assert_select 'a', :text => /Inactive Priority/, :count => 0
50 assert_select 'a', :text => /Inactive Priority/, :count => 0
51 # Versions
51 # Versions
52 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', :text => '2.0'
52 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=3', :text => '2.0'
53 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0'
53 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bfixed_version_id%5D=4', :text => 'eCookbook Subproject 1 - 2.0'
54 # Assignees
54 # Assignees
55 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
55 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=3', :text => 'Dave Lopper'
56 end
56 end
57
57
58 def test_context_menu_one_issue_by_anonymous
58 def test_context_menu_one_issue_by_anonymous
59 with_settings :default_language => 'en' do
59 with_settings :default_language => 'en' do
60 get :issues, :ids => [1]
60 get :issues, :ids => [1]
61 assert_response :success
61 assert_response :success
62 assert_template 'context_menus/issues'
62 assert_template 'context_menus/issues'
63 assert_select 'a.icon-del.disabled[href="#"]', :text => 'Delete'
63 assert_select 'a.icon-del.disabled[href="#"]', :text => 'Delete'
64 end
64 end
65 end
65 end
66
66
67 def test_context_menu_multiple_issues_of_same_project
67 def test_context_menu_multiple_issues_of_same_project
68 @request.session[:user_id] = 2
68 @request.session[:user_id] = 2
69 get :issues, :ids => [1, 2]
69 get :issues, :ids => [1, 2]
70 assert_response :success
70 assert_response :success
71 assert_template 'context_menus/issues'
71 assert_template 'context_menus/issues'
72 assert_not_nil assigns(:issues)
72 assert_not_nil assigns(:issues)
73 assert_equal [1, 2], assigns(:issues).map(&:id).sort
73 assert_equal [1, 2], assigns(:issues).map(&:id).sort
74
74
75 ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&')
75 ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&')
76
76
77 assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit'
77 assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit'
78 assert_select 'a.icon-copy[href=?]', "/issues/bulk_edit?copy=1&#{ids}", :text => 'Copy'
78 assert_select 'a.icon-copy[href=?]', "/issues/bulk_edit?copy=1&#{ids}", :text => 'Copy'
79 assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete'
79 assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete'
80
80
81 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed'
81 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed'
82 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate'
82 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate'
83 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", :text => 'Dave Lopper'
83 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", :text => 'Dave Lopper'
84 end
84 end
85
85
86 def test_context_menu_multiple_issues_of_different_projects
86 def test_context_menu_multiple_issues_of_different_projects
87 @request.session[:user_id] = 2
87 @request.session[:user_id] = 2
88 get :issues, :ids => [1, 2, 6]
88 get :issues, :ids => [1, 2, 6]
89 assert_response :success
89 assert_response :success
90 assert_template 'context_menus/issues'
90 assert_template 'context_menus/issues'
91 assert_not_nil assigns(:issues)
91 assert_not_nil assigns(:issues)
92 assert_equal [1, 2, 6], assigns(:issues).map(&:id).sort
92 assert_equal [1, 2, 6], assigns(:issues).map(&:id).sort
93
93
94 ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&')
94 ids = assigns(:issues).map(&:id).sort.map {|i| "ids%5B%5D=#{i}"}.join('&')
95
95
96 assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit'
96 assert_select 'a.icon-edit[href=?]', "/issues/bulk_edit?#{ids}", :text => 'Edit'
97 assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete'
97 assert_select 'a.icon-del[href=?]', "/issues?#{ids}", :text => 'Delete'
98
98
99 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed'
99 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bstatus_id%5D=5", :text => 'Closed'
100 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate'
100 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bpriority_id%5D=8", :text => 'Immediate'
101 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", :text => 'John Smith'
101 assert_select 'a[href=?]', "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=2", :text => 'John Smith'
102 end
102 end
103
103
104 def test_context_menu_should_include_list_custom_fields
104 def test_context_menu_should_include_list_custom_fields
105 field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
105 field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
106 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
106 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
107 @request.session[:user_id] = 2
107 @request.session[:user_id] = 2
108 get :issues, :ids => [1]
108 get :issues, :ids => [1]
109
109
110 assert_select "li.cf_#{field.id}" do
110 assert_select "li.cf_#{field.id}" do
111 assert_select 'a[href="#"]', :text => 'List'
111 assert_select 'a[href="#"]', :text => 'List'
112 assert_select 'ul' do
112 assert_select 'ul' do
113 assert_select 'a', 3
113 assert_select 'a', 3
114 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo'
114 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=Foo", :text => 'Foo'
115 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
115 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
116 end
116 end
117 end
117 end
118 end
118 end
119
119
120 def test_context_menu_should_not_include_null_value_for_required_custom_fields
120 def test_context_menu_should_not_include_null_value_for_required_custom_fields
121 field = IssueCustomField.create!(:name => 'List', :is_required => true, :field_format => 'list',
121 field = IssueCustomField.create!(:name => 'List', :is_required => true, :field_format => 'list',
122 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
122 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
123 @request.session[:user_id] = 2
123 @request.session[:user_id] = 2
124 get :issues, :ids => [1, 2]
124 get :issues, :ids => [1, 2]
125
125
126 assert_select "li.cf_#{field.id}" do
126 assert_select "li.cf_#{field.id}" do
127 assert_select 'a[href="#"]', :text => 'List'
127 assert_select 'a[href="#"]', :text => 'List'
128 assert_select 'ul' do
128 assert_select 'ul' do
129 assert_select 'a', 2
129 assert_select 'a', 2
130 assert_select 'a', :text => 'none', :count => 0
130 assert_select 'a', :text => 'none', :count => 0
131 end
131 end
132 end
132 end
133 end
133 end
134
134
135 def test_context_menu_on_single_issue_should_select_current_custom_field_value
135 def test_context_menu_on_single_issue_should_select_current_custom_field_value
136 field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
136 field = IssueCustomField.create!(:name => 'List', :field_format => 'list',
137 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
137 :possible_values => ['Foo', 'Bar'], :is_for_all => true, :tracker_ids => [1, 2, 3])
138 issue = Issue.find(1)
138 issue = Issue.find(1)
139 issue.custom_field_values = {field.id => 'Bar'}
139 issue.custom_field_values = {field.id => 'Bar'}
140 issue.save!
140 issue.save!
141 @request.session[:user_id] = 2
141 @request.session[:user_id] = 2
142 get :issues, :ids => [1]
142 get :issues, :ids => [1]
143
143
144 assert_select "li.cf_#{field.id}" do
144 assert_select "li.cf_#{field.id}" do
145 assert_select 'a[href="#"]', :text => 'List'
145 assert_select 'a[href="#"]', :text => 'List'
146 assert_select 'ul' do
146 assert_select 'ul' do
147 assert_select 'a', 3
147 assert_select 'a', 3
148 assert_select 'a.icon-checked', :text => 'Bar'
148 assert_select 'a.icon-checked', :text => 'Bar'
149 end
149 end
150 end
150 end
151 end
151 end
152
152
153 def test_context_menu_should_include_bool_custom_fields
153 def test_context_menu_should_include_bool_custom_fields
154 field = IssueCustomField.create!(:name => 'Bool', :field_format => 'bool',
154 field = IssueCustomField.create!(:name => 'Bool', :field_format => 'bool',
155 :is_for_all => true, :tracker_ids => [1, 2, 3])
155 :is_for_all => true, :tracker_ids => [1, 2, 3])
156 @request.session[:user_id] = 2
156 @request.session[:user_id] = 2
157 get :issues, :ids => [1]
157 get :issues, :ids => [1]
158
158
159 assert_select "li.cf_#{field.id}" do
159 assert_select "li.cf_#{field.id}" do
160 assert_select 'a[href="#"]', :text => 'Bool'
160 assert_select 'a[href="#"]', :text => 'Bool'
161 assert_select 'ul' do
161 assert_select 'ul' do
162 assert_select 'a', 3
162 assert_select 'a', 3
163 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=0", :text => 'No'
163 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=0", :text => 'No'
164 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1", :text => 'Yes'
164 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=1", :text => 'Yes'
165 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
165 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
166 end
166 end
167 end
167 end
168 end
168 end
169
169
170 def test_context_menu_should_include_user_custom_fields
170 def test_context_menu_should_include_user_custom_fields
171 field = IssueCustomField.create!(:name => 'User', :field_format => 'user',
171 field = IssueCustomField.create!(:name => 'User', :field_format => 'user',
172 :is_for_all => true, :tracker_ids => [1, 2, 3])
172 :is_for_all => true, :tracker_ids => [1, 2, 3])
173 @request.session[:user_id] = 2
173 @request.session[:user_id] = 2
174 get :issues, :ids => [1]
174 get :issues, :ids => [1]
175
175
176 assert_select "li.cf_#{field.id}" do
176 assert_select "li.cf_#{field.id}" do
177 assert_select 'a[href="#"]', :text => 'User'
177 assert_select 'a[href="#"]', :text => 'User'
178 assert_select 'ul' do
178 assert_select 'ul' do
179 assert_select 'a', Project.find(1).members.count + 1
179 assert_select 'a', Project.find(1).members.count + 1
180 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2", :text => 'John Smith'
180 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=2", :text => 'John Smith'
181 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
181 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
182 end
182 end
183 end
183 end
184 end
184 end
185
185
186 def test_context_menu_should_include_version_custom_fields
186 def test_context_menu_should_include_version_custom_fields
187 field = IssueCustomField.create!(:name => 'Version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1, 2, 3])
187 field = IssueCustomField.create!(:name => 'Version', :field_format => 'version', :is_for_all => true, :tracker_ids => [1, 2, 3])
188 @request.session[:user_id] = 2
188 @request.session[:user_id] = 2
189 get :issues, :ids => [1]
189 get :issues, :ids => [1]
190
190
191 assert_select "li.cf_#{field.id}" do
191 assert_select "li.cf_#{field.id}" do
192 assert_select 'a[href="#"]', :text => 'Version'
192 assert_select 'a[href="#"]', :text => 'Version'
193 assert_select 'ul' do
193 assert_select 'ul' do
194 assert_select 'a', Project.find(1).shared_versions.count + 1
194 assert_select 'a', Project.find(1).shared_versions.count + 1
195 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3", :text => '2.0'
195 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=3", :text => '2.0'
196 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
196 assert_select 'a[href=?]', "/issues/bulk_update?ids%5B%5D=1&issue%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
197 end
197 end
198 end
198 end
199 end
199 end
200
200
201 def test_context_menu_should_show_enabled_custom_fields_for_the_role_only
201 def test_context_menu_should_show_enabled_custom_fields_for_the_role_only
202 enabled_cf = IssueCustomField.generate!(:field_format => 'bool', :is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
202 enabled_cf = IssueCustomField.generate!(:field_format => 'bool', :is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [1,2])
203 disabled_cf = IssueCustomField.generate!(:field_format => 'bool', :is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
203 disabled_cf = IssueCustomField.generate!(:field_format => 'bool', :is_for_all => true, :tracker_ids => [1], :visible => false, :role_ids => [2])
204 issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
204 issue = Issue.generate!(:project_id => 1, :tracker_id => 1)
205
205
206 @request.session[:user_id] = 2
206 @request.session[:user_id] = 2
207 get :issues, :ids => [issue.id]
207 get :issues, :ids => [issue.id]
208
208
209 assert_select "li.cf_#{enabled_cf.id}"
209 assert_select "li.cf_#{enabled_cf.id}"
210 assert_select "li.cf_#{disabled_cf.id}", 0
210 assert_select "li.cf_#{disabled_cf.id}", 0
211 end
211 end
212
212
213 def test_context_menu_by_assignable_user_should_include_assigned_to_me_link
213 def test_context_menu_by_assignable_user_should_include_assigned_to_me_link
214 @request.session[:user_id] = 2
214 @request.session[:user_id] = 2
215 get :issues, :ids => [1]
215 get :issues, :ids => [1]
216 assert_response :success
216 assert_response :success
217 assert_template 'context_menus/issues'
217 assert_template 'context_menus/issues'
218
218
219 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=2', :text => / me /
219 assert_select 'a[href=?]', '/issues/bulk_update?ids%5B%5D=1&issue%5Bassigned_to_id%5D=2', :text => / me /
220 end
220 end
221
221
222 def test_context_menu_should_propose_shared_versions_for_issues_from_different_projects
222 def test_context_menu_should_propose_shared_versions_for_issues_from_different_projects
223 @request.session[:user_id] = 2
223 @request.session[:user_id] = 2
224 version = Version.create!(:name => 'Shared', :sharing => 'system', :project_id => 1)
224 version = Version.create!(:name => 'Shared', :sharing => 'system', :project_id => 1)
225
225
226 get :issues, :ids => [1, 4]
226 get :issues, :ids => [1, 4]
227 assert_response :success
227 assert_response :success
228 assert_template 'context_menus/issues'
228 assert_template 'context_menus/issues'
229
229
230 assert_include version, assigns(:versions)
230 assert_include version, assigns(:versions)
231 assert_select 'a', :text => 'eCookbook - Shared'
231 assert_select 'a', :text => 'eCookbook - Shared'
232 end
232 end
233
233
234 def test_context_menu_with_issue_that_is_not_visible_should_fail
234 def test_context_menu_with_issue_that_is_not_visible_should_fail
235 get :issues, :ids => [1, 4] # issue 4 is not visible
235 get :issues, :ids => [1, 4] # issue 4 is not visible
236 assert_response 302
236 assert_response 302
237 end
237 end
238
238
239 def test_should_respond_with_404_without_ids
239 def test_should_respond_with_404_without_ids
240 get :issues
240 get :issues
241 assert_response 404
241 assert_response 404
242 end
242 end
243
243
244 def test_time_entries_context_menu
244 def test_time_entries_context_menu
245 @request.session[:user_id] = 2
245 @request.session[:user_id] = 2
246 get :time_entries, :ids => [1, 2]
246 get :time_entries, :ids => [1, 2]
247 assert_response :success
247 assert_response :success
248 assert_template 'context_menus/time_entries'
248 assert_template 'context_menus/time_entries'
249
249
250 assert_select 'a:not(.disabled)', :text => 'Edit'
250 assert_select 'a:not(.disabled)', :text => 'Edit'
251 end
251 end
252
252
253 def test_context_menu_for_one_time_entry
253 def test_context_menu_for_one_time_entry
254 @request.session[:user_id] = 2
254 @request.session[:user_id] = 2
255 get :time_entries, :ids => [1]
255 get :time_entries, :ids => [1]
256 assert_response :success
256 assert_response :success
257 assert_template 'context_menus/time_entries'
257 assert_template 'context_menus/time_entries'
258
258
259 assert_select 'a:not(.disabled)', :text => 'Edit'
259 assert_select 'a:not(.disabled)', :text => 'Edit'
260 end
260 end
261
261
262 def test_time_entries_context_menu_should_include_custom_fields
262 def test_time_entries_context_menu_should_include_custom_fields
263 field = TimeEntryCustomField.generate!(:name => "Field", :field_format => "list", :possible_values => ["foo", "bar"])
263 field = TimeEntryCustomField.generate!(:name => "Field", :field_format => "list", :possible_values => ["foo", "bar"])
264
264
265 @request.session[:user_id] = 2
265 @request.session[:user_id] = 2
266 get :time_entries, :ids => [1, 2]
266 get :time_entries, :ids => [1, 2]
267 assert_response :success
267 assert_response :success
268 assert_select "li.cf_#{field.id}" do
268 assert_select "li.cf_#{field.id}" do
269 assert_select 'a[href="#"]', :text => "Field"
269 assert_select 'a[href="#"]', :text => "Field"
270 assert_select 'ul' do
270 assert_select 'ul' do
271 assert_select 'a', 3
271 assert_select 'a', 3
272 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=foo", :text => 'foo'
272 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=foo", :text => 'foo'
273 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=bar", :text => 'bar'
273 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=bar", :text => 'bar'
274 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
274 assert_select 'a[href=?]', "/time_entries/bulk_update?ids%5B%5D=1&ids%5B%5D=2&time_entry%5Bcustom_field_values%5D%5B#{field.id}%5D=__none__", :text => 'none'
275 end
275 end
276 end
276 end
277 end
277 end
278
278
279 def test_time_entries_context_menu_with_edit_own_time_entries_permission
280 @request.session[:user_id] = 2
281 Role.find_by_name('Manager').remove_permission! :edit_time_entries
282 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
283 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
284
285 get :time_entries, :ids => ids
286 assert_response :success
287 assert_template 'context_menus/time_entries'
288 assert_select 'a:not(.disabled)', :text => 'Edit'
289 end
290
279 def test_time_entries_context_menu_without_edit_permission
291 def test_time_entries_context_menu_without_edit_permission
280 @request.session[:user_id] = 2
292 @request.session[:user_id] = 2
281 Role.find_by_name('Manager').remove_permission! :edit_time_entries
293 Role.find_by_name('Manager').remove_permission! :edit_time_entries
282
294
283 get :time_entries, :ids => [1, 2]
295 get :time_entries, :ids => [1, 2]
284 assert_response :success
296 assert_response :success
285 assert_template 'context_menus/time_entries'
297 assert_template 'context_menus/time_entries'
286 assert_select 'a.disabled', :text => 'Edit'
298 assert_select 'a.disabled', :text => 'Edit'
287 end
299 end
288 end
300 end
@@ -1,773 +1,802
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # Redmine - project management software
2 # Redmine - project management software
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
3 # Copyright (C) 2006-2015 Jean-Philippe Lang
4 #
4 #
5 # This program is free software; you can redistribute it and/or
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
8 # of the License, or (at your option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful,
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
13 # GNU General Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
18
19 require File.expand_path('../../test_helper', __FILE__)
19 require File.expand_path('../../test_helper', __FILE__)
20
20
21 class TimelogControllerTest < ActionController::TestCase
21 class TimelogControllerTest < ActionController::TestCase
22 fixtures :projects, :enabled_modules, :roles, :members,
22 fixtures :projects, :enabled_modules, :roles, :members,
23 :member_roles, :issues, :time_entries, :users,
23 :member_roles, :issues, :time_entries, :users,
24 :trackers, :enumerations, :issue_statuses,
24 :trackers, :enumerations, :issue_statuses,
25 :custom_fields, :custom_values,
25 :custom_fields, :custom_values,
26 :projects_trackers, :custom_fields_trackers,
26 :projects_trackers, :custom_fields_trackers,
27 :custom_fields_projects
27 :custom_fields_projects
28
28
29 include Redmine::I18n
29 include Redmine::I18n
30
30
31 def test_new
31 def test_new
32 @request.session[:user_id] = 3
32 @request.session[:user_id] = 3
33 get :new
33 get :new
34 assert_response :success
34 assert_response :success
35 assert_template 'new'
35 assert_template 'new'
36 assert_select 'input[name=?][type=hidden]', 'project_id', 0
36 assert_select 'input[name=?][type=hidden]', 'project_id', 0
37 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
37 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
38 assert_select 'select[name=?]', 'time_entry[project_id]' do
38 assert_select 'select[name=?]', 'time_entry[project_id]' do
39 # blank option for project
39 # blank option for project
40 assert_select 'option[value=""]'
40 assert_select 'option[value=""]'
41 end
41 end
42 end
42 end
43
43
44 def test_new_with_project_id
44 def test_new_with_project_id
45 @request.session[:user_id] = 3
45 @request.session[:user_id] = 3
46 get :new, :project_id => 1
46 get :new, :project_id => 1
47 assert_response :success
47 assert_response :success
48 assert_template 'new'
48 assert_template 'new'
49 assert_select 'input[name=?][type=hidden]', 'project_id'
49 assert_select 'input[name=?][type=hidden]', 'project_id'
50 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
50 assert_select 'input[name=?][type=hidden]', 'issue_id', 0
51 assert_select 'select[name=?]', 'time_entry[project_id]', 0
51 assert_select 'select[name=?]', 'time_entry[project_id]', 0
52 end
52 end
53
53
54 def test_new_with_issue_id
54 def test_new_with_issue_id
55 @request.session[:user_id] = 3
55 @request.session[:user_id] = 3
56 get :new, :issue_id => 2
56 get :new, :issue_id => 2
57 assert_response :success
57 assert_response :success
58 assert_template 'new'
58 assert_template 'new'
59 assert_select 'input[name=?][type=hidden]', 'project_id', 0
59 assert_select 'input[name=?][type=hidden]', 'project_id', 0
60 assert_select 'input[name=?][type=hidden]', 'issue_id'
60 assert_select 'input[name=?][type=hidden]', 'issue_id'
61 assert_select 'select[name=?]', 'time_entry[project_id]', 0
61 assert_select 'select[name=?]', 'time_entry[project_id]', 0
62 end
62 end
63
63
64 def test_new_without_project_should_prefill_the_form
64 def test_new_without_project_should_prefill_the_form
65 @request.session[:user_id] = 3
65 @request.session[:user_id] = 3
66 get :new, :time_entry => {:project_id => '1'}
66 get :new, :time_entry => {:project_id => '1'}
67 assert_response :success
67 assert_response :success
68 assert_template 'new'
68 assert_template 'new'
69 assert_select 'select[name=?]', 'time_entry[project_id]' do
69 assert_select 'select[name=?]', 'time_entry[project_id]' do
70 assert_select 'option[value="1"][selected=selected]'
70 assert_select 'option[value="1"][selected=selected]'
71 end
71 end
72 end
72 end
73
73
74 def test_new_without_project_should_deny_without_permission
74 def test_new_without_project_should_deny_without_permission
75 Role.all.each {|role| role.remove_permission! :log_time}
75 Role.all.each {|role| role.remove_permission! :log_time}
76 @request.session[:user_id] = 3
76 @request.session[:user_id] = 3
77
77
78 get :new
78 get :new
79 assert_response 403
79 assert_response 403
80 end
80 end
81
81
82 def test_new_should_select_default_activity
82 def test_new_should_select_default_activity
83 @request.session[:user_id] = 3
83 @request.session[:user_id] = 3
84 get :new, :project_id => 1
84 get :new, :project_id => 1
85 assert_response :success
85 assert_response :success
86 assert_select 'select[name=?]', 'time_entry[activity_id]' do
86 assert_select 'select[name=?]', 'time_entry[activity_id]' do
87 assert_select 'option[selected=selected]', :text => 'Development'
87 assert_select 'option[selected=selected]', :text => 'Development'
88 end
88 end
89 end
89 end
90
90
91 def test_new_should_only_show_active_time_entry_activities
91 def test_new_should_only_show_active_time_entry_activities
92 @request.session[:user_id] = 3
92 @request.session[:user_id] = 3
93 get :new, :project_id => 1
93 get :new, :project_id => 1
94 assert_response :success
94 assert_response :success
95 assert_select 'option', :text => 'Inactive Activity', :count => 0
95 assert_select 'option', :text => 'Inactive Activity', :count => 0
96 end
96 end
97
97
98 def test_get_edit_existing_time
98 def test_get_edit_existing_time
99 @request.session[:user_id] = 2
99 @request.session[:user_id] = 2
100 get :edit, :id => 2, :project_id => nil
100 get :edit, :id => 2, :project_id => nil
101 assert_response :success
101 assert_response :success
102 assert_template 'edit'
102 assert_template 'edit'
103 assert_select 'form[action=?]', '/time_entries/2'
103 assert_select 'form[action=?]', '/time_entries/2'
104 end
104 end
105
105
106 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
106 def test_get_edit_with_an_existing_time_entry_with_inactive_activity
107 te = TimeEntry.find(1)
107 te = TimeEntry.find(1)
108 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
108 te.activity = TimeEntryActivity.find_by_name("Inactive Activity")
109 te.save!
109 te.save!
110
110
111 @request.session[:user_id] = 1
111 @request.session[:user_id] = 1
112 get :edit, :project_id => 1, :id => 1
112 get :edit, :project_id => 1, :id => 1
113 assert_response :success
113 assert_response :success
114 assert_template 'edit'
114 assert_template 'edit'
115 # Blank option since nothing is pre-selected
115 # Blank option since nothing is pre-selected
116 assert_select 'option', :text => '--- Please select ---'
116 assert_select 'option', :text => '--- Please select ---'
117 end
117 end
118
118
119 def test_post_create
119 def test_post_create
120 @request.session[:user_id] = 3
120 @request.session[:user_id] = 3
121 assert_difference 'TimeEntry.count' do
121 assert_difference 'TimeEntry.count' do
122 post :create, :project_id => 1,
122 post :create, :project_id => 1,
123 :time_entry => {:comments => 'Some work on TimelogControllerTest',
123 :time_entry => {:comments => 'Some work on TimelogControllerTest',
124 # Not the default activity
124 # Not the default activity
125 :activity_id => '11',
125 :activity_id => '11',
126 :spent_on => '2008-03-14',
126 :spent_on => '2008-03-14',
127 :issue_id => '1',
127 :issue_id => '1',
128 :hours => '7.3'}
128 :hours => '7.3'}
129 assert_redirected_to '/projects/ecookbook/time_entries'
129 assert_redirected_to '/projects/ecookbook/time_entries'
130 end
130 end
131
131
132 t = TimeEntry.order('id DESC').first
132 t = TimeEntry.order('id DESC').first
133 assert_not_nil t
133 assert_not_nil t
134 assert_equal 'Some work on TimelogControllerTest', t.comments
134 assert_equal 'Some work on TimelogControllerTest', t.comments
135 assert_equal 1, t.project_id
135 assert_equal 1, t.project_id
136 assert_equal 1, t.issue_id
136 assert_equal 1, t.issue_id
137 assert_equal 11, t.activity_id
137 assert_equal 11, t.activity_id
138 assert_equal 7.3, t.hours
138 assert_equal 7.3, t.hours
139 assert_equal 3, t.user_id
139 assert_equal 3, t.user_id
140 end
140 end
141
141
142 def test_post_create_with_blank_issue
142 def test_post_create_with_blank_issue
143 @request.session[:user_id] = 3
143 @request.session[:user_id] = 3
144 assert_difference 'TimeEntry.count' do
144 assert_difference 'TimeEntry.count' do
145 post :create, :project_id => 1,
145 post :create, :project_id => 1,
146 :time_entry => {:comments => 'Some work on TimelogControllerTest',
146 :time_entry => {:comments => 'Some work on TimelogControllerTest',
147 # Not the default activity
147 # Not the default activity
148 :activity_id => '11',
148 :activity_id => '11',
149 :issue_id => '',
149 :issue_id => '',
150 :spent_on => '2008-03-14',
150 :spent_on => '2008-03-14',
151 :hours => '7.3'}
151 :hours => '7.3'}
152 assert_redirected_to '/projects/ecookbook/time_entries'
152 assert_redirected_to '/projects/ecookbook/time_entries'
153 end
153 end
154
154
155 t = TimeEntry.order('id DESC').first
155 t = TimeEntry.order('id DESC').first
156 assert_not_nil t
156 assert_not_nil t
157 assert_equal 'Some work on TimelogControllerTest', t.comments
157 assert_equal 'Some work on TimelogControllerTest', t.comments
158 assert_equal 1, t.project_id
158 assert_equal 1, t.project_id
159 assert_nil t.issue_id
159 assert_nil t.issue_id
160 assert_equal 11, t.activity_id
160 assert_equal 11, t.activity_id
161 assert_equal 7.3, t.hours
161 assert_equal 7.3, t.hours
162 assert_equal 3, t.user_id
162 assert_equal 3, t.user_id
163 end
163 end
164
164
165 def test_create_on_project_with_time_tracking_disabled_should_fail
165 def test_create_on_project_with_time_tracking_disabled_should_fail
166 Project.find(1).disable_module! :time_tracking
166 Project.find(1).disable_module! :time_tracking
167
167
168 @request.session[:user_id] = 2
168 @request.session[:user_id] = 2
169 assert_no_difference 'TimeEntry.count' do
169 assert_no_difference 'TimeEntry.count' do
170 post :create, :time_entry => {
170 post :create, :time_entry => {
171 :project_id => '1', :issue_id => '',
171 :project_id => '1', :issue_id => '',
172 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
172 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
173 }
173 }
174 end
174 end
175 end
175 end
176
176
177 def test_create_on_project_without_permission_should_fail
177 def test_create_on_project_without_permission_should_fail
178 Role.find(1).remove_permission! :log_time
178 Role.find(1).remove_permission! :log_time
179
179
180 @request.session[:user_id] = 2
180 @request.session[:user_id] = 2
181 assert_no_difference 'TimeEntry.count' do
181 assert_no_difference 'TimeEntry.count' do
182 post :create, :time_entry => {
182 post :create, :time_entry => {
183 :project_id => '1', :issue_id => '',
183 :project_id => '1', :issue_id => '',
184 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
184 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
185 }
185 }
186 end
186 end
187 end
187 end
188
188
189 def test_create_on_issue_in_project_with_time_tracking_disabled_should_fail
189 def test_create_on_issue_in_project_with_time_tracking_disabled_should_fail
190 Project.find(1).disable_module! :time_tracking
190 Project.find(1).disable_module! :time_tracking
191
191
192 @request.session[:user_id] = 2
192 @request.session[:user_id] = 2
193 assert_no_difference 'TimeEntry.count' do
193 assert_no_difference 'TimeEntry.count' do
194 post :create, :time_entry => {
194 post :create, :time_entry => {
195 :project_id => '', :issue_id => '1',
195 :project_id => '', :issue_id => '1',
196 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
196 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
197 }
197 }
198 assert_select_error /Issue is invalid/
198 assert_select_error /Issue is invalid/
199 end
199 end
200 end
200 end
201
201
202 def test_create_on_issue_in_project_without_permission_should_fail
202 def test_create_on_issue_in_project_without_permission_should_fail
203 Role.find(1).remove_permission! :log_time
203 Role.find(1).remove_permission! :log_time
204
204
205 @request.session[:user_id] = 2
205 @request.session[:user_id] = 2
206 assert_no_difference 'TimeEntry.count' do
206 assert_no_difference 'TimeEntry.count' do
207 post :create, :time_entry => {
207 post :create, :time_entry => {
208 :project_id => '', :issue_id => '1',
208 :project_id => '', :issue_id => '1',
209 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
209 :activity_id => '11', :spent_on => '2008-03-14', :hours => '7.3'
210 }
210 }
211 assert_select_error /Issue is invalid/
211 assert_select_error /Issue is invalid/
212 end
212 end
213 end
213 end
214
214
215 def test_create_and_continue_at_project_level
215 def test_create_and_continue_at_project_level
216 @request.session[:user_id] = 2
216 @request.session[:user_id] = 2
217 assert_difference 'TimeEntry.count' do
217 assert_difference 'TimeEntry.count' do
218 post :create, :time_entry => {:project_id => '1',
218 post :create, :time_entry => {:project_id => '1',
219 :activity_id => '11',
219 :activity_id => '11',
220 :issue_id => '',
220 :issue_id => '',
221 :spent_on => '2008-03-14',
221 :spent_on => '2008-03-14',
222 :hours => '7.3'},
222 :hours => '7.3'},
223 :continue => '1'
223 :continue => '1'
224 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
224 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D=1'
225 end
225 end
226 end
226 end
227
227
228 def test_create_and_continue_at_issue_level
228 def test_create_and_continue_at_issue_level
229 @request.session[:user_id] = 2
229 @request.session[:user_id] = 2
230 assert_difference 'TimeEntry.count' do
230 assert_difference 'TimeEntry.count' do
231 post :create, :time_entry => {:project_id => '',
231 post :create, :time_entry => {:project_id => '',
232 :activity_id => '11',
232 :activity_id => '11',
233 :issue_id => '1',
233 :issue_id => '1',
234 :spent_on => '2008-03-14',
234 :spent_on => '2008-03-14',
235 :hours => '7.3'},
235 :hours => '7.3'},
236 :continue => '1'
236 :continue => '1'
237 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
237 assert_redirected_to '/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
238 end
238 end
239 end
239 end
240
240
241 def test_create_and_continue_with_project_id
241 def test_create_and_continue_with_project_id
242 @request.session[:user_id] = 2
242 @request.session[:user_id] = 2
243 assert_difference 'TimeEntry.count' do
243 assert_difference 'TimeEntry.count' do
244 post :create, :project_id => 1,
244 post :create, :project_id => 1,
245 :time_entry => {:activity_id => '11',
245 :time_entry => {:activity_id => '11',
246 :issue_id => '',
246 :issue_id => '',
247 :spent_on => '2008-03-14',
247 :spent_on => '2008-03-14',
248 :hours => '7.3'},
248 :hours => '7.3'},
249 :continue => '1'
249 :continue => '1'
250 assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D='
250 assert_redirected_to '/projects/ecookbook/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=&time_entry%5Bproject_id%5D='
251 end
251 end
252 end
252 end
253
253
254 def test_create_and_continue_with_issue_id
254 def test_create_and_continue_with_issue_id
255 @request.session[:user_id] = 2
255 @request.session[:user_id] = 2
256 assert_difference 'TimeEntry.count' do
256 assert_difference 'TimeEntry.count' do
257 post :create, :issue_id => 1,
257 post :create, :issue_id => 1,
258 :time_entry => {:activity_id => '11',
258 :time_entry => {:activity_id => '11',
259 :issue_id => '1',
259 :issue_id => '1',
260 :spent_on => '2008-03-14',
260 :spent_on => '2008-03-14',
261 :hours => '7.3'},
261 :hours => '7.3'},
262 :continue => '1'
262 :continue => '1'
263 assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
263 assert_redirected_to '/issues/1/time_entries/new?time_entry%5Bactivity_id%5D=11&time_entry%5Bissue_id%5D=1&time_entry%5Bproject_id%5D='
264 end
264 end
265 end
265 end
266
266
267 def test_create_without_log_time_permission_should_be_denied
267 def test_create_without_log_time_permission_should_be_denied
268 @request.session[:user_id] = 2
268 @request.session[:user_id] = 2
269 Role.find_by_name('Manager').remove_permission! :log_time
269 Role.find_by_name('Manager').remove_permission! :log_time
270 post :create, :project_id => 1,
270 post :create, :project_id => 1,
271 :time_entry => {:activity_id => '11',
271 :time_entry => {:activity_id => '11',
272 :issue_id => '',
272 :issue_id => '',
273 :spent_on => '2008-03-14',
273 :spent_on => '2008-03-14',
274 :hours => '7.3'}
274 :hours => '7.3'}
275
275
276 assert_response 403
276 assert_response 403
277 end
277 end
278
278
279 def test_create_without_project_and_issue_should_fail
279 def test_create_without_project_and_issue_should_fail
280 @request.session[:user_id] = 2
280 @request.session[:user_id] = 2
281 post :create, :time_entry => {:issue_id => ''}
281 post :create, :time_entry => {:issue_id => ''}
282
282
283 assert_response :success
283 assert_response :success
284 assert_template 'new'
284 assert_template 'new'
285 end
285 end
286
286
287 def test_create_with_failure
287 def test_create_with_failure
288 @request.session[:user_id] = 2
288 @request.session[:user_id] = 2
289 post :create, :project_id => 1,
289 post :create, :project_id => 1,
290 :time_entry => {:activity_id => '',
290 :time_entry => {:activity_id => '',
291 :issue_id => '',
291 :issue_id => '',
292 :spent_on => '2008-03-14',
292 :spent_on => '2008-03-14',
293 :hours => '7.3'}
293 :hours => '7.3'}
294
294
295 assert_response :success
295 assert_response :success
296 assert_template 'new'
296 assert_template 'new'
297 end
297 end
298
298
299 def test_create_without_project
299 def test_create_without_project
300 @request.session[:user_id] = 2
300 @request.session[:user_id] = 2
301 assert_difference 'TimeEntry.count' do
301 assert_difference 'TimeEntry.count' do
302 post :create, :time_entry => {:project_id => '1',
302 post :create, :time_entry => {:project_id => '1',
303 :activity_id => '11',
303 :activity_id => '11',
304 :issue_id => '',
304 :issue_id => '',
305 :spent_on => '2008-03-14',
305 :spent_on => '2008-03-14',
306 :hours => '7.3'}
306 :hours => '7.3'}
307 end
307 end
308
308
309 assert_redirected_to '/projects/ecookbook/time_entries'
309 assert_redirected_to '/projects/ecookbook/time_entries'
310 time_entry = TimeEntry.order('id DESC').first
310 time_entry = TimeEntry.order('id DESC').first
311 assert_equal 1, time_entry.project_id
311 assert_equal 1, time_entry.project_id
312 end
312 end
313
313
314 def test_create_without_project_should_fail_with_issue_not_inside_project
314 def test_create_without_project_should_fail_with_issue_not_inside_project
315 @request.session[:user_id] = 2
315 @request.session[:user_id] = 2
316 assert_no_difference 'TimeEntry.count' do
316 assert_no_difference 'TimeEntry.count' do
317 post :create, :time_entry => {:project_id => '1',
317 post :create, :time_entry => {:project_id => '1',
318 :activity_id => '11',
318 :activity_id => '11',
319 :issue_id => '5',
319 :issue_id => '5',
320 :spent_on => '2008-03-14',
320 :spent_on => '2008-03-14',
321 :hours => '7.3'}
321 :hours => '7.3'}
322 end
322 end
323
323
324 assert_response :success
324 assert_response :success
325 assert assigns(:time_entry).errors[:issue_id].present?
325 assert assigns(:time_entry).errors[:issue_id].present?
326 end
326 end
327
327
328 def test_create_without_project_should_deny_without_permission
328 def test_create_without_project_should_deny_without_permission
329 @request.session[:user_id] = 2
329 @request.session[:user_id] = 2
330 Project.find(3).disable_module!(:time_tracking)
330 Project.find(3).disable_module!(:time_tracking)
331
331
332 assert_no_difference 'TimeEntry.count' do
332 assert_no_difference 'TimeEntry.count' do
333 post :create, :time_entry => {:project_id => '3',
333 post :create, :time_entry => {:project_id => '3',
334 :activity_id => '11',
334 :activity_id => '11',
335 :issue_id => '',
335 :issue_id => '',
336 :spent_on => '2008-03-14',
336 :spent_on => '2008-03-14',
337 :hours => '7.3'}
337 :hours => '7.3'}
338 end
338 end
339
339
340 assert_response 403
340 assert_response 403
341 end
341 end
342
342
343 def test_create_without_project_with_failure
343 def test_create_without_project_with_failure
344 @request.session[:user_id] = 2
344 @request.session[:user_id] = 2
345 assert_no_difference 'TimeEntry.count' do
345 assert_no_difference 'TimeEntry.count' do
346 post :create, :time_entry => {:project_id => '1',
346 post :create, :time_entry => {:project_id => '1',
347 :activity_id => '11',
347 :activity_id => '11',
348 :issue_id => '',
348 :issue_id => '',
349 :spent_on => '2008-03-14',
349 :spent_on => '2008-03-14',
350 :hours => ''}
350 :hours => ''}
351 end
351 end
352
352
353 assert_response :success
353 assert_response :success
354 assert_select 'select[name=?]', 'time_entry[project_id]' do
354 assert_select 'select[name=?]', 'time_entry[project_id]' do
355 assert_select 'option[value="1"][selected=selected]'
355 assert_select 'option[value="1"][selected=selected]'
356 end
356 end
357 end
357 end
358
358
359 def test_update
359 def test_update
360 entry = TimeEntry.find(1)
360 entry = TimeEntry.find(1)
361 assert_equal 1, entry.issue_id
361 assert_equal 1, entry.issue_id
362 assert_equal 2, entry.user_id
362 assert_equal 2, entry.user_id
363
363
364 @request.session[:user_id] = 1
364 @request.session[:user_id] = 1
365 put :update, :id => 1,
365 put :update, :id => 1,
366 :time_entry => {:issue_id => '2',
366 :time_entry => {:issue_id => '2',
367 :hours => '8'}
367 :hours => '8'}
368 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
368 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
369 entry.reload
369 entry.reload
370
370
371 assert_equal 8, entry.hours
371 assert_equal 8, entry.hours
372 assert_equal 2, entry.issue_id
372 assert_equal 2, entry.issue_id
373 assert_equal 2, entry.user_id
373 assert_equal 2, entry.user_id
374 end
374 end
375
375
376 def test_update_should_allow_to_change_issue_to_another_project
376 def test_update_should_allow_to_change_issue_to_another_project
377 entry = TimeEntry.generate!(:issue_id => 1)
377 entry = TimeEntry.generate!(:issue_id => 1)
378
378
379 @request.session[:user_id] = 1
379 @request.session[:user_id] = 1
380 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
380 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
381 assert_response 302
381 assert_response 302
382 entry.reload
382 entry.reload
383
383
384 assert_equal 5, entry.issue_id
384 assert_equal 5, entry.issue_id
385 assert_equal 3, entry.project_id
385 assert_equal 3, entry.project_id
386 end
386 end
387
387
388 def test_update_should_not_allow_to_change_issue_to_an_invalid_project
388 def test_update_should_not_allow_to_change_issue_to_an_invalid_project
389 entry = TimeEntry.generate!(:issue_id => 1)
389 entry = TimeEntry.generate!(:issue_id => 1)
390 Project.find(3).disable_module!(:time_tracking)
390 Project.find(3).disable_module!(:time_tracking)
391
391
392 @request.session[:user_id] = 1
392 @request.session[:user_id] = 1
393 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
393 put :update, :id => entry.id, :time_entry => {:issue_id => '5'}
394 assert_response 200
394 assert_response 200
395 assert_include "Issue is invalid", assigns(:time_entry).errors.full_messages
395 assert_include "Issue is invalid", assigns(:time_entry).errors.full_messages
396 end
396 end
397
397
398 def test_get_bulk_edit
398 def test_get_bulk_edit
399 @request.session[:user_id] = 2
399 @request.session[:user_id] = 2
400 get :bulk_edit, :ids => [1, 2]
400 get :bulk_edit, :ids => [1, 2]
401 assert_response :success
401 assert_response :success
402 assert_template 'bulk_edit'
402 assert_template 'bulk_edit'
403
403
404 assert_select 'ul#bulk-selection' do
404 assert_select 'ul#bulk-selection' do
405 assert_select 'li', 2
405 assert_select 'li', 2
406 assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours'
406 assert_select 'li a', :text => '03/23/2007 - eCookbook: 4.25 hours'
407 end
407 end
408
408
409 assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
409 assert_select 'form#bulk_edit_form[action=?]', '/time_entries/bulk_update' do
410 # System wide custom field
410 # System wide custom field
411 assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
411 assert_select 'select[name=?]', 'time_entry[custom_field_values][10]'
412
412
413 # Activities
413 # Activities
414 assert_select 'select[name=?]', 'time_entry[activity_id]' do
414 assert_select 'select[name=?]', 'time_entry[activity_id]' do
415 assert_select 'option[value=""]', :text => '(No change)'
415 assert_select 'option[value=""]', :text => '(No change)'
416 assert_select 'option[value="9"]', :text => 'Design'
416 assert_select 'option[value="9"]', :text => 'Design'
417 end
417 end
418 end
418 end
419 end
419 end
420
420
421 def test_get_bulk_edit_on_different_projects
421 def test_get_bulk_edit_on_different_projects
422 @request.session[:user_id] = 2
422 @request.session[:user_id] = 2
423 get :bulk_edit, :ids => [1, 2, 6]
423 get :bulk_edit, :ids => [1, 2, 6]
424 assert_response :success
424 assert_response :success
425 assert_template 'bulk_edit'
425 assert_template 'bulk_edit'
426 end
426 end
427
427
428 def test_bulk_edit_with_edit_own_time_entries_permission
429 @request.session[:user_id] = 2
430 Role.find_by_name('Manager').remove_permission! :edit_time_entries
431 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
432 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
433
434 get :bulk_edit, :ids => ids
435 assert_response :success
436 end
437
428 def test_bulk_update
438 def test_bulk_update
429 @request.session[:user_id] = 2
439 @request.session[:user_id] = 2
430 # update time entry activity
440 # update time entry activity
431 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
441 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9}
432
442
433 assert_response 302
443 assert_response 302
434 # check that the issues were updated
444 # check that the issues were updated
435 assert_equal [9, 9], TimeEntry.where(:id => [1, 2]).collect {|i| i.activity_id}
445 assert_equal [9, 9], TimeEntry.where(:id => [1, 2]).collect {|i| i.activity_id}
436 end
446 end
437
447
438 def test_bulk_update_with_failure
448 def test_bulk_update_with_failure
439 @request.session[:user_id] = 2
449 @request.session[:user_id] = 2
440 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
450 post :bulk_update, :ids => [1, 2], :time_entry => { :hours => 'A'}
441
451
442 assert_response 302
452 assert_response 302
443 assert_match /Failed to save 2 time entrie/, flash[:error]
453 assert_match /Failed to save 2 time entrie/, flash[:error]
444 end
454 end
445
455
446 def test_bulk_update_on_different_projects
456 def test_bulk_update_on_different_projects
447 @request.session[:user_id] = 2
457 @request.session[:user_id] = 2
448 # makes user a manager on the other project
458 # makes user a manager on the other project
449 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
459 Member.create!(:user_id => 2, :project_id => 3, :role_ids => [1])
450
460
451 # update time entry activity
461 # update time entry activity
452 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
462 post :bulk_update, :ids => [1, 2, 4], :time_entry => { :activity_id => 9 }
453
463
454 assert_response 302
464 assert_response 302
455 # check that the issues were updated
465 # check that the issues were updated
456 assert_equal [9, 9, 9], TimeEntry.where(:id => [1, 2, 4]).collect {|i| i.activity_id}
466 assert_equal [9, 9, 9], TimeEntry.where(:id => [1, 2, 4]).collect {|i| i.activity_id}
457 end
467 end
458
468
459 def test_bulk_update_on_different_projects_without_rights
469 def test_bulk_update_on_different_projects_without_rights
460 @request.session[:user_id] = 3
470 @request.session[:user_id] = 3
461 user = User.find(3)
471 user = User.find(3)
462 action = { :controller => "timelog", :action => "bulk_update" }
472 action = { :controller => "timelog", :action => "bulk_update" }
463 assert user.allowed_to?(action, TimeEntry.find(1).project)
473 assert user.allowed_to?(action, TimeEntry.find(1).project)
464 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
474 assert ! user.allowed_to?(action, TimeEntry.find(5).project)
465 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
475 post :bulk_update, :ids => [1, 5], :time_entry => { :activity_id => 9 }
466 assert_response 403
476 assert_response 403
467 end
477 end
468
478
479 def test_bulk_update_with_edit_own_time_entries_permission
480 @request.session[:user_id] = 2
481 Role.find_by_name('Manager').remove_permission! :edit_time_entries
482 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
483 ids = (0..1).map {TimeEntry.generate!(:user => User.find(2)).id}
484
485 post :bulk_update, :ids => ids, :time_entry => { :activity_id => 9 }
486 assert_response 302
487 end
488
489 def test_bulk_update_with_edit_own_time_entries_permissions_should_be_denied_for_time_entries_of_other_user
490 @request.session[:user_id] = 2
491 Role.find_by_name('Manager').remove_permission! :edit_time_entries
492 Role.find_by_name('Manager').add_permission! :edit_own_time_entries
493
494 post :bulk_update, :ids => [1, 2], :time_entry => { :activity_id => 9 }
495 assert_response 403
496 end
497
469 def test_bulk_update_custom_field
498 def test_bulk_update_custom_field
470 @request.session[:user_id] = 2
499 @request.session[:user_id] = 2
471 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
500 post :bulk_update, :ids => [1, 2], :time_entry => { :custom_field_values => {'10' => '0'} }
472
501
473 assert_response 302
502 assert_response 302
474 assert_equal ["0", "0"], TimeEntry.where(:id => [1, 2]).collect {|i| i.custom_value_for(10).value}
503 assert_equal ["0", "0"], TimeEntry.where(:id => [1, 2]).collect {|i| i.custom_value_for(10).value}
475 end
504 end
476
505
477 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
506 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
478 @request.session[:user_id] = 2
507 @request.session[:user_id] = 2
479 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
508 post :bulk_update, :ids => [1,2], :back_url => '/time_entries'
480
509
481 assert_response :redirect
510 assert_response :redirect
482 assert_redirected_to '/time_entries'
511 assert_redirected_to '/time_entries'
483 end
512 end
484
513
485 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
514 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
486 @request.session[:user_id] = 2
515 @request.session[:user_id] = 2
487 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
516 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
488
517
489 assert_response :redirect
518 assert_response :redirect
490 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
519 assert_redirected_to :controller => 'timelog', :action => 'index', :project_id => Project.find(1).identifier
491 end
520 end
492
521
493 def test_post_bulk_update_without_edit_permission_should_be_denied
522 def test_post_bulk_update_without_edit_permission_should_be_denied
494 @request.session[:user_id] = 2
523 @request.session[:user_id] = 2
495 Role.find_by_name('Manager').remove_permission! :edit_time_entries
524 Role.find_by_name('Manager').remove_permission! :edit_time_entries
496 post :bulk_update, :ids => [1,2]
525 post :bulk_update, :ids => [1,2]
497
526
498 assert_response 403
527 assert_response 403
499 end
528 end
500
529
501 def test_destroy
530 def test_destroy
502 @request.session[:user_id] = 2
531 @request.session[:user_id] = 2
503 delete :destroy, :id => 1
532 delete :destroy, :id => 1
504 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
533 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
505 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
534 assert_equal I18n.t(:notice_successful_delete), flash[:notice]
506 assert_nil TimeEntry.find_by_id(1)
535 assert_nil TimeEntry.find_by_id(1)
507 end
536 end
508
537
509 def test_destroy_should_fail
538 def test_destroy_should_fail
510 # simulate that this fails (e.g. due to a plugin), see #5700
539 # simulate that this fails (e.g. due to a plugin), see #5700
511 TimeEntry.any_instance.expects(:destroy).returns(false)
540 TimeEntry.any_instance.expects(:destroy).returns(false)
512
541
513 @request.session[:user_id] = 2
542 @request.session[:user_id] = 2
514 delete :destroy, :id => 1
543 delete :destroy, :id => 1
515 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
544 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
516 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
545 assert_equal I18n.t(:notice_unable_delete_time_entry), flash[:error]
517 assert_not_nil TimeEntry.find_by_id(1)
546 assert_not_nil TimeEntry.find_by_id(1)
518 end
547 end
519
548
520 def test_index_all_projects
549 def test_index_all_projects
521 get :index
550 get :index
522 assert_response :success
551 assert_response :success
523 assert_template 'index'
552 assert_template 'index'
524 assert_not_nil assigns(:total_hours)
553 assert_not_nil assigns(:total_hours)
525 assert_equal "162.90", "%.2f" % assigns(:total_hours)
554 assert_equal "162.90", "%.2f" % assigns(:total_hours)
526 assert_select 'form#query_form[action=?]', '/time_entries'
555 assert_select 'form#query_form[action=?]', '/time_entries'
527 end
556 end
528
557
529 def test_index_all_projects_should_show_log_time_link
558 def test_index_all_projects_should_show_log_time_link
530 @request.session[:user_id] = 2
559 @request.session[:user_id] = 2
531 get :index
560 get :index
532 assert_response :success
561 assert_response :success
533 assert_template 'index'
562 assert_template 'index'
534 assert_select 'a[href=?]', '/time_entries/new', :text => /Log time/
563 assert_select 'a[href=?]', '/time_entries/new', :text => /Log time/
535 end
564 end
536
565
537 def test_index_my_spent_time
566 def test_index_my_spent_time
538 @request.session[:user_id] = 2
567 @request.session[:user_id] = 2
539 get :index, :user_id => 'me'
568 get :index, :user_id => 'me'
540 assert_response :success
569 assert_response :success
541 assert_template 'index'
570 assert_template 'index'
542 assert assigns(:entries).all? {|entry| entry.user_id == 2}
571 assert assigns(:entries).all? {|entry| entry.user_id == 2}
543 end
572 end
544
573
545 def test_index_at_project_level
574 def test_index_at_project_level
546 get :index, :project_id => 'ecookbook'
575 get :index, :project_id => 'ecookbook'
547 assert_response :success
576 assert_response :success
548 assert_template 'index'
577 assert_template 'index'
549 assert_not_nil assigns(:entries)
578 assert_not_nil assigns(:entries)
550 assert_equal 4, assigns(:entries).size
579 assert_equal 4, assigns(:entries).size
551 # project and subproject
580 # project and subproject
552 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
581 assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
553 assert_not_nil assigns(:total_hours)
582 assert_not_nil assigns(:total_hours)
554 assert_equal "162.90", "%.2f" % assigns(:total_hours)
583 assert_equal "162.90", "%.2f" % assigns(:total_hours)
555 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
584 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
556 end
585 end
557
586
558 def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries
587 def test_index_with_display_subprojects_issues_to_false_should_not_include_subproject_entries
559 entry = TimeEntry.generate!(:project => Project.find(3))
588 entry = TimeEntry.generate!(:project => Project.find(3))
560
589
561 with_settings :display_subprojects_issues => '0' do
590 with_settings :display_subprojects_issues => '0' do
562 get :index, :project_id => 'ecookbook'
591 get :index, :project_id => 'ecookbook'
563 assert_response :success
592 assert_response :success
564 assert_template 'index'
593 assert_template 'index'
565 assert_not_include entry, assigns(:entries)
594 assert_not_include entry, assigns(:entries)
566 end
595 end
567 end
596 end
568
597
569 def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries
598 def test_index_with_display_subprojects_issues_to_false_and_subproject_filter_should_include_subproject_entries
570 entry = TimeEntry.generate!(:project => Project.find(3))
599 entry = TimeEntry.generate!(:project => Project.find(3))
571
600
572 with_settings :display_subprojects_issues => '0' do
601 with_settings :display_subprojects_issues => '0' do
573 get :index, :project_id => 'ecookbook', :subproject_id => 3
602 get :index, :project_id => 'ecookbook', :subproject_id => 3
574 assert_response :success
603 assert_response :success
575 assert_template 'index'
604 assert_template 'index'
576 assert_include entry, assigns(:entries)
605 assert_include entry, assigns(:entries)
577 end
606 end
578 end
607 end
579
608
580 def test_index_at_project_level_with_date_range
609 def test_index_at_project_level_with_date_range
581 get :index, :project_id => 'ecookbook',
610 get :index, :project_id => 'ecookbook',
582 :f => ['spent_on'],
611 :f => ['spent_on'],
583 :op => {'spent_on' => '><'},
612 :op => {'spent_on' => '><'},
584 :v => {'spent_on' => ['2007-03-20', '2007-04-30']}
613 :v => {'spent_on' => ['2007-03-20', '2007-04-30']}
585 assert_response :success
614 assert_response :success
586 assert_template 'index'
615 assert_template 'index'
587 assert_not_nil assigns(:entries)
616 assert_not_nil assigns(:entries)
588 assert_equal 3, assigns(:entries).size
617 assert_equal 3, assigns(:entries).size
589 assert_not_nil assigns(:total_hours)
618 assert_not_nil assigns(:total_hours)
590 assert_equal "12.90", "%.2f" % assigns(:total_hours)
619 assert_equal "12.90", "%.2f" % assigns(:total_hours)
591 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
620 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
592 end
621 end
593
622
594 def test_index_at_project_level_with_date_range_using_from_and_to_params
623 def test_index_at_project_level_with_date_range_using_from_and_to_params
595 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
624 get :index, :project_id => 'ecookbook', :from => '2007-03-20', :to => '2007-04-30'
596 assert_response :success
625 assert_response :success
597 assert_template 'index'
626 assert_template 'index'
598 assert_not_nil assigns(:entries)
627 assert_not_nil assigns(:entries)
599 assert_equal 3, assigns(:entries).size
628 assert_equal 3, assigns(:entries).size
600 assert_not_nil assigns(:total_hours)
629 assert_not_nil assigns(:total_hours)
601 assert_equal "12.90", "%.2f" % assigns(:total_hours)
630 assert_equal "12.90", "%.2f" % assigns(:total_hours)
602 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
631 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
603 end
632 end
604
633
605 def test_index_at_project_level_with_period
634 def test_index_at_project_level_with_period
606 get :index, :project_id => 'ecookbook',
635 get :index, :project_id => 'ecookbook',
607 :f => ['spent_on'],
636 :f => ['spent_on'],
608 :op => {'spent_on' => '>t-'},
637 :op => {'spent_on' => '>t-'},
609 :v => {'spent_on' => ['7']}
638 :v => {'spent_on' => ['7']}
610 assert_response :success
639 assert_response :success
611 assert_template 'index'
640 assert_template 'index'
612 assert_not_nil assigns(:entries)
641 assert_not_nil assigns(:entries)
613 assert_not_nil assigns(:total_hours)
642 assert_not_nil assigns(:total_hours)
614 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
643 assert_select 'form#query_form[action=?]', '/projects/ecookbook/time_entries'
615 end
644 end
616
645
617 def test_index_at_issue_level
646 def test_index_at_issue_level
618 get :index, :issue_id => 1
647 get :index, :issue_id => 1
619 assert_response :success
648 assert_response :success
620 assert_template 'index'
649 assert_template 'index'
621 assert_not_nil assigns(:entries)
650 assert_not_nil assigns(:entries)
622 assert_equal 2, assigns(:entries).size
651 assert_equal 2, assigns(:entries).size
623 assert_not_nil assigns(:total_hours)
652 assert_not_nil assigns(:total_hours)
624 assert_equal 154.25, assigns(:total_hours)
653 assert_equal 154.25, assigns(:total_hours)
625 # display all time
654 # display all time
626 assert_nil assigns(:from)
655 assert_nil assigns(:from)
627 assert_nil assigns(:to)
656 assert_nil assigns(:to)
628 assert_select 'form#query_form[action=?]', '/issues/1/time_entries'
657 assert_select 'form#query_form[action=?]', '/issues/1/time_entries'
629 end
658 end
630
659
631 def test_index_should_sort_by_spent_on_and_created_on
660 def test_index_should_sort_by_spent_on_and_created_on
632 t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10)
661 t1 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:00:00', :activity_id => 10)
633 t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10)
662 t2 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-16', :created_on => '2012-06-16 20:05:00', :activity_id => 10)
634 t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10)
663 t3 = TimeEntry.create!(:user => User.find(1), :project => Project.find(1), :hours => 1, :spent_on => '2012-06-15', :created_on => '2012-06-16 20:10:00', :activity_id => 10)
635
664
636 get :index, :project_id => 1,
665 get :index, :project_id => 1,
637 :f => ['spent_on'],
666 :f => ['spent_on'],
638 :op => {'spent_on' => '><'},
667 :op => {'spent_on' => '><'},
639 :v => {'spent_on' => ['2012-06-15', '2012-06-16']}
668 :v => {'spent_on' => ['2012-06-15', '2012-06-16']}
640 assert_response :success
669 assert_response :success
641 assert_equal [t2, t1, t3], assigns(:entries)
670 assert_equal [t2, t1, t3], assigns(:entries)
642
671
643 get :index, :project_id => 1,
672 get :index, :project_id => 1,
644 :f => ['spent_on'],
673 :f => ['spent_on'],
645 :op => {'spent_on' => '><'},
674 :op => {'spent_on' => '><'},
646 :v => {'spent_on' => ['2012-06-15', '2012-06-16']},
675 :v => {'spent_on' => ['2012-06-15', '2012-06-16']},
647 :sort => 'spent_on'
676 :sort => 'spent_on'
648 assert_response :success
677 assert_response :success
649 assert_equal [t3, t1, t2], assigns(:entries)
678 assert_equal [t3, t1, t2], assigns(:entries)
650 end
679 end
651
680
652 def test_index_with_filter_on_issue_custom_field
681 def test_index_with_filter_on_issue_custom_field
653 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
682 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
654 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
683 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
655
684
656 get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']}
685 get :index, :f => ['issue.cf_2'], :op => {'issue.cf_2' => '='}, :v => {'issue.cf_2' => ['filter_on_issue_custom_field']}
657 assert_response :success
686 assert_response :success
658 assert_equal [entry], assigns(:entries)
687 assert_equal [entry], assigns(:entries)
659 end
688 end
660
689
661 def test_index_with_issue_custom_field_column
690 def test_index_with_issue_custom_field_column
662 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
691 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {2 => 'filter_on_issue_custom_field'})
663 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
692 entry = TimeEntry.generate!(:issue => issue, :hours => 2.5)
664
693
665 get :index, :c => %w(project spent_on issue comments hours issue.cf_2)
694 get :index, :c => %w(project spent_on issue comments hours issue.cf_2)
666 assert_response :success
695 assert_response :success
667 assert_include :'issue.cf_2', assigns(:query).column_names
696 assert_include :'issue.cf_2', assigns(:query).column_names
668 assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field'
697 assert_select 'td.issue_cf_2', :text => 'filter_on_issue_custom_field'
669 end
698 end
670
699
671 def test_index_with_time_entry_custom_field_column
700 def test_index_with_time_entry_custom_field_column
672 field = TimeEntryCustomField.generate!(:field_format => 'string')
701 field = TimeEntryCustomField.generate!(:field_format => 'string')
673 entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'})
702 entry = TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value'})
674 field_name = "cf_#{field.id}"
703 field_name = "cf_#{field.id}"
675
704
676 get :index, :c => ["hours", field_name]
705 get :index, :c => ["hours", field_name]
677 assert_response :success
706 assert_response :success
678 assert_include field_name.to_sym, assigns(:query).column_names
707 assert_include field_name.to_sym, assigns(:query).column_names
679 assert_select "td.#{field_name}", :text => 'CF Value'
708 assert_select "td.#{field_name}", :text => 'CF Value'
680 end
709 end
681
710
682 def test_index_with_time_entry_custom_field_sorting
711 def test_index_with_time_entry_custom_field_sorting
683 field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field')
712 field = TimeEntryCustomField.generate!(:field_format => 'string', :name => 'String Field')
684 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'})
713 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 1'})
685 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'})
714 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 3'})
686 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'})
715 TimeEntry.generate!(:hours => 2.5, :custom_field_values => {field.id => 'CF Value 2'})
687 field_name = "cf_#{field.id}"
716 field_name = "cf_#{field.id}"
688
717
689 get :index, :c => ["hours", field_name], :sort => field_name
718 get :index, :c => ["hours", field_name], :sort => field_name
690 assert_response :success
719 assert_response :success
691 assert_include field_name.to_sym, assigns(:query).column_names
720 assert_include field_name.to_sym, assigns(:query).column_names
692 assert_select "th a.sort", :text => 'String Field'
721 assert_select "th a.sort", :text => 'String Field'
693
722
694 # Make sure that values are properly sorted
723 # Make sure that values are properly sorted
695 values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact
724 values = assigns(:entries).map {|e| e.custom_field_value(field)}.compact
696 assert_equal 3, values.size
725 assert_equal 3, values.size
697 assert_equal values.sort, values
726 assert_equal values.sort, values
698 end
727 end
699
728
700 def test_index_atom_feed
729 def test_index_atom_feed
701 get :index, :project_id => 1, :format => 'atom'
730 get :index, :project_id => 1, :format => 'atom'
702 assert_response :success
731 assert_response :success
703 assert_equal 'application/atom+xml', @response.content_type
732 assert_equal 'application/atom+xml', @response.content_type
704 assert_not_nil assigns(:items)
733 assert_not_nil assigns(:items)
705 assert assigns(:items).first.is_a?(TimeEntry)
734 assert assigns(:items).first.is_a?(TimeEntry)
706 end
735 end
707
736
708 def test_index_at_project_level_should_include_csv_export_dialog
737 def test_index_at_project_level_should_include_csv_export_dialog
709 get :index, :project_id => 'ecookbook',
738 get :index, :project_id => 'ecookbook',
710 :f => ['spent_on'],
739 :f => ['spent_on'],
711 :op => {'spent_on' => '>='},
740 :op => {'spent_on' => '>='},
712 :v => {'spent_on' => ['2007-04-01']},
741 :v => {'spent_on' => ['2007-04-01']},
713 :c => ['spent_on', 'user']
742 :c => ['spent_on', 'user']
714 assert_response :success
743 assert_response :success
715
744
716 assert_select '#csv-export-options' do
745 assert_select '#csv-export-options' do
717 assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do
746 assert_select 'form[action=?][method=get]', '/projects/ecookbook/time_entries.csv' do
718 # filter
747 # filter
719 assert_select 'input[name=?][value=?]', 'f[]', 'spent_on'
748 assert_select 'input[name=?][value=?]', 'f[]', 'spent_on'
720 assert_select 'input[name=?][value=?]', 'op[spent_on]', '>='
749 assert_select 'input[name=?][value=?]', 'op[spent_on]', '>='
721 assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01'
750 assert_select 'input[name=?][value=?]', 'v[spent_on][]', '2007-04-01'
722 # columns
751 # columns
723 assert_select 'input[name=?][value=?]', 'c[]', 'spent_on'
752 assert_select 'input[name=?][value=?]', 'c[]', 'spent_on'
724 assert_select 'input[name=?][value=?]', 'c[]', 'user'
753 assert_select 'input[name=?][value=?]', 'c[]', 'user'
725 assert_select 'input[name=?]', 'c[]', 2
754 assert_select 'input[name=?]', 'c[]', 2
726 end
755 end
727 end
756 end
728 end
757 end
729
758
730 def test_index_cross_project_should_include_csv_export_dialog
759 def test_index_cross_project_should_include_csv_export_dialog
731 get :index
760 get :index
732 assert_response :success
761 assert_response :success
733
762
734 assert_select '#csv-export-options' do
763 assert_select '#csv-export-options' do
735 assert_select 'form[action=?][method=get]', '/time_entries.csv'
764 assert_select 'form[action=?][method=get]', '/time_entries.csv'
736 end
765 end
737 end
766 end
738
767
739 def test_index_at_issue_level_should_include_csv_export_dialog
768 def test_index_at_issue_level_should_include_csv_export_dialog
740 get :index, :issue_id => 3
769 get :index, :issue_id => 3
741 assert_response :success
770 assert_response :success
742
771
743 assert_select '#csv-export-options' do
772 assert_select '#csv-export-options' do
744 assert_select 'form[action=?][method=get]', '/issues/3/time_entries.csv'
773 assert_select 'form[action=?][method=get]', '/issues/3/time_entries.csv'
745 end
774 end
746 end
775 end
747
776
748 def test_index_csv_all_projects
777 def test_index_csv_all_projects
749 with_settings :date_format => '%m/%d/%Y' do
778 with_settings :date_format => '%m/%d/%Y' do
750 get :index, :format => 'csv'
779 get :index, :format => 'csv'
751 assert_response :success
780 assert_response :success
752 assert_equal 'text/csv; header=present', response.content_type
781 assert_equal 'text/csv; header=present', response.content_type
753 end
782 end
754 end
783 end
755
784
756 def test_index_csv
785 def test_index_csv
757 with_settings :date_format => '%m/%d/%Y' do
786 with_settings :date_format => '%m/%d/%Y' do
758 get :index, :project_id => 1, :format => 'csv'
787 get :index, :project_id => 1, :format => 'csv'
759 assert_response :success
788 assert_response :success
760 assert_equal 'text/csv; header=present', response.content_type
789 assert_equal 'text/csv; header=present', response.content_type
761 end
790 end
762 end
791 end
763
792
764 def test_index_csv_should_fill_issue_column_with_tracker_id_and_subject
793 def test_index_csv_should_fill_issue_column_with_tracker_id_and_subject
765 issue = Issue.find(1)
794 issue = Issue.find(1)
766 entry = TimeEntry.generate!(:issue => issue, :comments => "Issue column content test")
795 entry = TimeEntry.generate!(:issue => issue, :comments => "Issue column content test")
767
796
768 get :index, :format => 'csv'
797 get :index, :format => 'csv'
769 line = response.body.split("\n").detect {|l| l.include?(entry.comments)}
798 line = response.body.split("\n").detect {|l| l.include?(entry.comments)}
770 assert_not_nil line
799 assert_not_nil line
771 assert_include "#{issue.tracker} #1: #{issue.subject}", line
800 assert_include "#{issue.tracker} #1: #{issue.subject}", line
772 end
801 end
773 end
802 end
General Comments 0
You need to be logged in to leave comments. Login now