##// END OF EJS Templates
Ability to add non-member watchers on issue creation (#5159)....
Jean-Philippe Lang -
r9134:fae5250e5250
parent child
Show More
@@ -1,425 +1,426
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 IssuesController < ApplicationController
18 class IssuesController < ApplicationController
19 menu_item :new_issue, :only => [:new, :create]
19 menu_item :new_issue, :only => [:new, :create]
20 default_search_scope :issues
20 default_search_scope :issues
21
21
22 before_filter :find_issue, :only => [:show, :edit, :update]
22 before_filter :find_issue, :only => [:show, :edit, :update]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 before_filter :find_project, :only => [:new, :create]
24 before_filter :find_project, :only => [:new, :create]
25 before_filter :authorize, :except => [:index]
25 before_filter :authorize, :except => [:index]
26 before_filter :find_optional_project, :only => [:index]
26 before_filter :find_optional_project, :only => [:index]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
27 before_filter :check_for_default_issue_status, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
28 before_filter :build_new_issue_from_params, :only => [:new, :create]
29 accept_rss_auth :index, :show
29 accept_rss_auth :index, :show
30 accept_api_auth :index, :show, :create, :update, :destroy
30 accept_api_auth :index, :show, :create, :update, :destroy
31
31
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
33
33
34 helper :journals
34 helper :journals
35 helper :projects
35 helper :projects
36 include ProjectsHelper
36 include ProjectsHelper
37 helper :custom_fields
37 helper :custom_fields
38 include CustomFieldsHelper
38 include CustomFieldsHelper
39 helper :issue_relations
39 helper :issue_relations
40 include IssueRelationsHelper
40 include IssueRelationsHelper
41 helper :watchers
41 helper :watchers
42 include WatchersHelper
42 include WatchersHelper
43 helper :attachments
43 helper :attachments
44 include AttachmentsHelper
44 include AttachmentsHelper
45 helper :queries
45 helper :queries
46 include QueriesHelper
46 include QueriesHelper
47 helper :repositories
47 helper :repositories
48 include RepositoriesHelper
48 include RepositoriesHelper
49 helper :sort
49 helper :sort
50 include SortHelper
50 include SortHelper
51 include IssuesHelper
51 include IssuesHelper
52 helper :timelog
52 helper :timelog
53 helper :gantt
53 helper :gantt
54 include Redmine::Export::PDF
54 include Redmine::Export::PDF
55
55
56 def index
56 def index
57 retrieve_query
57 retrieve_query
58 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
58 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
59 sort_update(@query.sortable_columns)
59 sort_update(@query.sortable_columns)
60
60
61 if @query.valid?
61 if @query.valid?
62 case params[:format]
62 case params[:format]
63 when 'csv', 'pdf'
63 when 'csv', 'pdf'
64 @limit = Setting.issues_export_limit.to_i
64 @limit = Setting.issues_export_limit.to_i
65 when 'atom'
65 when 'atom'
66 @limit = Setting.feeds_limit.to_i
66 @limit = Setting.feeds_limit.to_i
67 when 'xml', 'json'
67 when 'xml', 'json'
68 @offset, @limit = api_offset_and_limit
68 @offset, @limit = api_offset_and_limit
69 else
69 else
70 @limit = per_page_option
70 @limit = per_page_option
71 end
71 end
72
72
73 @issue_count = @query.issue_count
73 @issue_count = @query.issue_count
74 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
74 @issue_pages = Paginator.new self, @issue_count, @limit, params['page']
75 @offset ||= @issue_pages.current.offset
75 @offset ||= @issue_pages.current.offset
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
76 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
77 :order => sort_clause,
77 :order => sort_clause,
78 :offset => @offset,
78 :offset => @offset,
79 :limit => @limit)
79 :limit => @limit)
80 @issue_count_by_group = @query.issue_count_by_group
80 @issue_count_by_group = @query.issue_count_by_group
81
81
82 respond_to do |format|
82 respond_to do |format|
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
83 format.html { render :template => 'issues/index', :layout => !request.xhr? }
84 format.api {
84 format.api {
85 Issue.load_relations(@issues) if include_in_api_response?('relations')
85 Issue.load_relations(@issues) if include_in_api_response?('relations')
86 }
86 }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
87 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
88 format.csv { send_data(issues_to_csv(@issues, @project, @query, params), :type => 'text/csv; header=present', :filename => 'export.csv') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
89 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'export.pdf') }
90 end
90 end
91 else
91 else
92 respond_to do |format|
92 respond_to do |format|
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
93 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
94 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
95 format.api { render_validation_errors(@query) }
95 format.api { render_validation_errors(@query) }
96 end
96 end
97 end
97 end
98 rescue ActiveRecord::RecordNotFound
98 rescue ActiveRecord::RecordNotFound
99 render_404
99 render_404
100 end
100 end
101
101
102 def show
102 def show
103 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
103 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
104 @journals.each_with_index {|j,i| j.indice = i+1}
104 @journals.each_with_index {|j,i| j.indice = i+1}
105 @journals.reverse! if User.current.wants_comments_in_reverse_order?
105 @journals.reverse! if User.current.wants_comments_in_reverse_order?
106
106
107 @changesets = @issue.changesets.visible.all
107 @changesets = @issue.changesets.visible.all
108 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
108 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
109
109
110 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
110 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
111 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
111 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
112 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
112 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
113 @priorities = IssuePriority.active
113 @priorities = IssuePriority.active
114 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
114 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
115 respond_to do |format|
115 respond_to do |format|
116 format.html {
116 format.html {
117 retrieve_previous_and_next_issue_ids
117 retrieve_previous_and_next_issue_ids
118 render :template => 'issues/show'
118 render :template => 'issues/show'
119 }
119 }
120 format.api
120 format.api
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
121 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
122 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
122 format.pdf { send_data(issue_to_pdf(@issue), :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf") }
123 end
123 end
124 end
124 end
125
125
126 # Add a new issue
126 # Add a new issue
127 # The new issue will be created from an existing one if copy_from parameter is given
127 # The new issue will be created from an existing one if copy_from parameter is given
128 def new
128 def new
129 respond_to do |format|
129 respond_to do |format|
130 format.html { render :action => 'new', :layout => !request.xhr? }
130 format.html { render :action => 'new', :layout => !request.xhr? }
131 format.js {
131 format.js {
132 render(:update) { |page|
132 render(:update) { |page|
133 if params[:project_change]
133 if params[:project_change]
134 page.replace_html 'all_attributes', :partial => 'form'
134 page.replace_html 'all_attributes', :partial => 'form'
135 else
135 else
136 page.replace_html 'attributes', :partial => 'attributes'
136 page.replace_html 'attributes', :partial => 'attributes'
137 end
137 end
138 m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
138 m = User.current.allowed_to?(:log_time, @issue.project) ? 'show' : 'hide'
139 page << "if ($('log_time')) {Element.#{m}('log_time');}"
139 page << "if ($('log_time')) {Element.#{m}('log_time');}"
140 }
140 }
141 }
141 }
142 end
142 end
143 end
143 end
144
144
145 def create
145 def create
146 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
146 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
147 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
147 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
148 if @issue.save
148 if @issue.save
149 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
149 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
150 respond_to do |format|
150 respond_to do |format|
151 format.html {
151 format.html {
152 render_attachment_warning_if_needed(@issue)
152 render_attachment_warning_if_needed(@issue)
153 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
153 flash[:notice] = l(:notice_issue_successful_create, :id => "<a href='#{issue_path(@issue)}'>##{@issue.id}</a>")
154 redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
154 redirect_to(params[:continue] ? { :action => 'new', :project_id => @issue.project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
155 { :action => 'show', :id => @issue })
155 { :action => 'show', :id => @issue })
156 }
156 }
157 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
157 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
158 end
158 end
159 return
159 return
160 else
160 else
161 respond_to do |format|
161 respond_to do |format|
162 format.html { render :action => 'new' }
162 format.html { render :action => 'new' }
163 format.api { render_validation_errors(@issue) }
163 format.api { render_validation_errors(@issue) }
164 end
164 end
165 end
165 end
166 end
166 end
167
167
168 def edit
168 def edit
169 return unless update_issue_from_params
169 return unless update_issue_from_params
170
170
171 respond_to do |format|
171 respond_to do |format|
172 format.html { }
172 format.html { }
173 format.xml { }
173 format.xml { }
174 end
174 end
175 end
175 end
176
176
177 def update
177 def update
178 return unless update_issue_from_params
178 return unless update_issue_from_params
179 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
179 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
180 saved = false
180 saved = false
181 begin
181 begin
182 saved = @issue.save_issue_with_child_records(params, @time_entry)
182 saved = @issue.save_issue_with_child_records(params, @time_entry)
183 rescue ActiveRecord::StaleObjectError
183 rescue ActiveRecord::StaleObjectError
184 @conflict = true
184 @conflict = true
185 if params[:last_journal_id]
185 if params[:last_journal_id]
186 if params[:last_journal_id].present?
186 if params[:last_journal_id].present?
187 last_journal_id = params[:last_journal_id].to_i
187 last_journal_id = params[:last_journal_id].to_i
188 @conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
188 @conflict_journals = @issue.journals.all(:conditions => ["#{Journal.table_name}.id > ?", last_journal_id])
189 else
189 else
190 @conflict_journals = @issue.journals.all
190 @conflict_journals = @issue.journals.all
191 end
191 end
192 end
192 end
193 end
193 end
194
194
195 if saved
195 if saved
196 render_attachment_warning_if_needed(@issue)
196 render_attachment_warning_if_needed(@issue)
197 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
197 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
198
198
199 respond_to do |format|
199 respond_to do |format|
200 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
200 format.html { redirect_back_or_default({:action => 'show', :id => @issue}) }
201 format.api { head :ok }
201 format.api { head :ok }
202 end
202 end
203 else
203 else
204 respond_to do |format|
204 respond_to do |format|
205 format.html { render :action => 'edit' }
205 format.html { render :action => 'edit' }
206 format.api { render_validation_errors(@issue) }
206 format.api { render_validation_errors(@issue) }
207 end
207 end
208 end
208 end
209 end
209 end
210
210
211 # Bulk edit/copy a set of issues
211 # Bulk edit/copy a set of issues
212 def bulk_edit
212 def bulk_edit
213 @issues.sort!
213 @issues.sort!
214 @copy = params[:copy].present?
214 @copy = params[:copy].present?
215 @notes = params[:notes]
215 @notes = params[:notes]
216
216
217 if User.current.allowed_to?(:move_issues, @projects)
217 if User.current.allowed_to?(:move_issues, @projects)
218 @allowed_projects = Issue.allowed_target_projects_on_move
218 @allowed_projects = Issue.allowed_target_projects_on_move
219 if params[:issue]
219 if params[:issue]
220 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
220 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
221 if @target_project
221 if @target_project
222 target_projects = [@target_project]
222 target_projects = [@target_project]
223 end
223 end
224 end
224 end
225 end
225 end
226 target_projects ||= @projects
226 target_projects ||= @projects
227
227
228 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
228 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
229 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
229 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
230 @assignables = target_projects.map(&:assignable_users).reduce(:&)
230 @assignables = target_projects.map(&:assignable_users).reduce(:&)
231 @trackers = target_projects.map(&:trackers).reduce(:&)
231 @trackers = target_projects.map(&:trackers).reduce(:&)
232 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
232 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
233 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
233 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
234
234
235 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
235 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
236 render :layout => false if request.xhr?
236 render :layout => false if request.xhr?
237 end
237 end
238
238
239 def bulk_update
239 def bulk_update
240 @issues.sort!
240 @issues.sort!
241 @copy = params[:copy].present?
241 @copy = params[:copy].present?
242 attributes = parse_params_for_bulk_issue_attributes(params)
242 attributes = parse_params_for_bulk_issue_attributes(params)
243
243
244 unsaved_issue_ids = []
244 unsaved_issue_ids = []
245 moved_issues = []
245 moved_issues = []
246 @issues.each do |issue|
246 @issues.each do |issue|
247 issue.reload
247 issue.reload
248 if @copy
248 if @copy
249 issue = issue.copy
249 issue = issue.copy
250 end
250 end
251 journal = issue.init_journal(User.current, params[:notes])
251 journal = issue.init_journal(User.current, params[:notes])
252 issue.safe_attributes = attributes
252 issue.safe_attributes = attributes
253 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
253 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
254 if issue.save
254 if issue.save
255 moved_issues << issue
255 moved_issues << issue
256 else
256 else
257 # Keep unsaved issue ids to display them in flash error
257 # Keep unsaved issue ids to display them in flash error
258 unsaved_issue_ids << issue.id
258 unsaved_issue_ids << issue.id
259 end
259 end
260 end
260 end
261 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
261 set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids)
262
262
263 if params[:follow]
263 if params[:follow]
264 if @issues.size == 1 && moved_issues.size == 1
264 if @issues.size == 1 && moved_issues.size == 1
265 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
265 redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first
266 elsif moved_issues.map(&:project).uniq.size == 1
266 elsif moved_issues.map(&:project).uniq.size == 1
267 redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
267 redirect_to :controller => 'issues', :action => 'index', :project_id => moved_issues.map(&:project).first
268 end
268 end
269 else
269 else
270 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
270 redirect_back_or_default({:controller => 'issues', :action => 'index', :project_id => @project})
271 end
271 end
272 end
272 end
273
273
274 def destroy
274 def destroy
275 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
275 @hours = TimeEntry.sum(:hours, :conditions => ['issue_id IN (?)', @issues]).to_f
276 if @hours > 0
276 if @hours > 0
277 case params[:todo]
277 case params[:todo]
278 when 'destroy'
278 when 'destroy'
279 # nothing to do
279 # nothing to do
280 when 'nullify'
280 when 'nullify'
281 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
281 TimeEntry.update_all('issue_id = NULL', ['issue_id IN (?)', @issues])
282 when 'reassign'
282 when 'reassign'
283 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
283 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
284 if reassign_to.nil?
284 if reassign_to.nil?
285 flash.now[:error] = l(:error_issue_not_found_in_project)
285 flash.now[:error] = l(:error_issue_not_found_in_project)
286 return
286 return
287 else
287 else
288 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
288 TimeEntry.update_all("issue_id = #{reassign_to.id}", ['issue_id IN (?)', @issues])
289 end
289 end
290 else
290 else
291 # display the destroy form if it's a user request
291 # display the destroy form if it's a user request
292 return unless api_request?
292 return unless api_request?
293 end
293 end
294 end
294 end
295 @issues.each do |issue|
295 @issues.each do |issue|
296 begin
296 begin
297 issue.reload.destroy
297 issue.reload.destroy
298 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
298 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
299 # nothing to do, issue was already deleted (eg. by a parent)
299 # nothing to do, issue was already deleted (eg. by a parent)
300 end
300 end
301 end
301 end
302 respond_to do |format|
302 respond_to do |format|
303 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
303 format.html { redirect_back_or_default(:action => 'index', :project_id => @project) }
304 format.api { head :ok }
304 format.api { head :ok }
305 end
305 end
306 end
306 end
307
307
308 private
308 private
309 def find_issue
309 def find_issue
310 # Issue.visible.find(...) can not be used to redirect user to the login form
310 # Issue.visible.find(...) can not be used to redirect user to the login form
311 # if the issue actually exists but requires authentication
311 # if the issue actually exists but requires authentication
312 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
312 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
313 unless @issue.visible?
313 unless @issue.visible?
314 deny_access
314 deny_access
315 return
315 return
316 end
316 end
317 @project = @issue.project
317 @project = @issue.project
318 rescue ActiveRecord::RecordNotFound
318 rescue ActiveRecord::RecordNotFound
319 render_404
319 render_404
320 end
320 end
321
321
322 def find_project
322 def find_project
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
323 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
324 @project = Project.find(project_id)
324 @project = Project.find(project_id)
325 rescue ActiveRecord::RecordNotFound
325 rescue ActiveRecord::RecordNotFound
326 render_404
326 render_404
327 end
327 end
328
328
329 def retrieve_previous_and_next_issue_ids
329 def retrieve_previous_and_next_issue_ids
330 retrieve_query_from_session
330 retrieve_query_from_session
331 if @query
331 if @query
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
332 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
333 sort_update(@query.sortable_columns, 'issues_index_sort')
333 sort_update(@query.sortable_columns, 'issues_index_sort')
334 limit = 500
334 limit = 500
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
335 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
336 if (idx = issue_ids.index(@issue.id)) && idx < limit
337 if issue_ids.size < 500
337 if issue_ids.size < 500
338 @issue_position = idx + 1
338 @issue_position = idx + 1
339 @issue_count = issue_ids.size
339 @issue_count = issue_ids.size
340 end
340 end
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
341 @prev_issue_id = issue_ids[idx - 1] if idx > 0
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
342 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
343 end
343 end
344 end
344 end
345 end
345 end
346
346
347 # Used by #edit and #update to set some common instance variables
347 # Used by #edit and #update to set some common instance variables
348 # from the params
348 # from the params
349 # TODO: Refactor, not everything in here is needed by #edit
349 # TODO: Refactor, not everything in here is needed by #edit
350 def update_issue_from_params
350 def update_issue_from_params
351 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
351 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
352 @priorities = IssuePriority.active
352 @priorities = IssuePriority.active
353 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
353 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
354 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
354 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
355 @time_entry.attributes = params[:time_entry]
355 @time_entry.attributes = params[:time_entry]
356
356
357 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
357 @notes = params[:notes] || (params[:issue].present? ? params[:issue][:notes] : nil)
358 @issue.init_journal(User.current, @notes)
358 @issue.init_journal(User.current, @notes)
359
359
360 issue_attributes = params[:issue]
360 issue_attributes = params[:issue]
361 if issue_attributes && params[:conflict_resolution]
361 if issue_attributes && params[:conflict_resolution]
362 case params[:conflict_resolution]
362 case params[:conflict_resolution]
363 when 'overwrite'
363 when 'overwrite'
364 issue_attributes = issue_attributes.dup
364 issue_attributes = issue_attributes.dup
365 issue_attributes.delete(:lock_version)
365 issue_attributes.delete(:lock_version)
366 when 'add_notes'
366 when 'add_notes'
367 issue_attributes = {}
367 issue_attributes = {}
368 when 'cancel'
368 when 'cancel'
369 redirect_to issue_path(@issue)
369 redirect_to issue_path(@issue)
370 return false
370 return false
371 end
371 end
372 end
372 end
373 @issue.safe_attributes = issue_attributes
373 @issue.safe_attributes = issue_attributes
374 true
374 true
375 end
375 end
376
376
377 # TODO: Refactor, lots of extra code in here
377 # TODO: Refactor, lots of extra code in here
378 # TODO: Changing tracker on an existing issue should not trigger this
378 # TODO: Changing tracker on an existing issue should not trigger this
379 def build_new_issue_from_params
379 def build_new_issue_from_params
380 if params[:id].blank?
380 if params[:id].blank?
381 @issue = Issue.new
381 @issue = Issue.new
382 if params[:copy_from]
382 if params[:copy_from]
383 begin
383 begin
384 @copy_from = Issue.visible.find(params[:copy_from])
384 @copy_from = Issue.visible.find(params[:copy_from])
385 @copy_attachments = params[:copy_attachments].present? || request.get?
385 @copy_attachments = params[:copy_attachments].present? || request.get?
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments)
386 @issue.copy_from(@copy_from, :attachments => @copy_attachments)
387 rescue ActiveRecord::RecordNotFound
387 rescue ActiveRecord::RecordNotFound
388 render_404
388 render_404
389 return
389 return
390 end
390 end
391 end
391 end
392 @issue.project = @project
392 @issue.project = @project
393 else
393 else
394 @issue = @project.issues.visible.find(params[:id])
394 @issue = @project.issues.visible.find(params[:id])
395 end
395 end
396
396
397 @issue.project = @project
397 @issue.project = @project
398 @issue.author = User.current
398 @issue.author = User.current
399 # Tracker must be set before custom field values
399 # Tracker must be set before custom field values
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
400 @issue.tracker ||= @project.trackers.find((params[:issue] && params[:issue][:tracker_id]) || params[:tracker_id] || :first)
401 if @issue.tracker.nil?
401 if @issue.tracker.nil?
402 render_error l(:error_no_tracker_in_project)
402 render_error l(:error_no_tracker_in_project)
403 return false
403 return false
404 end
404 end
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
405 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
406 @issue.safe_attributes = params[:issue]
406 @issue.safe_attributes = params[:issue]
407
407
408 @priorities = IssuePriority.active
408 @priorities = IssuePriority.active
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
409 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, true)
410 @available_watchers = (@issue.project.users.sort + @issue.watcher_users).uniq
410 end
411 end
411
412
412 def check_for_default_issue_status
413 def check_for_default_issue_status
413 if IssueStatus.default.nil?
414 if IssueStatus.default.nil?
414 render_error l(:error_no_default_issue_status)
415 render_error l(:error_no_default_issue_status)
415 return false
416 return false
416 end
417 end
417 end
418 end
418
419
419 def parse_params_for_bulk_issue_attributes(params)
420 def parse_params_for_bulk_issue_attributes(params)
420 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
421 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
421 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
422 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
422 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
423 attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
423 attributes
424 attributes
424 end
425 end
425 end
426 end
@@ -1,110 +1,134
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 WatchersController < ApplicationController
18 class WatchersController < ApplicationController
19 before_filter :find_project
19 before_filter :find_project
20 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
20 before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch]
21 before_filter :authorize, :only => [:new, :destroy]
21 before_filter :authorize, :only => [:new, :destroy]
22
22
23 def watch
23 def watch
24 if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
24 if @watched.respond_to?(:visible?) && !@watched.visible?(User.current)
25 render_403
25 render_403
26 else
26 else
27 set_watcher(User.current, true)
27 set_watcher(User.current, true)
28 end
28 end
29 end
29 end
30
30
31 def unwatch
31 def unwatch
32 set_watcher(User.current, false)
32 set_watcher(User.current, false)
33 end
33 end
34
34
35 def new
35 def new
36 respond_to do |format|
36 respond_to do |format|
37 format.js do
37 format.js do
38 render :update do |page|
38 render :update do |page|
39 page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
39 page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
40 page << "showModal('ajax-modal', '400px');"
40 page << "showModal('ajax-modal', '400px');"
41 page << "$('ajax-modal').addClassName('new-watcher');"
41 page << "$('ajax-modal').addClassName('new-watcher');"
42 end
42 end
43 end
43 end
44 end
44 end
45 end
45 end
46
46
47 def create
47 def create
48 if params[:watcher].is_a?(Hash) && request.post?
48 if params[:watcher].is_a?(Hash) && request.post?
49 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
49 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
50 user_ids.each do |user_id|
50 user_ids.each do |user_id|
51 Watcher.create(:watchable => @watched, :user_id => user_id)
51 Watcher.create(:watchable => @watched, :user_id => user_id)
52 end
52 end
53 end
53 end
54 respond_to do |format|
54 respond_to do |format|
55 format.html { redirect_to :back }
55 format.html { redirect_to :back }
56 format.js do
56 format.js do
57 render :update do |page|
57 render :update do |page|
58 page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
58 page.replace_html 'ajax-modal', :partial => 'watchers/new', :locals => {:watched => @watched}
59 page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
59 page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
60 end
60 end
61 end
61 end
62 end
62 end
63 rescue ::ActionController::RedirectBackError
63 rescue ::ActionController::RedirectBackError
64 render :text => 'Watcher added.', :layout => true
64 render :text => 'Watcher added.', :layout => true
65 end
65 end
66
66
67 def append
68 if params[:watcher].is_a?(Hash)
69 user_ids = params[:watcher][:user_ids] || [params[:watcher][:user_id]]
70 users = User.active.find_all_by_id(user_ids)
71 respond_to do |format|
72 format.js do
73 render :update do |page|
74 users.each do |user|
75 page.select("#issue_watcher_user_ids_#{user.id}").each(&:hide)
76 end
77 page.insert_html :bottom, 'watchers_inputs', :text => watchers_checkboxes(nil, users, true)
78 end
79 end
80 end
81 end
82 end
83
67 def destroy
84 def destroy
68 @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
85 @watched.set_watcher(User.find(params[:user_id]), false) if request.post?
69 respond_to do |format|
86 respond_to do |format|
70 format.html { redirect_to :back }
87 format.html { redirect_to :back }
71 format.js do
88 format.js do
72 render :update do |page|
89 render :update do |page|
73 page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
90 page.replace_html 'watchers', :partial => 'watchers/watchers', :locals => {:watched => @watched}
74 end
91 end
75 end
92 end
76 end
93 end
77 end
94 end
78
95
79 def autocomplete_for_user
96 def autocomplete_for_user
80 @users = User.active.like(params[:q]).find(:all, :limit => 100) - @watched.watcher_users
97 @users = User.active.like(params[:q]).find(:all, :limit => 100)
98 if @watched
99 @user -= @watched.watcher_users
100 end
81 render :layout => false
101 render :layout => false
82 end
102 end
83
103
84 private
104 private
85 def find_project
105 def find_project
106 if params[:object_type] && params[:object_id]
86 klass = Object.const_get(params[:object_type].camelcase)
107 klass = Object.const_get(params[:object_type].camelcase)
87 return false unless klass.respond_to?('watched_by')
108 return false unless klass.respond_to?('watched_by')
88 @watched = klass.find(params[:object_id])
109 @watched = klass.find(params[:object_id])
89 @project = @watched.project
110 @project = @watched.project
111 elsif params[:project_id]
112 @project = Project.visible.find(params[:project_id])
113 end
90 rescue
114 rescue
91 render_404
115 render_404
92 end
116 end
93
117
94 def set_watcher(user, watching)
118 def set_watcher(user, watching)
95 @watched.set_watcher(user, watching)
119 @watched.set_watcher(user, watching)
96 respond_to do |format|
120 respond_to do |format|
97 format.html { redirect_to :back }
121 format.html { redirect_to :back }
98 format.js do
122 format.js do
99 render(:update) do |page|
123 render(:update) do |page|
100 c = watcher_css(@watched)
124 c = watcher_css(@watched)
101 page.select(".#{c}").each do |item|
125 page.select(".#{c}").each do |item|
102 page.replace_html item, watcher_link(@watched, user)
126 page.replace_html item, watcher_link(@watched, user)
103 end
127 end
104 end
128 end
105 end
129 end
106 end
130 end
107 rescue ::ActionController::RedirectBackError
131 rescue ::ActionController::RedirectBackError
108 render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
132 render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true
109 end
133 end
110 end
134 end
@@ -1,66 +1,74
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 module WatchersHelper
20 module WatchersHelper
21
21
22 def watcher_tag(object, user, options={})
22 def watcher_tag(object, user, options={})
23 content_tag("span", watcher_link(object, user), :class => watcher_css(object))
23 content_tag("span", watcher_link(object, user), :class => watcher_css(object))
24 end
24 end
25
25
26 def watcher_link(object, user)
26 def watcher_link(object, user)
27 return '' unless user && user.logged? && object.respond_to?('watched_by?')
27 return '' unless user && user.logged? && object.respond_to?('watched_by?')
28 watched = object.watched_by?(user)
28 watched = object.watched_by?(user)
29 url = {:controller => 'watchers',
29 url = {:controller => 'watchers',
30 :action => (watched ? 'unwatch' : 'watch'),
30 :action => (watched ? 'unwatch' : 'watch'),
31 :object_type => object.class.to_s.underscore,
31 :object_type => object.class.to_s.underscore,
32 :object_id => object.id}
32 :object_id => object.id}
33 link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
33 link_to_remote((watched ? l(:button_unwatch) : l(:button_watch)),
34 {:url => url},
34 {:url => url},
35 :href => url_for(url),
35 :href => url_for(url),
36 :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
36 :class => (watched ? 'icon icon-fav' : 'icon icon-fav-off'))
37
37
38 end
38 end
39
39
40 # Returns the css class used to identify watch links for a given +object+
40 # Returns the css class used to identify watch links for a given +object+
41 def watcher_css(object)
41 def watcher_css(object)
42 "#{object.class.to_s.underscore}-#{object.id}-watcher"
42 "#{object.class.to_s.underscore}-#{object.id}-watcher"
43 end
43 end
44
44
45 # Returns a comma separated list of users watching the given object
45 # Returns a comma separated list of users watching the given object
46 def watchers_list(object)
46 def watchers_list(object)
47 remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
47 remove_allowed = User.current.allowed_to?("delete_#{object.class.name.underscore}_watchers".to_sym, object.project)
48 lis = object.watcher_users.collect do |user|
48 lis = object.watcher_users.collect do |user|
49 s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
49 s = avatar(user, :size => "16").to_s + link_to_user(user, :class => 'user').to_s
50 if remove_allowed
50 if remove_allowed
51 url = {:controller => 'watchers',
51 url = {:controller => 'watchers',
52 :action => 'destroy',
52 :action => 'destroy',
53 :object_type => object.class.to_s.underscore,
53 :object_type => object.class.to_s.underscore,
54 :object_id => object.id,
54 :object_id => object.id,
55 :user_id => user}
55 :user_id => user}
56 s += ' ' + link_to_remote(image_tag('delete.png'),
56 s += ' ' + link_to_remote(image_tag('delete.png'),
57 {:url => url},
57 {:url => url},
58 :href => url_for(url),
58 :href => url_for(url),
59 :style => "vertical-align: middle",
59 :style => "vertical-align: middle",
60 :class => "delete")
60 :class => "delete")
61 end
61 end
62 content_tag :li, s.html_safe
62 content_tag :li, s.html_safe
63 end
63 end
64 (lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>").html_safe
64 (lis.empty? ? "" : "<ul>#{ lis.join("\n") }</ul>").html_safe
65 end
65 end
66
67 def watchers_checkboxes(object, users, checked=nil)
68 users.map do |user|
69 c = checked.nil? ? object.watched_by?(user) : checked
70 tag = check_box_tag 'issue[watcher_user_ids][]', user.id, c, :id => nil
71 content_tag 'label', "#{tag} #{h(user)}", :id => "issue_watcher_user_ids_#{user.id}", :class => "floating"
72 end.join
73 end
66 end
74 end
@@ -1,19 +1,18
1 <% if defined?(container) && container && container.saved_attachments %>
1 <% if defined?(container) && container && container.saved_attachments %>
2 <% container.saved_attachments.each_with_index do |attachment, i| %>
2 <% container.saved_attachments.each_with_index do |attachment, i| %>
3 <span class="icon icon-attachment" style="display:block; line-height:1.5em;">
3 <span class="icon icon-attachment" style="display:block; line-height:1.5em;">
4 <%= h(attachment.filename) %> (<%= number_to_human_size(attachment.filesize) %>)
4 <%= h(attachment.filename) %> (<%= number_to_human_size(attachment.filesize) %>)
5 <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.id}.#{attachment.digest}" %>
5 <%= hidden_field_tag "attachments[p#{i}][token]", "#{attachment.id}.#{attachment.digest}" %>
6 </span>
6 </span>
7 <% end %>
7 <% end %>
8 <% end %>
8 <% end %>
9 <span id="attachments_fields">
9 <span id="attachments_fields">
10 <span>
10 <span>
11 <%= file_field_tag 'attachments[1][file]', :size => 30, :id => nil, :class => 'file',
11 <%= file_field_tag 'attachments[1][file]', :size => 30, :id => nil, :class => 'file',
12 :onchange => "checkFileSize(this, #{Setting.attachment_max_size.to_i.kilobytes}, '#{escape_javascript(l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)))}');" -%>
12 :onchange => "checkFileSize(this, #{Setting.attachment_max_size.to_i.kilobytes}, '#{escape_javascript(l(:error_attachment_too_big, :max_size => number_to_human_size(Setting.attachment_max_size.to_i.kilobytes)))}');" -%>
13 <%= text_field_tag 'attachments[1][description]', '', :id => nil, :class => 'description', :placeholder => l(:label_optional_description) %>
13 <%= text_field_tag 'attachments[1][description]', '', :id => nil, :class => 'description', :placeholder => l(:label_optional_description) %>
14 <%= link_to_function(image_tag('delete.png'), 'removeFileField(this)', :title => (l(:button_delete))) %>
14 <%= link_to_function(image_tag('delete.png'), 'removeFileField(this)', :title => (l(:button_delete))) %>
15 </span>
15 </span>
16 </span>
16 </span>
17 <small><%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;' %>
17 <span class="add_attachment"><%= link_to l(:label_add_another_file), '#', :onclick => 'addFileField(); return false;', :class => 'add_attachment' %>
18 (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)
18 (<%= l(:label_max_size) %>: <%= number_to_human_size(Setting.attachment_max_size.to_i.kilobytes) %>)</span>
19 </small>
@@ -1,50 +1,55
1 <h2><%=l(:label_issue_new)%></h2>
1 <h2><%=l(:label_issue_new)%></h2>
2
2
3 <%= call_hook(:view_issues_new_top, {:issue => @issue}) %>
3 <%= call_hook(:view_issues_new_top, {:issue => @issue}) %>
4
4
5 <% labelled_form_for @issue, :url => project_issues_path(@project),
5 <% labelled_form_for @issue, :url => project_issues_path(@project),
6 :html => {:id => 'issue-form', :multipart => true} do |f| %>
6 :html => {:id => 'issue-form', :multipart => true} do |f| %>
7 <%= error_messages_for 'issue' %>
7 <%= error_messages_for 'issue' %>
8 <%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %>
8 <%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %>
9 <div class="box tabular">
9 <div class="box tabular">
10 <div id="all_attributes">
10 <div id="all_attributes">
11 <%= render :partial => 'issues/form', :locals => {:f => f} %>
11 <%= render :partial => 'issues/form', :locals => {:f => f} %>
12 </div>
12 </div>
13
13
14 <% if @copy_from && @copy_from.attachments.any? %>
14 <% if @copy_from && @copy_from.attachments.any? %>
15 <p>
15 <p>
16 <label for="copy_attachments"><%= l(:label_copy_attachments) %></label>
16 <label for="copy_attachments"><%= l(:label_copy_attachments) %></label>
17 <%= check_box_tag 'copy_attachments', '1', @copy_attachments %>
17 <%= check_box_tag 'copy_attachments', '1', @copy_attachments %>
18 </p>
18 </p>
19 <% end %>
19 <% end %>
20
20
21 <p id="attachments_form"><%= label_tag('attachments[1][file]', l(:label_attachment_plural))%><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
21 <p id="attachments_form"><%= label_tag('attachments[1][file]', l(:label_attachment_plural))%><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
22
22
23 <% if @issue.safe_attribute? 'watcher_user_ids' -%>
23 <% if @issue.safe_attribute? 'watcher_user_ids' -%>
24 <p id="watchers_form"><label><%= l(:label_issue_watchers) %></label>
24 <p id="watchers_form"><label><%= l(:label_issue_watchers) %></label>
25 <% @issue.project.users.sort.each do |user| -%>
25 <span id="watchers_inputs">
26 <label class="floating"><%= check_box_tag 'issue[watcher_user_ids][]', user.id, @issue.watched_by?(user), :id => nil %> <%=h user %></label>
26 <%= watchers_checkboxes(@issue, @available_watchers) %>
27 <% end -%>
27 </span>
28 <span class="search_for_watchers">
29 <%= link_to_remote l(:label_search_for_watchers),
30 :url => {:controller => 'watchers', :action => 'new', :project_id => @issue.project},
31 :method => 'get' %>
32 </span>
28 </p>
33 </p>
29 <% end %>
34 <% end %>
30 </div>
35 </div>
31
36
32 <%= submit_tag l(:button_create) %>
37 <%= submit_tag l(:button_create) %>
33 <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
38 <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
34 <%= link_to_remote l(:label_preview),
39 <%= link_to_remote l(:label_preview),
35 { :url => preview_new_issue_path(:project_id => @project),
40 { :url => preview_new_issue_path(:project_id => @project),
36 :method => 'post',
41 :method => 'post',
37 :update => 'preview',
42 :update => 'preview',
38 :with => "Form.serialize('issue-form')",
43 :with => "Form.serialize('issue-form')",
39 :complete => "Element.scrollTo('preview')"
44 :complete => "Element.scrollTo('preview')"
40 }, :accesskey => accesskey(:preview) %>
45 }, :accesskey => accesskey(:preview) %>
41
46
42 <%= javascript_tag "Form.Element.focus('issue_subject');" %>
47 <%= javascript_tag "Form.Element.focus('issue_subject');" %>
43 <% end %>
48 <% end %>
44
49
45 <div id="preview" class="wiki"></div>
50 <div id="preview" class="wiki"></div>
46
51
47 <% content_for :header_tags do %>
52 <% content_for :header_tags do %>
48 <%= stylesheet_link_tag 'scm' %>
53 <%= stylesheet_link_tag 'scm' %>
49 <%= robot_exclusion_tag %>
54 <%= robot_exclusion_tag %>
50 <% end %>
55 <% end %>
@@ -1,32 +1,32
1 <h3 class="title"><%= l(:permission_add_issue_watchers) %></h3>
1 <h3 class="title"><%= l(:permission_add_issue_watchers) %></h3>
2
2
3 <% form_remote_tag :url => {:controller => 'watchers',
3 <% form_remote_tag :url => {:controller => 'watchers',
4 :action => 'create',
4 :action => (watched ? 'create' : 'append'),
5 :object_type => watched.class.name.underscore,
5 :object_type => watched.class.name.underscore,
6 :object_id => watched},
6 :object_id => watched},
7 :method => :post,
7 :method => :post,
8 :html => {:id => 'new-watcher-form'} do %>
8 :html => {:id => 'new-watcher-form'} do %>
9
9
10 <p><%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p>
10 <p><%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p>
11 <%= observe_field(:user_search,
11 <%= observe_field(:user_search,
12 :frequency => 0.5,
12 :frequency => 0.5,
13 :update => :users_for_watcher,
13 :update => :users_for_watcher,
14 :method => :get,
14 :method => :get,
15 :before => '$("user_search").addClassName("ajax-loading")',
15 :before => '$("user_search").addClassName("ajax-loading")',
16 :complete => '$("user_search").removeClassName("ajax-loading")',
16 :complete => '$("user_search").removeClassName("ajax-loading")',
17 :url => {
17 :url => {
18 :controller => 'watchers',
18 :controller => 'watchers',
19 :action => 'autocomplete_for_user',
19 :action => 'autocomplete_for_user',
20 :object_type => watched.class.name.underscore,
20 :object_type => watched.class.name.underscore,
21 :object_id => watched},
21 :object_id => watched},
22 :with => 'q') %>
22 :with => 'q') %>
23
23
24 <div id="users_for_watcher">
24 <div id="users_for_watcher">
25 <%= principals_check_box_tags 'watcher[user_ids][]', watched.addable_watcher_users %>
25 <%= principals_check_box_tags 'watcher[user_ids][]', (watched ? watched.addable_watcher_users : User.active.all(:limit => 100)) %>
26 </div>
26 </div>
27
27
28 <p class="buttons">
28 <p class="buttons">
29 <%= submit_tag l(:button_add), :name => nil, :onclick => "hideModal(this);" %>
29 <%= submit_tag l(:button_add), :name => nil, :onclick => "hideModal(this);" %>
30 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
30 <%= submit_tag l(:button_cancel), :name => nil, :onclick => "hideModal(this);", :type => 'button' %>
31 </p>
31 </p>
32 <% end %>
32 <% end %>
@@ -1,1024 +1,1025
1 en:
1 en:
2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
3 direction: ltr
3 direction: ltr
4 date:
4 date:
5 formats:
5 formats:
6 # Use the strftime parameters for formats.
6 # Use the strftime parameters for formats.
7 # When no format has been given, it uses default.
7 # When no format has been given, it uses default.
8 # You can provide other formats here if you like!
8 # You can provide other formats here if you like!
9 default: "%m/%d/%Y"
9 default: "%m/%d/%Y"
10 short: "%b %d"
10 short: "%b %d"
11 long: "%B %d, %Y"
11 long: "%B %d, %Y"
12
12
13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
15
15
16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
19 # Used in date_select and datime_select.
19 # Used in date_select and datime_select.
20 order:
20 order:
21 - :year
21 - :year
22 - :month
22 - :month
23 - :day
23 - :day
24
24
25 time:
25 time:
26 formats:
26 formats:
27 default: "%m/%d/%Y %I:%M %p"
27 default: "%m/%d/%Y %I:%M %p"
28 time: "%I:%M %p"
28 time: "%I:%M %p"
29 short: "%d %b %H:%M"
29 short: "%d %b %H:%M"
30 long: "%B %d, %Y %H:%M"
30 long: "%B %d, %Y %H:%M"
31 am: "am"
31 am: "am"
32 pm: "pm"
32 pm: "pm"
33
33
34 datetime:
34 datetime:
35 distance_in_words:
35 distance_in_words:
36 half_a_minute: "half a minute"
36 half_a_minute: "half a minute"
37 less_than_x_seconds:
37 less_than_x_seconds:
38 one: "less than 1 second"
38 one: "less than 1 second"
39 other: "less than %{count} seconds"
39 other: "less than %{count} seconds"
40 x_seconds:
40 x_seconds:
41 one: "1 second"
41 one: "1 second"
42 other: "%{count} seconds"
42 other: "%{count} seconds"
43 less_than_x_minutes:
43 less_than_x_minutes:
44 one: "less than a minute"
44 one: "less than a minute"
45 other: "less than %{count} minutes"
45 other: "less than %{count} minutes"
46 x_minutes:
46 x_minutes:
47 one: "1 minute"
47 one: "1 minute"
48 other: "%{count} minutes"
48 other: "%{count} minutes"
49 about_x_hours:
49 about_x_hours:
50 one: "about 1 hour"
50 one: "about 1 hour"
51 other: "about %{count} hours"
51 other: "about %{count} hours"
52 x_days:
52 x_days:
53 one: "1 day"
53 one: "1 day"
54 other: "%{count} days"
54 other: "%{count} days"
55 about_x_months:
55 about_x_months:
56 one: "about 1 month"
56 one: "about 1 month"
57 other: "about %{count} months"
57 other: "about %{count} months"
58 x_months:
58 x_months:
59 one: "1 month"
59 one: "1 month"
60 other: "%{count} months"
60 other: "%{count} months"
61 about_x_years:
61 about_x_years:
62 one: "about 1 year"
62 one: "about 1 year"
63 other: "about %{count} years"
63 other: "about %{count} years"
64 over_x_years:
64 over_x_years:
65 one: "over 1 year"
65 one: "over 1 year"
66 other: "over %{count} years"
66 other: "over %{count} years"
67 almost_x_years:
67 almost_x_years:
68 one: "almost 1 year"
68 one: "almost 1 year"
69 other: "almost %{count} years"
69 other: "almost %{count} years"
70
70
71 number:
71 number:
72 format:
72 format:
73 separator: "."
73 separator: "."
74 delimiter: ""
74 delimiter: ""
75 precision: 3
75 precision: 3
76
76
77 human:
77 human:
78 format:
78 format:
79 delimiter: ""
79 delimiter: ""
80 precision: 1
80 precision: 1
81 storage_units:
81 storage_units:
82 format: "%n %u"
82 format: "%n %u"
83 units:
83 units:
84 byte:
84 byte:
85 one: "Byte"
85 one: "Byte"
86 other: "Bytes"
86 other: "Bytes"
87 kb: "kB"
87 kb: "kB"
88 mb: "MB"
88 mb: "MB"
89 gb: "GB"
89 gb: "GB"
90 tb: "TB"
90 tb: "TB"
91
91
92 # Used in array.to_sentence.
92 # Used in array.to_sentence.
93 support:
93 support:
94 array:
94 array:
95 sentence_connector: "and"
95 sentence_connector: "and"
96 skip_last_comma: false
96 skip_last_comma: false
97
97
98 activerecord:
98 activerecord:
99 errors:
99 errors:
100 template:
100 template:
101 header:
101 header:
102 one: "1 error prohibited this %{model} from being saved"
102 one: "1 error prohibited this %{model} from being saved"
103 other: "%{count} errors prohibited this %{model} from being saved"
103 other: "%{count} errors prohibited this %{model} from being saved"
104 messages:
104 messages:
105 inclusion: "is not included in the list"
105 inclusion: "is not included in the list"
106 exclusion: "is reserved"
106 exclusion: "is reserved"
107 invalid: "is invalid"
107 invalid: "is invalid"
108 confirmation: "doesn't match confirmation"
108 confirmation: "doesn't match confirmation"
109 accepted: "must be accepted"
109 accepted: "must be accepted"
110 empty: "can't be empty"
110 empty: "can't be empty"
111 blank: "can't be blank"
111 blank: "can't be blank"
112 too_long: "is too long (maximum is %{count} characters)"
112 too_long: "is too long (maximum is %{count} characters)"
113 too_short: "is too short (minimum is %{count} characters)"
113 too_short: "is too short (minimum is %{count} characters)"
114 wrong_length: "is the wrong length (should be %{count} characters)"
114 wrong_length: "is the wrong length (should be %{count} characters)"
115 taken: "has already been taken"
115 taken: "has already been taken"
116 not_a_number: "is not a number"
116 not_a_number: "is not a number"
117 not_a_date: "is not a valid date"
117 not_a_date: "is not a valid date"
118 greater_than: "must be greater than %{count}"
118 greater_than: "must be greater than %{count}"
119 greater_than_or_equal_to: "must be greater than or equal to %{count}"
119 greater_than_or_equal_to: "must be greater than or equal to %{count}"
120 equal_to: "must be equal to %{count}"
120 equal_to: "must be equal to %{count}"
121 less_than: "must be less than %{count}"
121 less_than: "must be less than %{count}"
122 less_than_or_equal_to: "must be less than or equal to %{count}"
122 less_than_or_equal_to: "must be less than or equal to %{count}"
123 odd: "must be odd"
123 odd: "must be odd"
124 even: "must be even"
124 even: "must be even"
125 greater_than_start_date: "must be greater than start date"
125 greater_than_start_date: "must be greater than start date"
126 not_same_project: "doesn't belong to the same project"
126 not_same_project: "doesn't belong to the same project"
127 circular_dependency: "This relation would create a circular dependency"
127 circular_dependency: "This relation would create a circular dependency"
128 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
128 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
129
129
130 actionview_instancetag_blank_option: Please select
130 actionview_instancetag_blank_option: Please select
131
131
132 general_text_No: 'No'
132 general_text_No: 'No'
133 general_text_Yes: 'Yes'
133 general_text_Yes: 'Yes'
134 general_text_no: 'no'
134 general_text_no: 'no'
135 general_text_yes: 'yes'
135 general_text_yes: 'yes'
136 general_lang_name: 'English'
136 general_lang_name: 'English'
137 general_csv_separator: ','
137 general_csv_separator: ','
138 general_csv_decimal_separator: '.'
138 general_csv_decimal_separator: '.'
139 general_csv_encoding: ISO-8859-1
139 general_csv_encoding: ISO-8859-1
140 general_pdf_encoding: UTF-8
140 general_pdf_encoding: UTF-8
141 general_first_day_of_week: '7'
141 general_first_day_of_week: '7'
142
142
143 notice_account_updated: Account was successfully updated.
143 notice_account_updated: Account was successfully updated.
144 notice_account_invalid_creditentials: Invalid user or password
144 notice_account_invalid_creditentials: Invalid user or password
145 notice_account_password_updated: Password was successfully updated.
145 notice_account_password_updated: Password was successfully updated.
146 notice_account_wrong_password: Wrong password
146 notice_account_wrong_password: Wrong password
147 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
147 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
148 notice_account_unknown_email: Unknown user.
148 notice_account_unknown_email: Unknown user.
149 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
149 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
150 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
150 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
151 notice_account_activated: Your account has been activated. You can now log in.
151 notice_account_activated: Your account has been activated. You can now log in.
152 notice_successful_create: Successful creation.
152 notice_successful_create: Successful creation.
153 notice_successful_update: Successful update.
153 notice_successful_update: Successful update.
154 notice_successful_delete: Successful deletion.
154 notice_successful_delete: Successful deletion.
155 notice_successful_connection: Successful connection.
155 notice_successful_connection: Successful connection.
156 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
156 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
157 notice_locking_conflict: Data has been updated by another user.
157 notice_locking_conflict: Data has been updated by another user.
158 notice_not_authorized: You are not authorized to access this page.
158 notice_not_authorized: You are not authorized to access this page.
159 notice_not_authorized_archived_project: The project you're trying to access has been archived.
159 notice_not_authorized_archived_project: The project you're trying to access has been archived.
160 notice_email_sent: "An email was sent to %{value}"
160 notice_email_sent: "An email was sent to %{value}"
161 notice_email_error: "An error occurred while sending mail (%{value})"
161 notice_email_error: "An error occurred while sending mail (%{value})"
162 notice_feeds_access_key_reseted: Your RSS access key was reset.
162 notice_feeds_access_key_reseted: Your RSS access key was reset.
163 notice_api_access_key_reseted: Your API access key was reset.
163 notice_api_access_key_reseted: Your API access key was reset.
164 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
164 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
165 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
165 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
166 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
166 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
167 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
167 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
168 notice_account_pending: "Your account was created and is now pending administrator approval."
168 notice_account_pending: "Your account was created and is now pending administrator approval."
169 notice_default_data_loaded: Default configuration successfully loaded.
169 notice_default_data_loaded: Default configuration successfully loaded.
170 notice_unable_delete_version: Unable to delete version.
170 notice_unable_delete_version: Unable to delete version.
171 notice_unable_delete_time_entry: Unable to delete time log entry.
171 notice_unable_delete_time_entry: Unable to delete time log entry.
172 notice_issue_done_ratios_updated: Issue done ratios updated.
172 notice_issue_done_ratios_updated: Issue done ratios updated.
173 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
173 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
174 notice_issue_successful_create: "Issue %{id} created."
174 notice_issue_successful_create: "Issue %{id} created."
175 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
175 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
176
176
177 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
177 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
178 error_scm_not_found: "The entry or revision was not found in the repository."
178 error_scm_not_found: "The entry or revision was not found in the repository."
179 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
179 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
180 error_scm_annotate: "The entry does not exist or cannot be annotated."
180 error_scm_annotate: "The entry does not exist or cannot be annotated."
181 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
181 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
182 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
182 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
183 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
183 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
184 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
184 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
185 error_can_not_delete_custom_field: Unable to delete custom field
185 error_can_not_delete_custom_field: Unable to delete custom field
186 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
186 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
187 error_can_not_remove_role: "This role is in use and cannot be deleted."
187 error_can_not_remove_role: "This role is in use and cannot be deleted."
188 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
188 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
189 error_can_not_archive_project: This project cannot be archived
189 error_can_not_archive_project: This project cannot be archived
190 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
190 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
191 error_workflow_copy_source: 'Please select a source tracker or role'
191 error_workflow_copy_source: 'Please select a source tracker or role'
192 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
192 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
193 error_unable_delete_issue_status: 'Unable to delete issue status'
193 error_unable_delete_issue_status: 'Unable to delete issue status'
194 error_unable_to_connect: "Unable to connect (%{value})"
194 error_unable_to_connect: "Unable to connect (%{value})"
195 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
195 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
196 warning_attachments_not_saved: "%{count} file(s) could not be saved."
196 warning_attachments_not_saved: "%{count} file(s) could not be saved."
197
197
198 mail_subject_lost_password: "Your %{value} password"
198 mail_subject_lost_password: "Your %{value} password"
199 mail_body_lost_password: 'To change your password, click on the following link:'
199 mail_body_lost_password: 'To change your password, click on the following link:'
200 mail_subject_register: "Your %{value} account activation"
200 mail_subject_register: "Your %{value} account activation"
201 mail_body_register: 'To activate your account, click on the following link:'
201 mail_body_register: 'To activate your account, click on the following link:'
202 mail_body_account_information_external: "You can use your %{value} account to log in."
202 mail_body_account_information_external: "You can use your %{value} account to log in."
203 mail_body_account_information: Your account information
203 mail_body_account_information: Your account information
204 mail_subject_account_activation_request: "%{value} account activation request"
204 mail_subject_account_activation_request: "%{value} account activation request"
205 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
205 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
206 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
206 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
207 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
207 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
208 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
208 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
209 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
209 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
210 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
210 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
211 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
211 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
212
212
213 gui_validation_error: 1 error
213 gui_validation_error: 1 error
214 gui_validation_error_plural: "%{count} errors"
214 gui_validation_error_plural: "%{count} errors"
215
215
216 field_name: Name
216 field_name: Name
217 field_description: Description
217 field_description: Description
218 field_summary: Summary
218 field_summary: Summary
219 field_is_required: Required
219 field_is_required: Required
220 field_firstname: First name
220 field_firstname: First name
221 field_lastname: Last name
221 field_lastname: Last name
222 field_mail: Email
222 field_mail: Email
223 field_filename: File
223 field_filename: File
224 field_filesize: Size
224 field_filesize: Size
225 field_downloads: Downloads
225 field_downloads: Downloads
226 field_author: Author
226 field_author: Author
227 field_created_on: Created
227 field_created_on: Created
228 field_updated_on: Updated
228 field_updated_on: Updated
229 field_field_format: Format
229 field_field_format: Format
230 field_is_for_all: For all projects
230 field_is_for_all: For all projects
231 field_possible_values: Possible values
231 field_possible_values: Possible values
232 field_regexp: Regular expression
232 field_regexp: Regular expression
233 field_min_length: Minimum length
233 field_min_length: Minimum length
234 field_max_length: Maximum length
234 field_max_length: Maximum length
235 field_value: Value
235 field_value: Value
236 field_category: Category
236 field_category: Category
237 field_title: Title
237 field_title: Title
238 field_project: Project
238 field_project: Project
239 field_issue: Issue
239 field_issue: Issue
240 field_status: Status
240 field_status: Status
241 field_notes: Notes
241 field_notes: Notes
242 field_is_closed: Issue closed
242 field_is_closed: Issue closed
243 field_is_default: Default value
243 field_is_default: Default value
244 field_tracker: Tracker
244 field_tracker: Tracker
245 field_subject: Subject
245 field_subject: Subject
246 field_due_date: Due date
246 field_due_date: Due date
247 field_assigned_to: Assignee
247 field_assigned_to: Assignee
248 field_priority: Priority
248 field_priority: Priority
249 field_fixed_version: Target version
249 field_fixed_version: Target version
250 field_user: User
250 field_user: User
251 field_principal: Principal
251 field_principal: Principal
252 field_role: Role
252 field_role: Role
253 field_homepage: Homepage
253 field_homepage: Homepage
254 field_is_public: Public
254 field_is_public: Public
255 field_parent: Subproject of
255 field_parent: Subproject of
256 field_is_in_roadmap: Issues displayed in roadmap
256 field_is_in_roadmap: Issues displayed in roadmap
257 field_login: Login
257 field_login: Login
258 field_mail_notification: Email notifications
258 field_mail_notification: Email notifications
259 field_admin: Administrator
259 field_admin: Administrator
260 field_last_login_on: Last connection
260 field_last_login_on: Last connection
261 field_language: Language
261 field_language: Language
262 field_effective_date: Date
262 field_effective_date: Date
263 field_password: Password
263 field_password: Password
264 field_new_password: New password
264 field_new_password: New password
265 field_password_confirmation: Confirmation
265 field_password_confirmation: Confirmation
266 field_version: Version
266 field_version: Version
267 field_type: Type
267 field_type: Type
268 field_host: Host
268 field_host: Host
269 field_port: Port
269 field_port: Port
270 field_account: Account
270 field_account: Account
271 field_base_dn: Base DN
271 field_base_dn: Base DN
272 field_attr_login: Login attribute
272 field_attr_login: Login attribute
273 field_attr_firstname: Firstname attribute
273 field_attr_firstname: Firstname attribute
274 field_attr_lastname: Lastname attribute
274 field_attr_lastname: Lastname attribute
275 field_attr_mail: Email attribute
275 field_attr_mail: Email attribute
276 field_onthefly: On-the-fly user creation
276 field_onthefly: On-the-fly user creation
277 field_start_date: Start date
277 field_start_date: Start date
278 field_done_ratio: "% Done"
278 field_done_ratio: "% Done"
279 field_auth_source: Authentication mode
279 field_auth_source: Authentication mode
280 field_hide_mail: Hide my email address
280 field_hide_mail: Hide my email address
281 field_comments: Comment
281 field_comments: Comment
282 field_url: URL
282 field_url: URL
283 field_start_page: Start page
283 field_start_page: Start page
284 field_subproject: Subproject
284 field_subproject: Subproject
285 field_hours: Hours
285 field_hours: Hours
286 field_activity: Activity
286 field_activity: Activity
287 field_spent_on: Date
287 field_spent_on: Date
288 field_identifier: Identifier
288 field_identifier: Identifier
289 field_is_filter: Used as a filter
289 field_is_filter: Used as a filter
290 field_issue_to: Related issue
290 field_issue_to: Related issue
291 field_delay: Delay
291 field_delay: Delay
292 field_assignable: Issues can be assigned to this role
292 field_assignable: Issues can be assigned to this role
293 field_redirect_existing_links: Redirect existing links
293 field_redirect_existing_links: Redirect existing links
294 field_estimated_hours: Estimated time
294 field_estimated_hours: Estimated time
295 field_column_names: Columns
295 field_column_names: Columns
296 field_time_entries: Log time
296 field_time_entries: Log time
297 field_time_zone: Time zone
297 field_time_zone: Time zone
298 field_searchable: Searchable
298 field_searchable: Searchable
299 field_default_value: Default value
299 field_default_value: Default value
300 field_comments_sorting: Display comments
300 field_comments_sorting: Display comments
301 field_parent_title: Parent page
301 field_parent_title: Parent page
302 field_editable: Editable
302 field_editable: Editable
303 field_watcher: Watcher
303 field_watcher: Watcher
304 field_identity_url: OpenID URL
304 field_identity_url: OpenID URL
305 field_content: Content
305 field_content: Content
306 field_group_by: Group results by
306 field_group_by: Group results by
307 field_sharing: Sharing
307 field_sharing: Sharing
308 field_parent_issue: Parent task
308 field_parent_issue: Parent task
309 field_member_of_group: "Assignee's group"
309 field_member_of_group: "Assignee's group"
310 field_assigned_to_role: "Assignee's role"
310 field_assigned_to_role: "Assignee's role"
311 field_text: Text field
311 field_text: Text field
312 field_visible: Visible
312 field_visible: Visible
313 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
313 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
314 field_issues_visibility: Issues visibility
314 field_issues_visibility: Issues visibility
315 field_is_private: Private
315 field_is_private: Private
316 field_commit_logs_encoding: Commit messages encoding
316 field_commit_logs_encoding: Commit messages encoding
317 field_scm_path_encoding: Path encoding
317 field_scm_path_encoding: Path encoding
318 field_path_to_repository: Path to repository
318 field_path_to_repository: Path to repository
319 field_root_directory: Root directory
319 field_root_directory: Root directory
320 field_cvsroot: CVSROOT
320 field_cvsroot: CVSROOT
321 field_cvs_module: Module
321 field_cvs_module: Module
322 field_repository_is_default: Main repository
322 field_repository_is_default: Main repository
323 field_multiple: Multiple values
323 field_multiple: Multiple values
324 field_ldap_filter: LDAP filter
324 field_ldap_filter: LDAP filter
325
325
326 setting_app_title: Application title
326 setting_app_title: Application title
327 setting_app_subtitle: Application subtitle
327 setting_app_subtitle: Application subtitle
328 setting_welcome_text: Welcome text
328 setting_welcome_text: Welcome text
329 setting_default_language: Default language
329 setting_default_language: Default language
330 setting_login_required: Authentication required
330 setting_login_required: Authentication required
331 setting_self_registration: Self-registration
331 setting_self_registration: Self-registration
332 setting_attachment_max_size: Maximum attachment size
332 setting_attachment_max_size: Maximum attachment size
333 setting_issues_export_limit: Issues export limit
333 setting_issues_export_limit: Issues export limit
334 setting_mail_from: Emission email address
334 setting_mail_from: Emission email address
335 setting_bcc_recipients: Blind carbon copy recipients (bcc)
335 setting_bcc_recipients: Blind carbon copy recipients (bcc)
336 setting_plain_text_mail: Plain text mail (no HTML)
336 setting_plain_text_mail: Plain text mail (no HTML)
337 setting_host_name: Host name and path
337 setting_host_name: Host name and path
338 setting_text_formatting: Text formatting
338 setting_text_formatting: Text formatting
339 setting_wiki_compression: Wiki history compression
339 setting_wiki_compression: Wiki history compression
340 setting_feeds_limit: Maximum number of items in Atom feeds
340 setting_feeds_limit: Maximum number of items in Atom feeds
341 setting_default_projects_public: New projects are public by default
341 setting_default_projects_public: New projects are public by default
342 setting_autofetch_changesets: Fetch commits automatically
342 setting_autofetch_changesets: Fetch commits automatically
343 setting_sys_api_enabled: Enable WS for repository management
343 setting_sys_api_enabled: Enable WS for repository management
344 setting_commit_ref_keywords: Referencing keywords
344 setting_commit_ref_keywords: Referencing keywords
345 setting_commit_fix_keywords: Fixing keywords
345 setting_commit_fix_keywords: Fixing keywords
346 setting_autologin: Autologin
346 setting_autologin: Autologin
347 setting_date_format: Date format
347 setting_date_format: Date format
348 setting_time_format: Time format
348 setting_time_format: Time format
349 setting_cross_project_issue_relations: Allow cross-project issue relations
349 setting_cross_project_issue_relations: Allow cross-project issue relations
350 setting_issue_list_default_columns: Default columns displayed on the issue list
350 setting_issue_list_default_columns: Default columns displayed on the issue list
351 setting_repositories_encodings: Attachments and repositories encodings
351 setting_repositories_encodings: Attachments and repositories encodings
352 setting_emails_header: Emails header
352 setting_emails_header: Emails header
353 setting_emails_footer: Emails footer
353 setting_emails_footer: Emails footer
354 setting_protocol: Protocol
354 setting_protocol: Protocol
355 setting_per_page_options: Objects per page options
355 setting_per_page_options: Objects per page options
356 setting_user_format: Users display format
356 setting_user_format: Users display format
357 setting_activity_days_default: Days displayed on project activity
357 setting_activity_days_default: Days displayed on project activity
358 setting_display_subprojects_issues: Display subprojects issues on main projects by default
358 setting_display_subprojects_issues: Display subprojects issues on main projects by default
359 setting_enabled_scm: Enabled SCM
359 setting_enabled_scm: Enabled SCM
360 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
360 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
361 setting_mail_handler_api_enabled: Enable WS for incoming emails
361 setting_mail_handler_api_enabled: Enable WS for incoming emails
362 setting_mail_handler_api_key: API key
362 setting_mail_handler_api_key: API key
363 setting_sequential_project_identifiers: Generate sequential project identifiers
363 setting_sequential_project_identifiers: Generate sequential project identifiers
364 setting_gravatar_enabled: Use Gravatar user icons
364 setting_gravatar_enabled: Use Gravatar user icons
365 setting_gravatar_default: Default Gravatar image
365 setting_gravatar_default: Default Gravatar image
366 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
366 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
367 setting_file_max_size_displayed: Maximum size of text files displayed inline
367 setting_file_max_size_displayed: Maximum size of text files displayed inline
368 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
368 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
369 setting_openid: Allow OpenID login and registration
369 setting_openid: Allow OpenID login and registration
370 setting_password_min_length: Minimum password length
370 setting_password_min_length: Minimum password length
371 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
371 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
372 setting_default_projects_modules: Default enabled modules for new projects
372 setting_default_projects_modules: Default enabled modules for new projects
373 setting_issue_done_ratio: Calculate the issue done ratio with
373 setting_issue_done_ratio: Calculate the issue done ratio with
374 setting_issue_done_ratio_issue_field: Use the issue field
374 setting_issue_done_ratio_issue_field: Use the issue field
375 setting_issue_done_ratio_issue_status: Use the issue status
375 setting_issue_done_ratio_issue_status: Use the issue status
376 setting_start_of_week: Start calendars on
376 setting_start_of_week: Start calendars on
377 setting_rest_api_enabled: Enable REST web service
377 setting_rest_api_enabled: Enable REST web service
378 setting_cache_formatted_text: Cache formatted text
378 setting_cache_formatted_text: Cache formatted text
379 setting_default_notification_option: Default notification option
379 setting_default_notification_option: Default notification option
380 setting_commit_logtime_enabled: Enable time logging
380 setting_commit_logtime_enabled: Enable time logging
381 setting_commit_logtime_activity_id: Activity for logged time
381 setting_commit_logtime_activity_id: Activity for logged time
382 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
382 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
383 setting_issue_group_assignment: Allow issue assignment to groups
383 setting_issue_group_assignment: Allow issue assignment to groups
384 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
384 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
385 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
385 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
386
386
387 permission_add_project: Create project
387 permission_add_project: Create project
388 permission_add_subprojects: Create subprojects
388 permission_add_subprojects: Create subprojects
389 permission_edit_project: Edit project
389 permission_edit_project: Edit project
390 permission_select_project_modules: Select project modules
390 permission_select_project_modules: Select project modules
391 permission_manage_members: Manage members
391 permission_manage_members: Manage members
392 permission_manage_project_activities: Manage project activities
392 permission_manage_project_activities: Manage project activities
393 permission_manage_versions: Manage versions
393 permission_manage_versions: Manage versions
394 permission_manage_categories: Manage issue categories
394 permission_manage_categories: Manage issue categories
395 permission_view_issues: View Issues
395 permission_view_issues: View Issues
396 permission_add_issues: Add issues
396 permission_add_issues: Add issues
397 permission_edit_issues: Edit issues
397 permission_edit_issues: Edit issues
398 permission_manage_issue_relations: Manage issue relations
398 permission_manage_issue_relations: Manage issue relations
399 permission_set_issues_private: Set issues public or private
399 permission_set_issues_private: Set issues public or private
400 permission_set_own_issues_private: Set own issues public or private
400 permission_set_own_issues_private: Set own issues public or private
401 permission_add_issue_notes: Add notes
401 permission_add_issue_notes: Add notes
402 permission_edit_issue_notes: Edit notes
402 permission_edit_issue_notes: Edit notes
403 permission_edit_own_issue_notes: Edit own notes
403 permission_edit_own_issue_notes: Edit own notes
404 permission_move_issues: Move issues
404 permission_move_issues: Move issues
405 permission_delete_issues: Delete issues
405 permission_delete_issues: Delete issues
406 permission_manage_public_queries: Manage public queries
406 permission_manage_public_queries: Manage public queries
407 permission_save_queries: Save queries
407 permission_save_queries: Save queries
408 permission_view_gantt: View gantt chart
408 permission_view_gantt: View gantt chart
409 permission_view_calendar: View calendar
409 permission_view_calendar: View calendar
410 permission_view_issue_watchers: View watchers list
410 permission_view_issue_watchers: View watchers list
411 permission_add_issue_watchers: Add watchers
411 permission_add_issue_watchers: Add watchers
412 permission_delete_issue_watchers: Delete watchers
412 permission_delete_issue_watchers: Delete watchers
413 permission_log_time: Log spent time
413 permission_log_time: Log spent time
414 permission_view_time_entries: View spent time
414 permission_view_time_entries: View spent time
415 permission_edit_time_entries: Edit time logs
415 permission_edit_time_entries: Edit time logs
416 permission_edit_own_time_entries: Edit own time logs
416 permission_edit_own_time_entries: Edit own time logs
417 permission_manage_news: Manage news
417 permission_manage_news: Manage news
418 permission_comment_news: Comment news
418 permission_comment_news: Comment news
419 permission_manage_documents: Manage documents
419 permission_manage_documents: Manage documents
420 permission_view_documents: View documents
420 permission_view_documents: View documents
421 permission_manage_files: Manage files
421 permission_manage_files: Manage files
422 permission_view_files: View files
422 permission_view_files: View files
423 permission_manage_wiki: Manage wiki
423 permission_manage_wiki: Manage wiki
424 permission_rename_wiki_pages: Rename wiki pages
424 permission_rename_wiki_pages: Rename wiki pages
425 permission_delete_wiki_pages: Delete wiki pages
425 permission_delete_wiki_pages: Delete wiki pages
426 permission_view_wiki_pages: View wiki
426 permission_view_wiki_pages: View wiki
427 permission_view_wiki_edits: View wiki history
427 permission_view_wiki_edits: View wiki history
428 permission_edit_wiki_pages: Edit wiki pages
428 permission_edit_wiki_pages: Edit wiki pages
429 permission_delete_wiki_pages_attachments: Delete attachments
429 permission_delete_wiki_pages_attachments: Delete attachments
430 permission_protect_wiki_pages: Protect wiki pages
430 permission_protect_wiki_pages: Protect wiki pages
431 permission_manage_repository: Manage repository
431 permission_manage_repository: Manage repository
432 permission_browse_repository: Browse repository
432 permission_browse_repository: Browse repository
433 permission_view_changesets: View changesets
433 permission_view_changesets: View changesets
434 permission_commit_access: Commit access
434 permission_commit_access: Commit access
435 permission_manage_boards: Manage forums
435 permission_manage_boards: Manage forums
436 permission_view_messages: View messages
436 permission_view_messages: View messages
437 permission_add_messages: Post messages
437 permission_add_messages: Post messages
438 permission_edit_messages: Edit messages
438 permission_edit_messages: Edit messages
439 permission_edit_own_messages: Edit own messages
439 permission_edit_own_messages: Edit own messages
440 permission_delete_messages: Delete messages
440 permission_delete_messages: Delete messages
441 permission_delete_own_messages: Delete own messages
441 permission_delete_own_messages: Delete own messages
442 permission_export_wiki_pages: Export wiki pages
442 permission_export_wiki_pages: Export wiki pages
443 permission_manage_subtasks: Manage subtasks
443 permission_manage_subtasks: Manage subtasks
444 permission_manage_related_issues: Manage related issues
444 permission_manage_related_issues: Manage related issues
445
445
446 project_module_issue_tracking: Issue tracking
446 project_module_issue_tracking: Issue tracking
447 project_module_time_tracking: Time tracking
447 project_module_time_tracking: Time tracking
448 project_module_news: News
448 project_module_news: News
449 project_module_documents: Documents
449 project_module_documents: Documents
450 project_module_files: Files
450 project_module_files: Files
451 project_module_wiki: Wiki
451 project_module_wiki: Wiki
452 project_module_repository: Repository
452 project_module_repository: Repository
453 project_module_boards: Forums
453 project_module_boards: Forums
454 project_module_calendar: Calendar
454 project_module_calendar: Calendar
455 project_module_gantt: Gantt
455 project_module_gantt: Gantt
456
456
457 label_user: User
457 label_user: User
458 label_user_plural: Users
458 label_user_plural: Users
459 label_user_new: New user
459 label_user_new: New user
460 label_user_anonymous: Anonymous
460 label_user_anonymous: Anonymous
461 label_project: Project
461 label_project: Project
462 label_project_new: New project
462 label_project_new: New project
463 label_project_plural: Projects
463 label_project_plural: Projects
464 label_x_projects:
464 label_x_projects:
465 zero: no projects
465 zero: no projects
466 one: 1 project
466 one: 1 project
467 other: "%{count} projects"
467 other: "%{count} projects"
468 label_project_all: All Projects
468 label_project_all: All Projects
469 label_project_latest: Latest projects
469 label_project_latest: Latest projects
470 label_issue: Issue
470 label_issue: Issue
471 label_issue_new: New issue
471 label_issue_new: New issue
472 label_issue_plural: Issues
472 label_issue_plural: Issues
473 label_issue_view_all: View all issues
473 label_issue_view_all: View all issues
474 label_issues_by: "Issues by %{value}"
474 label_issues_by: "Issues by %{value}"
475 label_issue_added: Issue added
475 label_issue_added: Issue added
476 label_issue_updated: Issue updated
476 label_issue_updated: Issue updated
477 label_issue_note_added: Note added
477 label_issue_note_added: Note added
478 label_issue_status_updated: Status updated
478 label_issue_status_updated: Status updated
479 label_issue_priority_updated: Priority updated
479 label_issue_priority_updated: Priority updated
480 label_document: Document
480 label_document: Document
481 label_document_new: New document
481 label_document_new: New document
482 label_document_plural: Documents
482 label_document_plural: Documents
483 label_document_added: Document added
483 label_document_added: Document added
484 label_role: Role
484 label_role: Role
485 label_role_plural: Roles
485 label_role_plural: Roles
486 label_role_new: New role
486 label_role_new: New role
487 label_role_and_permissions: Roles and permissions
487 label_role_and_permissions: Roles and permissions
488 label_role_anonymous: Anonymous
488 label_role_anonymous: Anonymous
489 label_role_non_member: Non member
489 label_role_non_member: Non member
490 label_member: Member
490 label_member: Member
491 label_member_new: New member
491 label_member_new: New member
492 label_member_plural: Members
492 label_member_plural: Members
493 label_tracker: Tracker
493 label_tracker: Tracker
494 label_tracker_plural: Trackers
494 label_tracker_plural: Trackers
495 label_tracker_new: New tracker
495 label_tracker_new: New tracker
496 label_workflow: Workflow
496 label_workflow: Workflow
497 label_issue_status: Issue status
497 label_issue_status: Issue status
498 label_issue_status_plural: Issue statuses
498 label_issue_status_plural: Issue statuses
499 label_issue_status_new: New status
499 label_issue_status_new: New status
500 label_issue_category: Issue category
500 label_issue_category: Issue category
501 label_issue_category_plural: Issue categories
501 label_issue_category_plural: Issue categories
502 label_issue_category_new: New category
502 label_issue_category_new: New category
503 label_custom_field: Custom field
503 label_custom_field: Custom field
504 label_custom_field_plural: Custom fields
504 label_custom_field_plural: Custom fields
505 label_custom_field_new: New custom field
505 label_custom_field_new: New custom field
506 label_enumerations: Enumerations
506 label_enumerations: Enumerations
507 label_enumeration_new: New value
507 label_enumeration_new: New value
508 label_information: Information
508 label_information: Information
509 label_information_plural: Information
509 label_information_plural: Information
510 label_please_login: Please log in
510 label_please_login: Please log in
511 label_register: Register
511 label_register: Register
512 label_login_with_open_id_option: or login with OpenID
512 label_login_with_open_id_option: or login with OpenID
513 label_password_lost: Lost password
513 label_password_lost: Lost password
514 label_home: Home
514 label_home: Home
515 label_my_page: My page
515 label_my_page: My page
516 label_my_account: My account
516 label_my_account: My account
517 label_my_projects: My projects
517 label_my_projects: My projects
518 label_my_page_block: My page block
518 label_my_page_block: My page block
519 label_administration: Administration
519 label_administration: Administration
520 label_login: Sign in
520 label_login: Sign in
521 label_logout: Sign out
521 label_logout: Sign out
522 label_help: Help
522 label_help: Help
523 label_reported_issues: Reported issues
523 label_reported_issues: Reported issues
524 label_assigned_to_me_issues: Issues assigned to me
524 label_assigned_to_me_issues: Issues assigned to me
525 label_last_login: Last connection
525 label_last_login: Last connection
526 label_registered_on: Registered on
526 label_registered_on: Registered on
527 label_activity: Activity
527 label_activity: Activity
528 label_overall_activity: Overall activity
528 label_overall_activity: Overall activity
529 label_user_activity: "%{value}'s activity"
529 label_user_activity: "%{value}'s activity"
530 label_new: New
530 label_new: New
531 label_logged_as: Logged in as
531 label_logged_as: Logged in as
532 label_environment: Environment
532 label_environment: Environment
533 label_authentication: Authentication
533 label_authentication: Authentication
534 label_auth_source: Authentication mode
534 label_auth_source: Authentication mode
535 label_auth_source_new: New authentication mode
535 label_auth_source_new: New authentication mode
536 label_auth_source_plural: Authentication modes
536 label_auth_source_plural: Authentication modes
537 label_subproject_plural: Subprojects
537 label_subproject_plural: Subprojects
538 label_subproject_new: New subproject
538 label_subproject_new: New subproject
539 label_and_its_subprojects: "%{value} and its subprojects"
539 label_and_its_subprojects: "%{value} and its subprojects"
540 label_min_max_length: Min - Max length
540 label_min_max_length: Min - Max length
541 label_list: List
541 label_list: List
542 label_date: Date
542 label_date: Date
543 label_integer: Integer
543 label_integer: Integer
544 label_float: Float
544 label_float: Float
545 label_boolean: Boolean
545 label_boolean: Boolean
546 label_string: Text
546 label_string: Text
547 label_text: Long text
547 label_text: Long text
548 label_attribute: Attribute
548 label_attribute: Attribute
549 label_attribute_plural: Attributes
549 label_attribute_plural: Attributes
550 label_download: "%{count} Download"
550 label_download: "%{count} Download"
551 label_download_plural: "%{count} Downloads"
551 label_download_plural: "%{count} Downloads"
552 label_no_data: No data to display
552 label_no_data: No data to display
553 label_change_status: Change status
553 label_change_status: Change status
554 label_history: History
554 label_history: History
555 label_attachment: File
555 label_attachment: File
556 label_attachment_new: New file
556 label_attachment_new: New file
557 label_attachment_delete: Delete file
557 label_attachment_delete: Delete file
558 label_attachment_plural: Files
558 label_attachment_plural: Files
559 label_file_added: File added
559 label_file_added: File added
560 label_report: Report
560 label_report: Report
561 label_report_plural: Reports
561 label_report_plural: Reports
562 label_news: News
562 label_news: News
563 label_news_new: Add news
563 label_news_new: Add news
564 label_news_plural: News
564 label_news_plural: News
565 label_news_latest: Latest news
565 label_news_latest: Latest news
566 label_news_view_all: View all news
566 label_news_view_all: View all news
567 label_news_added: News added
567 label_news_added: News added
568 label_news_comment_added: Comment added to a news
568 label_news_comment_added: Comment added to a news
569 label_settings: Settings
569 label_settings: Settings
570 label_overview: Overview
570 label_overview: Overview
571 label_version: Version
571 label_version: Version
572 label_version_new: New version
572 label_version_new: New version
573 label_version_plural: Versions
573 label_version_plural: Versions
574 label_close_versions: Close completed versions
574 label_close_versions: Close completed versions
575 label_confirmation: Confirmation
575 label_confirmation: Confirmation
576 label_export_to: 'Also available in:'
576 label_export_to: 'Also available in:'
577 label_read: Read...
577 label_read: Read...
578 label_public_projects: Public projects
578 label_public_projects: Public projects
579 label_open_issues: open
579 label_open_issues: open
580 label_open_issues_plural: open
580 label_open_issues_plural: open
581 label_closed_issues: closed
581 label_closed_issues: closed
582 label_closed_issues_plural: closed
582 label_closed_issues_plural: closed
583 label_x_open_issues_abbr_on_total:
583 label_x_open_issues_abbr_on_total:
584 zero: 0 open / %{total}
584 zero: 0 open / %{total}
585 one: 1 open / %{total}
585 one: 1 open / %{total}
586 other: "%{count} open / %{total}"
586 other: "%{count} open / %{total}"
587 label_x_open_issues_abbr:
587 label_x_open_issues_abbr:
588 zero: 0 open
588 zero: 0 open
589 one: 1 open
589 one: 1 open
590 other: "%{count} open"
590 other: "%{count} open"
591 label_x_closed_issues_abbr:
591 label_x_closed_issues_abbr:
592 zero: 0 closed
592 zero: 0 closed
593 one: 1 closed
593 one: 1 closed
594 other: "%{count} closed"
594 other: "%{count} closed"
595 label_x_issues:
595 label_x_issues:
596 zero: 0 issues
596 zero: 0 issues
597 one: 1 issue
597 one: 1 issue
598 other: "%{count} issues"
598 other: "%{count} issues"
599 label_total: Total
599 label_total: Total
600 label_permissions: Permissions
600 label_permissions: Permissions
601 label_current_status: Current status
601 label_current_status: Current status
602 label_new_statuses_allowed: New statuses allowed
602 label_new_statuses_allowed: New statuses allowed
603 label_all: all
603 label_all: all
604 label_none: none
604 label_none: none
605 label_nobody: nobody
605 label_nobody: nobody
606 label_next: Next
606 label_next: Next
607 label_previous: Previous
607 label_previous: Previous
608 label_used_by: Used by
608 label_used_by: Used by
609 label_details: Details
609 label_details: Details
610 label_add_note: Add a note
610 label_add_note: Add a note
611 label_per_page: Per page
611 label_per_page: Per page
612 label_calendar: Calendar
612 label_calendar: Calendar
613 label_months_from: months from
613 label_months_from: months from
614 label_gantt: Gantt
614 label_gantt: Gantt
615 label_internal: Internal
615 label_internal: Internal
616 label_last_changes: "last %{count} changes"
616 label_last_changes: "last %{count} changes"
617 label_change_view_all: View all changes
617 label_change_view_all: View all changes
618 label_personalize_page: Personalize this page
618 label_personalize_page: Personalize this page
619 label_comment: Comment
619 label_comment: Comment
620 label_comment_plural: Comments
620 label_comment_plural: Comments
621 label_x_comments:
621 label_x_comments:
622 zero: no comments
622 zero: no comments
623 one: 1 comment
623 one: 1 comment
624 other: "%{count} comments"
624 other: "%{count} comments"
625 label_comment_add: Add a comment
625 label_comment_add: Add a comment
626 label_comment_added: Comment added
626 label_comment_added: Comment added
627 label_comment_delete: Delete comments
627 label_comment_delete: Delete comments
628 label_query: Custom query
628 label_query: Custom query
629 label_query_plural: Custom queries
629 label_query_plural: Custom queries
630 label_query_new: New query
630 label_query_new: New query
631 label_my_queries: My custom queries
631 label_my_queries: My custom queries
632 label_filter_add: Add filter
632 label_filter_add: Add filter
633 label_filter_plural: Filters
633 label_filter_plural: Filters
634 label_equals: is
634 label_equals: is
635 label_not_equals: is not
635 label_not_equals: is not
636 label_in_less_than: in less than
636 label_in_less_than: in less than
637 label_in_more_than: in more than
637 label_in_more_than: in more than
638 label_greater_or_equal: '>='
638 label_greater_or_equal: '>='
639 label_less_or_equal: '<='
639 label_less_or_equal: '<='
640 label_between: between
640 label_between: between
641 label_in: in
641 label_in: in
642 label_today: today
642 label_today: today
643 label_all_time: all time
643 label_all_time: all time
644 label_yesterday: yesterday
644 label_yesterday: yesterday
645 label_this_week: this week
645 label_this_week: this week
646 label_last_week: last week
646 label_last_week: last week
647 label_last_n_days: "last %{count} days"
647 label_last_n_days: "last %{count} days"
648 label_this_month: this month
648 label_this_month: this month
649 label_last_month: last month
649 label_last_month: last month
650 label_this_year: this year
650 label_this_year: this year
651 label_date_range: Date range
651 label_date_range: Date range
652 label_less_than_ago: less than days ago
652 label_less_than_ago: less than days ago
653 label_more_than_ago: more than days ago
653 label_more_than_ago: more than days ago
654 label_ago: days ago
654 label_ago: days ago
655 label_contains: contains
655 label_contains: contains
656 label_not_contains: doesn't contain
656 label_not_contains: doesn't contain
657 label_day_plural: days
657 label_day_plural: days
658 label_repository: Repository
658 label_repository: Repository
659 label_repository_new: New repository
659 label_repository_new: New repository
660 label_repository_plural: Repositories
660 label_repository_plural: Repositories
661 label_browse: Browse
661 label_browse: Browse
662 label_modification: "%{count} change"
662 label_modification: "%{count} change"
663 label_modification_plural: "%{count} changes"
663 label_modification_plural: "%{count} changes"
664 label_branch: Branch
664 label_branch: Branch
665 label_tag: Tag
665 label_tag: Tag
666 label_revision: Revision
666 label_revision: Revision
667 label_revision_plural: Revisions
667 label_revision_plural: Revisions
668 label_revision_id: "Revision %{value}"
668 label_revision_id: "Revision %{value}"
669 label_associated_revisions: Associated revisions
669 label_associated_revisions: Associated revisions
670 label_added: added
670 label_added: added
671 label_modified: modified
671 label_modified: modified
672 label_copied: copied
672 label_copied: copied
673 label_renamed: renamed
673 label_renamed: renamed
674 label_deleted: deleted
674 label_deleted: deleted
675 label_latest_revision: Latest revision
675 label_latest_revision: Latest revision
676 label_latest_revision_plural: Latest revisions
676 label_latest_revision_plural: Latest revisions
677 label_view_revisions: View revisions
677 label_view_revisions: View revisions
678 label_view_all_revisions: View all revisions
678 label_view_all_revisions: View all revisions
679 label_max_size: Maximum size
679 label_max_size: Maximum size
680 label_sort_highest: Move to top
680 label_sort_highest: Move to top
681 label_sort_higher: Move up
681 label_sort_higher: Move up
682 label_sort_lower: Move down
682 label_sort_lower: Move down
683 label_sort_lowest: Move to bottom
683 label_sort_lowest: Move to bottom
684 label_roadmap: Roadmap
684 label_roadmap: Roadmap
685 label_roadmap_due_in: "Due in %{value}"
685 label_roadmap_due_in: "Due in %{value}"
686 label_roadmap_overdue: "%{value} late"
686 label_roadmap_overdue: "%{value} late"
687 label_roadmap_no_issues: No issues for this version
687 label_roadmap_no_issues: No issues for this version
688 label_search: Search
688 label_search: Search
689 label_result_plural: Results
689 label_result_plural: Results
690 label_all_words: All words
690 label_all_words: All words
691 label_wiki: Wiki
691 label_wiki: Wiki
692 label_wiki_edit: Wiki edit
692 label_wiki_edit: Wiki edit
693 label_wiki_edit_plural: Wiki edits
693 label_wiki_edit_plural: Wiki edits
694 label_wiki_page: Wiki page
694 label_wiki_page: Wiki page
695 label_wiki_page_plural: Wiki pages
695 label_wiki_page_plural: Wiki pages
696 label_index_by_title: Index by title
696 label_index_by_title: Index by title
697 label_index_by_date: Index by date
697 label_index_by_date: Index by date
698 label_current_version: Current version
698 label_current_version: Current version
699 label_preview: Preview
699 label_preview: Preview
700 label_feed_plural: Feeds
700 label_feed_plural: Feeds
701 label_changes_details: Details of all changes
701 label_changes_details: Details of all changes
702 label_issue_tracking: Issue tracking
702 label_issue_tracking: Issue tracking
703 label_spent_time: Spent time
703 label_spent_time: Spent time
704 label_overall_spent_time: Overall spent time
704 label_overall_spent_time: Overall spent time
705 label_f_hour: "%{value} hour"
705 label_f_hour: "%{value} hour"
706 label_f_hour_plural: "%{value} hours"
706 label_f_hour_plural: "%{value} hours"
707 label_time_tracking: Time tracking
707 label_time_tracking: Time tracking
708 label_change_plural: Changes
708 label_change_plural: Changes
709 label_statistics: Statistics
709 label_statistics: Statistics
710 label_commits_per_month: Commits per month
710 label_commits_per_month: Commits per month
711 label_commits_per_author: Commits per author
711 label_commits_per_author: Commits per author
712 label_diff: diff
712 label_diff: diff
713 label_view_diff: View differences
713 label_view_diff: View differences
714 label_diff_inline: inline
714 label_diff_inline: inline
715 label_diff_side_by_side: side by side
715 label_diff_side_by_side: side by side
716 label_options: Options
716 label_options: Options
717 label_copy_workflow_from: Copy workflow from
717 label_copy_workflow_from: Copy workflow from
718 label_permissions_report: Permissions report
718 label_permissions_report: Permissions report
719 label_watched_issues: Watched issues
719 label_watched_issues: Watched issues
720 label_related_issues: Related issues
720 label_related_issues: Related issues
721 label_applied_status: Applied status
721 label_applied_status: Applied status
722 label_loading: Loading...
722 label_loading: Loading...
723 label_relation_new: New relation
723 label_relation_new: New relation
724 label_relation_delete: Delete relation
724 label_relation_delete: Delete relation
725 label_relates_to: related to
725 label_relates_to: related to
726 label_duplicates: duplicates
726 label_duplicates: duplicates
727 label_duplicated_by: duplicated by
727 label_duplicated_by: duplicated by
728 label_blocks: blocks
728 label_blocks: blocks
729 label_blocked_by: blocked by
729 label_blocked_by: blocked by
730 label_precedes: precedes
730 label_precedes: precedes
731 label_follows: follows
731 label_follows: follows
732 label_end_to_start: end to start
732 label_end_to_start: end to start
733 label_end_to_end: end to end
733 label_end_to_end: end to end
734 label_start_to_start: start to start
734 label_start_to_start: start to start
735 label_start_to_end: start to end
735 label_start_to_end: start to end
736 label_stay_logged_in: Stay logged in
736 label_stay_logged_in: Stay logged in
737 label_disabled: disabled
737 label_disabled: disabled
738 label_show_completed_versions: Show completed versions
738 label_show_completed_versions: Show completed versions
739 label_me: me
739 label_me: me
740 label_board: Forum
740 label_board: Forum
741 label_board_new: New forum
741 label_board_new: New forum
742 label_board_plural: Forums
742 label_board_plural: Forums
743 label_board_locked: Locked
743 label_board_locked: Locked
744 label_board_sticky: Sticky
744 label_board_sticky: Sticky
745 label_topic_plural: Topics
745 label_topic_plural: Topics
746 label_message_plural: Messages
746 label_message_plural: Messages
747 label_message_last: Last message
747 label_message_last: Last message
748 label_message_new: New message
748 label_message_new: New message
749 label_message_posted: Message added
749 label_message_posted: Message added
750 label_reply_plural: Replies
750 label_reply_plural: Replies
751 label_send_information: Send account information to the user
751 label_send_information: Send account information to the user
752 label_year: Year
752 label_year: Year
753 label_month: Month
753 label_month: Month
754 label_week: Week
754 label_week: Week
755 label_date_from: From
755 label_date_from: From
756 label_date_to: To
756 label_date_to: To
757 label_language_based: Based on user's language
757 label_language_based: Based on user's language
758 label_sort_by: "Sort by %{value}"
758 label_sort_by: "Sort by %{value}"
759 label_send_test_email: Send a test email
759 label_send_test_email: Send a test email
760 label_feeds_access_key: RSS access key
760 label_feeds_access_key: RSS access key
761 label_missing_feeds_access_key: Missing a RSS access key
761 label_missing_feeds_access_key: Missing a RSS access key
762 label_feeds_access_key_created_on: "RSS access key created %{value} ago"
762 label_feeds_access_key_created_on: "RSS access key created %{value} ago"
763 label_module_plural: Modules
763 label_module_plural: Modules
764 label_added_time_by: "Added by %{author} %{age} ago"
764 label_added_time_by: "Added by %{author} %{age} ago"
765 label_updated_time_by: "Updated by %{author} %{age} ago"
765 label_updated_time_by: "Updated by %{author} %{age} ago"
766 label_updated_time: "Updated %{value} ago"
766 label_updated_time: "Updated %{value} ago"
767 label_jump_to_a_project: Jump to a project...
767 label_jump_to_a_project: Jump to a project...
768 label_file_plural: Files
768 label_file_plural: Files
769 label_changeset_plural: Changesets
769 label_changeset_plural: Changesets
770 label_default_columns: Default columns
770 label_default_columns: Default columns
771 label_no_change_option: (No change)
771 label_no_change_option: (No change)
772 label_bulk_edit_selected_issues: Bulk edit selected issues
772 label_bulk_edit_selected_issues: Bulk edit selected issues
773 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
773 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
774 label_theme: Theme
774 label_theme: Theme
775 label_default: Default
775 label_default: Default
776 label_search_titles_only: Search titles only
776 label_search_titles_only: Search titles only
777 label_user_mail_option_all: "For any event on all my projects"
777 label_user_mail_option_all: "For any event on all my projects"
778 label_user_mail_option_selected: "For any event on the selected projects only..."
778 label_user_mail_option_selected: "For any event on the selected projects only..."
779 label_user_mail_option_none: "No events"
779 label_user_mail_option_none: "No events"
780 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
780 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
781 label_user_mail_option_only_assigned: "Only for things I am assigned to"
781 label_user_mail_option_only_assigned: "Only for things I am assigned to"
782 label_user_mail_option_only_owner: "Only for things I am the owner of"
782 label_user_mail_option_only_owner: "Only for things I am the owner of"
783 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
783 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
784 label_registration_activation_by_email: account activation by email
784 label_registration_activation_by_email: account activation by email
785 label_registration_manual_activation: manual account activation
785 label_registration_manual_activation: manual account activation
786 label_registration_automatic_activation: automatic account activation
786 label_registration_automatic_activation: automatic account activation
787 label_display_per_page: "Per page: %{value}"
787 label_display_per_page: "Per page: %{value}"
788 label_age: Age
788 label_age: Age
789 label_change_properties: Change properties
789 label_change_properties: Change properties
790 label_general: General
790 label_general: General
791 label_more: More
791 label_more: More
792 label_scm: SCM
792 label_scm: SCM
793 label_plugins: Plugins
793 label_plugins: Plugins
794 label_ldap_authentication: LDAP authentication
794 label_ldap_authentication: LDAP authentication
795 label_downloads_abbr: D/L
795 label_downloads_abbr: D/L
796 label_optional_description: Optional description
796 label_optional_description: Optional description
797 label_add_another_file: Add another file
797 label_add_another_file: Add another file
798 label_preferences: Preferences
798 label_preferences: Preferences
799 label_chronological_order: In chronological order
799 label_chronological_order: In chronological order
800 label_reverse_chronological_order: In reverse chronological order
800 label_reverse_chronological_order: In reverse chronological order
801 label_planning: Planning
801 label_planning: Planning
802 label_incoming_emails: Incoming emails
802 label_incoming_emails: Incoming emails
803 label_generate_key: Generate a key
803 label_generate_key: Generate a key
804 label_issue_watchers: Watchers
804 label_issue_watchers: Watchers
805 label_example: Example
805 label_example: Example
806 label_display: Display
806 label_display: Display
807 label_sort: Sort
807 label_sort: Sort
808 label_ascending: Ascending
808 label_ascending: Ascending
809 label_descending: Descending
809 label_descending: Descending
810 label_date_from_to: From %{start} to %{end}
810 label_date_from_to: From %{start} to %{end}
811 label_wiki_content_added: Wiki page added
811 label_wiki_content_added: Wiki page added
812 label_wiki_content_updated: Wiki page updated
812 label_wiki_content_updated: Wiki page updated
813 label_group: Group
813 label_group: Group
814 label_group_plural: Groups
814 label_group_plural: Groups
815 label_group_new: New group
815 label_group_new: New group
816 label_time_entry_plural: Spent time
816 label_time_entry_plural: Spent time
817 label_version_sharing_none: Not shared
817 label_version_sharing_none: Not shared
818 label_version_sharing_descendants: With subprojects
818 label_version_sharing_descendants: With subprojects
819 label_version_sharing_hierarchy: With project hierarchy
819 label_version_sharing_hierarchy: With project hierarchy
820 label_version_sharing_tree: With project tree
820 label_version_sharing_tree: With project tree
821 label_version_sharing_system: With all projects
821 label_version_sharing_system: With all projects
822 label_update_issue_done_ratios: Update issue done ratios
822 label_update_issue_done_ratios: Update issue done ratios
823 label_copy_source: Source
823 label_copy_source: Source
824 label_copy_target: Target
824 label_copy_target: Target
825 label_copy_same_as_target: Same as target
825 label_copy_same_as_target: Same as target
826 label_display_used_statuses_only: Only display statuses that are used by this tracker
826 label_display_used_statuses_only: Only display statuses that are used by this tracker
827 label_api_access_key: API access key
827 label_api_access_key: API access key
828 label_missing_api_access_key: Missing an API access key
828 label_missing_api_access_key: Missing an API access key
829 label_api_access_key_created_on: "API access key created %{value} ago"
829 label_api_access_key_created_on: "API access key created %{value} ago"
830 label_profile: Profile
830 label_profile: Profile
831 label_subtask_plural: Subtasks
831 label_subtask_plural: Subtasks
832 label_project_copy_notifications: Send email notifications during the project copy
832 label_project_copy_notifications: Send email notifications during the project copy
833 label_principal_search: "Search for user or group:"
833 label_principal_search: "Search for user or group:"
834 label_user_search: "Search for user:"
834 label_user_search: "Search for user:"
835 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
835 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
836 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
836 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
837 label_issues_visibility_all: All issues
837 label_issues_visibility_all: All issues
838 label_issues_visibility_public: All non private issues
838 label_issues_visibility_public: All non private issues
839 label_issues_visibility_own: Issues created by or assigned to the user
839 label_issues_visibility_own: Issues created by or assigned to the user
840 label_git_report_last_commit: Report last commit for files and directories
840 label_git_report_last_commit: Report last commit for files and directories
841 label_parent_revision: Parent
841 label_parent_revision: Parent
842 label_child_revision: Child
842 label_child_revision: Child
843 label_export_options: "%{export_format} export options"
843 label_export_options: "%{export_format} export options"
844 label_copy_attachments: Copy attachments
844 label_copy_attachments: Copy attachments
845 label_item_position: "%{position} of %{count}"
845 label_item_position: "%{position} of %{count}"
846 label_completed_versions: Completed versions
846 label_completed_versions: Completed versions
847 label_search_for_watchers: Search for watchers to add
847
848
848 button_login: Login
849 button_login: Login
849 button_submit: Submit
850 button_submit: Submit
850 button_save: Save
851 button_save: Save
851 button_check_all: Check all
852 button_check_all: Check all
852 button_uncheck_all: Uncheck all
853 button_uncheck_all: Uncheck all
853 button_collapse_all: Collapse all
854 button_collapse_all: Collapse all
854 button_expand_all: Expand all
855 button_expand_all: Expand all
855 button_delete: Delete
856 button_delete: Delete
856 button_create: Create
857 button_create: Create
857 button_create_and_continue: Create and continue
858 button_create_and_continue: Create and continue
858 button_test: Test
859 button_test: Test
859 button_edit: Edit
860 button_edit: Edit
860 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
861 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
861 button_add: Add
862 button_add: Add
862 button_change: Change
863 button_change: Change
863 button_apply: Apply
864 button_apply: Apply
864 button_clear: Clear
865 button_clear: Clear
865 button_lock: Lock
866 button_lock: Lock
866 button_unlock: Unlock
867 button_unlock: Unlock
867 button_download: Download
868 button_download: Download
868 button_list: List
869 button_list: List
869 button_view: View
870 button_view: View
870 button_move: Move
871 button_move: Move
871 button_move_and_follow: Move and follow
872 button_move_and_follow: Move and follow
872 button_back: Back
873 button_back: Back
873 button_cancel: Cancel
874 button_cancel: Cancel
874 button_activate: Activate
875 button_activate: Activate
875 button_sort: Sort
876 button_sort: Sort
876 button_log_time: Log time
877 button_log_time: Log time
877 button_rollback: Rollback to this version
878 button_rollback: Rollback to this version
878 button_watch: Watch
879 button_watch: Watch
879 button_unwatch: Unwatch
880 button_unwatch: Unwatch
880 button_reply: Reply
881 button_reply: Reply
881 button_archive: Archive
882 button_archive: Archive
882 button_unarchive: Unarchive
883 button_unarchive: Unarchive
883 button_reset: Reset
884 button_reset: Reset
884 button_rename: Rename
885 button_rename: Rename
885 button_change_password: Change password
886 button_change_password: Change password
886 button_copy: Copy
887 button_copy: Copy
887 button_copy_and_follow: Copy and follow
888 button_copy_and_follow: Copy and follow
888 button_annotate: Annotate
889 button_annotate: Annotate
889 button_update: Update
890 button_update: Update
890 button_configure: Configure
891 button_configure: Configure
891 button_quote: Quote
892 button_quote: Quote
892 button_duplicate: Duplicate
893 button_duplicate: Duplicate
893 button_show: Show
894 button_show: Show
894 button_edit_section: Edit this section
895 button_edit_section: Edit this section
895 button_export: Export
896 button_export: Export
896
897
897 status_active: active
898 status_active: active
898 status_registered: registered
899 status_registered: registered
899 status_locked: locked
900 status_locked: locked
900
901
901 version_status_open: open
902 version_status_open: open
902 version_status_locked: locked
903 version_status_locked: locked
903 version_status_closed: closed
904 version_status_closed: closed
904
905
905 field_active: Active
906 field_active: Active
906
907
907 text_select_mail_notifications: Select actions for which email notifications should be sent.
908 text_select_mail_notifications: Select actions for which email notifications should be sent.
908 text_regexp_info: eg. ^[A-Z0-9]+$
909 text_regexp_info: eg. ^[A-Z0-9]+$
909 text_min_max_length_info: 0 means no restriction
910 text_min_max_length_info: 0 means no restriction
910 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
911 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
911 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
912 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
912 text_workflow_edit: Select a role and a tracker to edit the workflow
913 text_workflow_edit: Select a role and a tracker to edit the workflow
913 text_are_you_sure: Are you sure?
914 text_are_you_sure: Are you sure?
914 text_are_you_sure_with_children: "Delete issue and all child issues?"
915 text_are_you_sure_with_children: "Delete issue and all child issues?"
915 text_journal_changed: "%{label} changed from %{old} to %{new}"
916 text_journal_changed: "%{label} changed from %{old} to %{new}"
916 text_journal_changed_no_detail: "%{label} updated"
917 text_journal_changed_no_detail: "%{label} updated"
917 text_journal_set_to: "%{label} set to %{value}"
918 text_journal_set_to: "%{label} set to %{value}"
918 text_journal_deleted: "%{label} deleted (%{old})"
919 text_journal_deleted: "%{label} deleted (%{old})"
919 text_journal_added: "%{label} %{value} added"
920 text_journal_added: "%{label} %{value} added"
920 text_tip_issue_begin_day: issue beginning this day
921 text_tip_issue_begin_day: issue beginning this day
921 text_tip_issue_end_day: issue ending this day
922 text_tip_issue_end_day: issue ending this day
922 text_tip_issue_begin_end_day: issue beginning and ending this day
923 text_tip_issue_begin_end_day: issue beginning and ending this day
923 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
924 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
924 text_caracters_maximum: "%{count} characters maximum."
925 text_caracters_maximum: "%{count} characters maximum."
925 text_caracters_minimum: "Must be at least %{count} characters long."
926 text_caracters_minimum: "Must be at least %{count} characters long."
926 text_length_between: "Length between %{min} and %{max} characters."
927 text_length_between: "Length between %{min} and %{max} characters."
927 text_tracker_no_workflow: No workflow defined for this tracker
928 text_tracker_no_workflow: No workflow defined for this tracker
928 text_unallowed_characters: Unallowed characters
929 text_unallowed_characters: Unallowed characters
929 text_comma_separated: Multiple values allowed (comma separated).
930 text_comma_separated: Multiple values allowed (comma separated).
930 text_line_separated: Multiple values allowed (one line for each value).
931 text_line_separated: Multiple values allowed (one line for each value).
931 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
932 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
932 text_issue_added: "Issue %{id} has been reported by %{author}."
933 text_issue_added: "Issue %{id} has been reported by %{author}."
933 text_issue_updated: "Issue %{id} has been updated by %{author}."
934 text_issue_updated: "Issue %{id} has been updated by %{author}."
934 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
935 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
935 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
936 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
936 text_issue_category_destroy_assignments: Remove category assignments
937 text_issue_category_destroy_assignments: Remove category assignments
937 text_issue_category_reassign_to: Reassign issues to this category
938 text_issue_category_reassign_to: Reassign issues to this category
938 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
939 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
939 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
940 text_no_configuration_data: "Roles, trackers, issue statuses and workflow have not been configured yet.\nIt is highly recommended to load the default configuration. You will be able to modify it once loaded."
940 text_load_default_configuration: Load the default configuration
941 text_load_default_configuration: Load the default configuration
941 text_status_changed_by_changeset: "Applied in changeset %{value}."
942 text_status_changed_by_changeset: "Applied in changeset %{value}."
942 text_time_logged_by_changeset: "Applied in changeset %{value}."
943 text_time_logged_by_changeset: "Applied in changeset %{value}."
943 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
944 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
944 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
945 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
945 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
946 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
946 text_select_project_modules: 'Select modules to enable for this project:'
947 text_select_project_modules: 'Select modules to enable for this project:'
947 text_default_administrator_account_changed: Default administrator account changed
948 text_default_administrator_account_changed: Default administrator account changed
948 text_file_repository_writable: Attachments directory writable
949 text_file_repository_writable: Attachments directory writable
949 text_plugin_assets_writable: Plugin assets directory writable
950 text_plugin_assets_writable: Plugin assets directory writable
950 text_rmagick_available: RMagick available (optional)
951 text_rmagick_available: RMagick available (optional)
951 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
952 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
952 text_destroy_time_entries: Delete reported hours
953 text_destroy_time_entries: Delete reported hours
953 text_assign_time_entries_to_project: Assign reported hours to the project
954 text_assign_time_entries_to_project: Assign reported hours to the project
954 text_reassign_time_entries: 'Reassign reported hours to this issue:'
955 text_reassign_time_entries: 'Reassign reported hours to this issue:'
955 text_user_wrote: "%{value} wrote:"
956 text_user_wrote: "%{value} wrote:"
956 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
957 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
957 text_enumeration_category_reassign_to: 'Reassign them to this value:'
958 text_enumeration_category_reassign_to: 'Reassign them to this value:'
958 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them."
959 text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/configuration.yml and restart the application to enable them."
959 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
960 text_repository_usernames_mapping: "Select or update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
960 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
961 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
961 text_custom_field_possible_values_info: 'One line for each value'
962 text_custom_field_possible_values_info: 'One line for each value'
962 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
963 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
963 text_wiki_page_nullify_children: "Keep child pages as root pages"
964 text_wiki_page_nullify_children: "Keep child pages as root pages"
964 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
965 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
965 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
966 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
966 text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?"
967 text_own_membership_delete_confirmation: "You are about to remove some or all of your permissions and may no longer be able to edit this project after that.\nAre you sure you want to continue?"
967 text_zoom_in: Zoom in
968 text_zoom_in: Zoom in
968 text_zoom_out: Zoom out
969 text_zoom_out: Zoom out
969 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
970 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
970 text_scm_path_encoding_note: "Default: UTF-8"
971 text_scm_path_encoding_note: "Default: UTF-8"
971 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
972 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
972 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
973 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
973 text_scm_command: Command
974 text_scm_command: Command
974 text_scm_command_version: Version
975 text_scm_command_version: Version
975 text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
976 text_scm_config: You can configure your scm commands in config/configuration.yml. Please restart the application after editing it.
976 text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
977 text_scm_command_not_available: Scm command is not available. Please check settings on the administration panel.
977 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
978 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
978 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
979 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
979 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
980 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
980
981
981 default_role_manager: Manager
982 default_role_manager: Manager
982 default_role_developer: Developer
983 default_role_developer: Developer
983 default_role_reporter: Reporter
984 default_role_reporter: Reporter
984 default_tracker_bug: Bug
985 default_tracker_bug: Bug
985 default_tracker_feature: Feature
986 default_tracker_feature: Feature
986 default_tracker_support: Support
987 default_tracker_support: Support
987 default_issue_status_new: New
988 default_issue_status_new: New
988 default_issue_status_in_progress: In Progress
989 default_issue_status_in_progress: In Progress
989 default_issue_status_resolved: Resolved
990 default_issue_status_resolved: Resolved
990 default_issue_status_feedback: Feedback
991 default_issue_status_feedback: Feedback
991 default_issue_status_closed: Closed
992 default_issue_status_closed: Closed
992 default_issue_status_rejected: Rejected
993 default_issue_status_rejected: Rejected
993 default_doc_category_user: User documentation
994 default_doc_category_user: User documentation
994 default_doc_category_tech: Technical documentation
995 default_doc_category_tech: Technical documentation
995 default_priority_low: Low
996 default_priority_low: Low
996 default_priority_normal: Normal
997 default_priority_normal: Normal
997 default_priority_high: High
998 default_priority_high: High
998 default_priority_urgent: Urgent
999 default_priority_urgent: Urgent
999 default_priority_immediate: Immediate
1000 default_priority_immediate: Immediate
1000 default_activity_design: Design
1001 default_activity_design: Design
1001 default_activity_development: Development
1002 default_activity_development: Development
1002
1003
1003 enumeration_issue_priorities: Issue priorities
1004 enumeration_issue_priorities: Issue priorities
1004 enumeration_doc_categories: Document categories
1005 enumeration_doc_categories: Document categories
1005 enumeration_activities: Activities (time tracking)
1006 enumeration_activities: Activities (time tracking)
1006 enumeration_system_activity: System Activity
1007 enumeration_system_activity: System Activity
1007 description_filter: Filter
1008 description_filter: Filter
1008 description_search: Searchfield
1009 description_search: Searchfield
1009 description_choose_project: Projects
1010 description_choose_project: Projects
1010 description_project_scope: Search scope
1011 description_project_scope: Search scope
1011 description_notes: Notes
1012 description_notes: Notes
1012 description_message_content: Message content
1013 description_message_content: Message content
1013 description_query_sort_criteria_attribute: Sort attribute
1014 description_query_sort_criteria_attribute: Sort attribute
1014 description_query_sort_criteria_direction: Sort direction
1015 description_query_sort_criteria_direction: Sort direction
1015 description_user_mail_notification: Mail notification settings
1016 description_user_mail_notification: Mail notification settings
1016 description_available_columns: Available Columns
1017 description_available_columns: Available Columns
1017 description_selected_columns: Selected Columns
1018 description_selected_columns: Selected Columns
1018 description_all_columns: All Columns
1019 description_all_columns: All Columns
1019 description_issue_category_reassign: Choose issue category
1020 description_issue_category_reassign: Choose issue category
1020 description_wiki_subpages_reassign: Choose new parent page
1021 description_wiki_subpages_reassign: Choose new parent page
1021 description_date_range_list: Choose range from list
1022 description_date_range_list: Choose range from list
1022 description_date_range_interval: Choose range by selecting start and end date
1023 description_date_range_interval: Choose range by selecting start and end date
1023 description_date_from: Enter start date
1024 description_date_from: Enter start date
1024 description_date_to: Enter end date
1025 description_date_to: Enter end date
@@ -1,391 +1,393
1 ActionController::Routing::Routes.draw do |map|
1 ActionController::Routing::Routes.draw do |map|
2 # Add your own custom routes here.
2 # Add your own custom routes here.
3 # The priority is based upon order of creation: first created -> highest priority.
3 # The priority is based upon order of creation: first created -> highest priority.
4
4
5 # Here's a sample route:
5 # Here's a sample route:
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
6 # map.connect 'products/:id', :controller => 'catalog', :action => 'view'
7 # Keep in mind you can assign values other than :controller and :action
7 # Keep in mind you can assign values other than :controller and :action
8
8
9 map.home '', :controller => 'welcome', :conditions => {:method => :get}
9 map.home '', :controller => 'welcome', :conditions => {:method => :get}
10
10
11 map.signin 'login', :controller => 'account', :action => 'login',
11 map.signin 'login', :controller => 'account', :action => 'login',
12 :conditions => {:method => [:get, :post]}
12 :conditions => {:method => [:get, :post]}
13 map.signout 'logout', :controller => 'account', :action => 'logout',
13 map.signout 'logout', :controller => 'account', :action => 'logout',
14 :conditions => {:method => :get}
14 :conditions => {:method => :get}
15 map.connect 'account/register', :controller => 'account', :action => 'register',
15 map.connect 'account/register', :controller => 'account', :action => 'register',
16 :conditions => {:method => [:get, :post]}
16 :conditions => {:method => [:get, :post]}
17 map.connect 'account/lost_password', :controller => 'account', :action => 'lost_password',
17 map.connect 'account/lost_password', :controller => 'account', :action => 'lost_password',
18 :conditions => {:method => [:get, :post]}
18 :conditions => {:method => [:get, :post]}
19 map.connect 'account/activate', :controller => 'account', :action => 'activate',
19 map.connect 'account/activate', :controller => 'account', :action => 'activate',
20 :conditions => {:method => :get}
20 :conditions => {:method => :get}
21
21
22 map.connect 'projects/:id/wiki', :controller => 'wikis',
22 map.connect 'projects/:id/wiki', :controller => 'wikis',
23 :action => 'edit', :conditions => {:method => :post}
23 :action => 'edit', :conditions => {:method => :post}
24 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis',
24 map.connect 'projects/:id/wiki/destroy', :controller => 'wikis',
25 :action => 'destroy', :conditions => {:method => [:get, :post]}
25 :action => 'destroy', :conditions => {:method => [:get, :post]}
26
26
27 map.with_options :controller => 'messages' do |messages_routes|
27 map.with_options :controller => 'messages' do |messages_routes|
28 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
28 messages_routes.with_options :conditions => {:method => :get} do |messages_views|
29 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
29 messages_views.connect 'boards/:board_id/topics/new', :action => 'new'
30 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
30 messages_views.connect 'boards/:board_id/topics/:id', :action => 'show'
31 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
31 messages_views.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
32 end
32 end
33 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
33 messages_routes.with_options :conditions => {:method => :post} do |messages_actions|
34 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
34 messages_actions.connect 'boards/:board_id/topics/new', :action => 'new'
35 messages_actions.connect 'boards/:board_id/topics/preview', :action => 'preview'
35 messages_actions.connect 'boards/:board_id/topics/preview', :action => 'preview'
36 messages_actions.connect 'boards/:board_id/topics/quote/:id', :action => 'quote'
36 messages_actions.connect 'boards/:board_id/topics/quote/:id', :action => 'quote'
37 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
37 messages_actions.connect 'boards/:board_id/topics/:id/replies', :action => 'reply'
38 messages_actions.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
38 messages_actions.connect 'boards/:board_id/topics/:id/edit', :action => 'edit'
39 messages_actions.connect 'boards/:board_id/topics/:id/destroy', :action => 'destroy'
39 messages_actions.connect 'boards/:board_id/topics/:id/destroy', :action => 'destroy'
40 end
40 end
41 end
41 end
42
42
43 # Misc issue routes. TODO: move into resources
43 # Misc issue routes. TODO: move into resources
44 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes',
44 map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes',
45 :action => 'issues', :conditions => { :method => :get }
45 :action => 'issues', :conditions => { :method => :get }
46 # TODO: would look nicer as /issues/:id/preview
46 # TODO: would look nicer as /issues/:id/preview
47 map.preview_new_issue '/issues/preview/new/:project_id', :controller => 'previews',
47 map.preview_new_issue '/issues/preview/new/:project_id', :controller => 'previews',
48 :action => 'issue'
48 :action => 'issue'
49 map.preview_edit_issue '/issues/preview/edit/:id', :controller => 'previews',
49 map.preview_edit_issue '/issues/preview/edit/:id', :controller => 'previews',
50 :action => 'issue'
50 :action => 'issue'
51 map.issues_context_menu '/issues/context_menu',
51 map.issues_context_menu '/issues/context_menu',
52 :controller => 'context_menus', :action => 'issues'
52 :controller => 'context_menus', :action => 'issues'
53
53
54 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
54 map.issue_changes '/issues/changes', :controller => 'journals', :action => 'index'
55 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new',
55 map.quoted_issue '/issues/:id/quoted', :controller => 'journals', :action => 'new',
56 :id => /\d+/, :conditions => { :method => :post }
56 :id => /\d+/, :conditions => { :method => :post }
57
57
58 map.connect '/journals/diff/:id', :controller => 'journals', :action => 'diff',
58 map.connect '/journals/diff/:id', :controller => 'journals', :action => 'diff',
59 :id => /\d+/, :conditions => { :method => :get }
59 :id => /\d+/, :conditions => { :method => :get }
60 map.connect '/journals/edit/:id', :controller => 'journals', :action => 'edit',
60 map.connect '/journals/edit/:id', :controller => 'journals', :action => 'edit',
61 :id => /\d+/, :conditions => { :method => [:get, :post] }
61 :id => /\d+/, :conditions => { :method => [:get, :post] }
62
62
63 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
63 map.with_options :controller => 'gantts', :action => 'show' do |gantts_routes|
64 gantts_routes.connect '/projects/:project_id/issues/gantt'
64 gantts_routes.connect '/projects/:project_id/issues/gantt'
65 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
65 gantts_routes.connect '/projects/:project_id/issues/gantt.:format'
66 gantts_routes.connect '/issues/gantt.:format'
66 gantts_routes.connect '/issues/gantt.:format'
67 end
67 end
68
68
69 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
69 map.with_options :controller => 'calendars', :action => 'show' do |calendars_routes|
70 calendars_routes.connect '/projects/:project_id/issues/calendar'
70 calendars_routes.connect '/projects/:project_id/issues/calendar'
71 calendars_routes.connect '/issues/calendar'
71 calendars_routes.connect '/issues/calendar'
72 end
72 end
73
73
74 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
74 map.with_options :controller => 'reports', :conditions => {:method => :get} do |reports|
75 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
75 reports.connect 'projects/:id/issues/report', :action => 'issue_report'
76 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
76 reports.connect 'projects/:id/issues/report/:detail', :action => 'issue_report_details'
77 end
77 end
78
78
79 map.connect 'my/account', :controller => 'my', :action => 'account',
79 map.connect 'my/account', :controller => 'my', :action => 'account',
80 :conditions => {:method => [:get, :post]}
80 :conditions => {:method => [:get, :post]}
81 map.connect 'my/page', :controller => 'my', :action => 'page',
81 map.connect 'my/page', :controller => 'my', :action => 'page',
82 :conditions => {:method => :get}
82 :conditions => {:method => :get}
83 # Redirects to my/page
83 # Redirects to my/page
84 map.connect 'my', :controller => 'my', :action => 'index',
84 map.connect 'my', :controller => 'my', :action => 'index',
85 :conditions => {:method => :get}
85 :conditions => {:method => :get}
86 map.connect 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key',
86 map.connect 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key',
87 :conditions => {:method => :post}
87 :conditions => {:method => :post}
88 map.connect 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key',
88 map.connect 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key',
89 :conditions => {:method => :post}
89 :conditions => {:method => :post}
90 map.connect 'my/password', :controller => 'my', :action => 'password',
90 map.connect 'my/password', :controller => 'my', :action => 'password',
91 :conditions => {:method => [:get, :post]}
91 :conditions => {:method => [:get, :post]}
92 map.connect 'my/page_layout', :controller => 'my', :action => 'page_layout',
92 map.connect 'my/page_layout', :controller => 'my', :action => 'page_layout',
93 :conditions => {:method => :get}
93 :conditions => {:method => :get}
94 map.connect 'my/add_block', :controller => 'my', :action => 'add_block',
94 map.connect 'my/add_block', :controller => 'my', :action => 'add_block',
95 :conditions => {:method => :post}
95 :conditions => {:method => :post}
96 map.connect 'my/remove_block', :controller => 'my', :action => 'remove_block',
96 map.connect 'my/remove_block', :controller => 'my', :action => 'remove_block',
97 :conditions => {:method => :post}
97 :conditions => {:method => :post}
98 map.connect 'my/order_blocks', :controller => 'my', :action => 'order_blocks',
98 map.connect 'my/order_blocks', :controller => 'my', :action => 'order_blocks',
99 :conditions => {:method => :post}
99 :conditions => {:method => :post}
100
100
101 map.with_options :controller => 'users' do |users|
101 map.with_options :controller => 'users' do |users|
102 users.user_membership 'users/:id/memberships/:membership_id',
102 users.user_membership 'users/:id/memberships/:membership_id',
103 :action => 'edit_membership',
103 :action => 'edit_membership',
104 :conditions => {:method => :put}
104 :conditions => {:method => :put}
105 users.connect 'users/:id/memberships/:membership_id',
105 users.connect 'users/:id/memberships/:membership_id',
106 :action => 'destroy_membership',
106 :action => 'destroy_membership',
107 :conditions => {:method => :delete}
107 :conditions => {:method => :delete}
108 users.user_memberships 'users/:id/memberships',
108 users.user_memberships 'users/:id/memberships',
109 :action => 'edit_membership',
109 :action => 'edit_membership',
110 :conditions => {:method => :post}
110 :conditions => {:method => :post}
111 end
111 end
112 map.resources :users
112 map.resources :users
113
113
114 # For nice "roadmap" in the url for the index action
114 # For nice "roadmap" in the url for the index action
115 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
115 map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
116
116
117 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
117 map.preview_news '/news/preview', :controller => 'previews', :action => 'news'
118 map.connect 'news/:id/comments', :controller => 'comments',
118 map.connect 'news/:id/comments', :controller => 'comments',
119 :action => 'create', :conditions => {:method => :post}
119 :action => 'create', :conditions => {:method => :post}
120 map.connect 'news/:id/comments/:comment_id', :controller => 'comments',
120 map.connect 'news/:id/comments/:comment_id', :controller => 'comments',
121 :action => 'destroy', :conditions => {:method => :delete}
121 :action => 'destroy', :conditions => {:method => :delete}
122
122
123 map.connect 'watchers/new', :controller=> 'watchers', :action => 'new',
123 map.connect 'watchers/new', :controller=> 'watchers', :action => 'new',
124 :conditions => {:method => :get}
124 :conditions => {:method => :get}
125 map.connect 'watchers', :controller=> 'watchers', :action => 'create',
125 map.connect 'watchers', :controller=> 'watchers', :action => 'create',
126 :conditions => {:method => :post}
126 :conditions => {:method => :post}
127 map.connect 'watchers/append', :controller=> 'watchers', :action => 'append',
128 :conditions => {:method => :post}
127 map.connect 'watchers/destroy', :controller=> 'watchers', :action => 'destroy',
129 map.connect 'watchers/destroy', :controller=> 'watchers', :action => 'destroy',
128 :conditions => {:method => :post}
130 :conditions => {:method => :post}
129 map.connect 'watchers/watch', :controller=> 'watchers', :action => 'watch',
131 map.connect 'watchers/watch', :controller=> 'watchers', :action => 'watch',
130 :conditions => {:method => :post}
132 :conditions => {:method => :post}
131 map.connect 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch',
133 map.connect 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch',
132 :conditions => {:method => :post}
134 :conditions => {:method => :post}
133 map.connect 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user',
135 map.connect 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user',
134 :conditions => {:method => :get}
136 :conditions => {:method => :get}
135
137
136 # TODO: port to be part of the resources route(s)
138 # TODO: port to be part of the resources route(s)
137 map.with_options :conditions => {:method => :get} do |project_views|
139 map.with_options :conditions => {:method => :get} do |project_views|
138 project_views.connect 'projects/:id/settings/:tab',
140 project_views.connect 'projects/:id/settings/:tab',
139 :controller => 'projects', :action => 'settings'
141 :controller => 'projects', :action => 'settings'
140 project_views.connect 'projects/:project_id/issues/:copy_from/copy',
142 project_views.connect 'projects/:project_id/issues/:copy_from/copy',
141 :controller => 'issues', :action => 'new'
143 :controller => 'issues', :action => 'new'
142 end
144 end
143
145
144 map.resources :projects, :member => {
146 map.resources :projects, :member => {
145 :copy => [:get, :post],
147 :copy => [:get, :post],
146 :settings => :get,
148 :settings => :get,
147 :modules => :post,
149 :modules => :post,
148 :archive => :post,
150 :archive => :post,
149 :unarchive => :post
151 :unarchive => :post
150 } do |project|
152 } do |project|
151 project.resource :enumerations, :controller => 'project_enumerations',
153 project.resource :enumerations, :controller => 'project_enumerations',
152 :only => [:update, :destroy]
154 :only => [:update, :destroy]
153 # issue form update
155 # issue form update
154 project.issue_form 'issues/new', :controller => 'issues',
156 project.issue_form 'issues/new', :controller => 'issues',
155 :action => 'new', :conditions => {:method => [:post, :put]}
157 :action => 'new', :conditions => {:method => [:post, :put]}
156 project.resources :issues, :only => [:index, :new, :create] do |issues|
158 project.resources :issues, :only => [:index, :new, :create] do |issues|
157 issues.resources :time_entries, :controller => 'timelog',
159 issues.resources :time_entries, :controller => 'timelog',
158 :collection => {:report => :get}
160 :collection => {:report => :get}
159 end
161 end
160
162
161 project.resources :files, :only => [:index, :new, :create]
163 project.resources :files, :only => [:index, :new, :create]
162 project.resources :versions, :shallow => true,
164 project.resources :versions, :shallow => true,
163 :collection => {:close_completed => :put},
165 :collection => {:close_completed => :put},
164 :member => {:status_by => :post}
166 :member => {:status_by => :post}
165 project.resources :news, :shallow => true
167 project.resources :news, :shallow => true
166 project.resources :time_entries, :controller => 'timelog',
168 project.resources :time_entries, :controller => 'timelog',
167 :collection => {:report => :get}
169 :collection => {:report => :get}
168 project.resources :queries, :only => [:new, :create]
170 project.resources :queries, :only => [:new, :create]
169 project.resources :issue_categories, :shallow => true
171 project.resources :issue_categories, :shallow => true
170 project.resources :documents, :shallow => true, :member => {:add_attachment => :post}
172 project.resources :documents, :shallow => true, :member => {:add_attachment => :post}
171 project.resources :boards
173 project.resources :boards
172 project.resources :repositories, :shallow => true, :except => [:index, :show],
174 project.resources :repositories, :shallow => true, :except => [:index, :show],
173 :member => {:committers => [:get, :post]}
175 :member => {:committers => [:get, :post]}
174 project.resources :memberships, :shallow => true, :controller => 'members',
176 project.resources :memberships, :shallow => true, :controller => 'members',
175 :only => [:index, :show, :create, :update, :destroy],
177 :only => [:index, :show, :create, :update, :destroy],
176 :collection => {:autocomplete => :get}
178 :collection => {:autocomplete => :get}
177
179
178 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
180 project.wiki_start_page 'wiki', :controller => 'wiki', :action => 'show', :conditions => {:method => :get}
179 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
181 project.wiki_index 'wiki/index', :controller => 'wiki', :action => 'index', :conditions => {:method => :get}
180 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
182 project.wiki_diff 'wiki/:id/diff/:version', :controller => 'wiki', :action => 'diff', :version => nil
181 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
183 project.wiki_diff 'wiki/:id/diff/:version/vs/:version_from', :controller => 'wiki', :action => 'diff'
182 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
184 project.wiki_annotate 'wiki/:id/annotate/:version', :controller => 'wiki', :action => 'annotate'
183 project.resources :wiki, :except => [:new, :create], :member => {
185 project.resources :wiki, :except => [:new, :create], :member => {
184 :rename => [:get, :post],
186 :rename => [:get, :post],
185 :history => :get,
187 :history => :get,
186 :preview => :any,
188 :preview => :any,
187 :protect => :post,
189 :protect => :post,
188 :add_attachment => :post
190 :add_attachment => :post
189 }, :collection => {
191 }, :collection => {
190 :export => :get,
192 :export => :get,
191 :date_index => :get
193 :date_index => :get
192 }
194 }
193 end
195 end
194
196
195 map.connect 'news', :controller => 'news', :action => 'index'
197 map.connect 'news', :controller => 'news', :action => 'index'
196 map.connect 'news.:format', :controller => 'news', :action => 'index'
198 map.connect 'news.:format', :controller => 'news', :action => 'index'
197
199
198 map.resources :queries, :except => [:show]
200 map.resources :queries, :except => [:show]
199 map.resources :issues,
201 map.resources :issues,
200 :collection => {:bulk_edit => [:get, :post], :bulk_update => :post} do |issues|
202 :collection => {:bulk_edit => [:get, :post], :bulk_update => :post} do |issues|
201 issues.resources :time_entries, :controller => 'timelog',
203 issues.resources :time_entries, :controller => 'timelog',
202 :collection => {:report => :get}
204 :collection => {:report => :get}
203 issues.resources :relations, :shallow => true,
205 issues.resources :relations, :shallow => true,
204 :controller => 'issue_relations',
206 :controller => 'issue_relations',
205 :only => [:index, :show, :create, :destroy]
207 :only => [:index, :show, :create, :destroy]
206 end
208 end
207 # Bulk deletion
209 # Bulk deletion
208 map.connect '/issues', :controller => 'issues', :action => 'destroy',
210 map.connect '/issues', :controller => 'issues', :action => 'destroy',
209 :conditions => {:method => :delete}
211 :conditions => {:method => :delete}
210
212
211 map.connect '/time_entries/destroy',
213 map.connect '/time_entries/destroy',
212 :controller => 'timelog', :action => 'destroy',
214 :controller => 'timelog', :action => 'destroy',
213 :conditions => { :method => :delete }
215 :conditions => { :method => :delete }
214 map.time_entries_context_menu '/time_entries/context_menu',
216 map.time_entries_context_menu '/time_entries/context_menu',
215 :controller => 'context_menus', :action => 'time_entries'
217 :controller => 'context_menus', :action => 'time_entries'
216
218
217 map.resources :time_entries, :controller => 'timelog',
219 map.resources :time_entries, :controller => 'timelog',
218 :collection => {:report => :get, :bulk_edit => :get, :bulk_update => :post}
220 :collection => {:report => :get, :bulk_edit => :get, :bulk_update => :post}
219
221
220 map.with_options :controller => 'activities', :action => 'index',
222 map.with_options :controller => 'activities', :action => 'index',
221 :conditions => {:method => :get} do |activity|
223 :conditions => {:method => :get} do |activity|
222 activity.connect 'projects/:id/activity'
224 activity.connect 'projects/:id/activity'
223 activity.connect 'projects/:id/activity.:format'
225 activity.connect 'projects/:id/activity.:format'
224 activity.connect 'activity', :id => nil
226 activity.connect 'activity', :id => nil
225 activity.connect 'activity.:format', :id => nil
227 activity.connect 'activity.:format', :id => nil
226 end
228 end
227
229
228 map.with_options :controller => 'repositories' do |repositories|
230 map.with_options :controller => 'repositories' do |repositories|
229 repositories.with_options :conditions => {:method => :get} do |repository_views|
231 repositories.with_options :conditions => {:method => :get} do |repository_views|
230 repository_views.connect 'projects/:id/repository',
232 repository_views.connect 'projects/:id/repository',
231 :action => 'show'
233 :action => 'show'
232
234
233 repository_views.connect 'projects/:id/repository/:repository_id/statistics',
235 repository_views.connect 'projects/:id/repository/:repository_id/statistics',
234 :action => 'stats'
236 :action => 'stats'
235 repository_views.connect 'projects/:id/repository/:repository_id/graph',
237 repository_views.connect 'projects/:id/repository/:repository_id/graph',
236 :action => 'graph'
238 :action => 'graph'
237
239
238 repository_views.connect 'projects/:id/repository/statistics',
240 repository_views.connect 'projects/:id/repository/statistics',
239 :action => 'stats'
241 :action => 'stats'
240 repository_views.connect 'projects/:id/repository/graph',
242 repository_views.connect 'projects/:id/repository/graph',
241 :action => 'graph'
243 :action => 'graph'
242
244
243 repository_views.connect 'projects/:id/repository/:repository_id/revisions',
245 repository_views.connect 'projects/:id/repository/:repository_id/revisions',
244 :action => 'revisions'
246 :action => 'revisions'
245 repository_views.connect 'projects/:id/repository/:repository_id/revisions.:format',
247 repository_views.connect 'projects/:id/repository/:repository_id/revisions.:format',
246 :action => 'revisions'
248 :action => 'revisions'
247 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev',
249 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev',
248 :action => 'revision'
250 :action => 'revision'
249 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues',
251 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues',
250 :action => 'add_related_issue', :conditions => {:method => :post}
252 :action => 'add_related_issue', :conditions => {:method => :post}
251 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id',
253 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id',
252 :action => 'remove_related_issue', :conditions => {:method => :delete}
254 :action => 'remove_related_issue', :conditions => {:method => :delete}
253 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff',
255 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff',
254 :action => 'diff'
256 :action => 'diff'
255 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff.:format',
257 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/diff.:format',
256 :action => 'diff'
258 :action => 'diff'
257 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/raw/*path',
259 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/raw/*path',
258 :action => 'entry', :format => 'raw'
260 :action => 'entry', :format => 'raw'
259 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/:action/*path',
261 repository_views.connect 'projects/:id/repository/:repository_id/revisions/:rev/:action/*path',
260 :requirements => {
262 :requirements => {
261 :action => /(browse|show|entry|changes|annotate|diff)/,
263 :action => /(browse|show|entry|changes|annotate|diff)/,
262 :rev => /[a-z0-9\.\-_]+/
264 :rev => /[a-z0-9\.\-_]+/
263 }
265 }
264 repository_views.connect 'projects/:id/repository/:repository_id/raw/*path',
266 repository_views.connect 'projects/:id/repository/:repository_id/raw/*path',
265 :action => 'entry', :format => 'raw'
267 :action => 'entry', :format => 'raw'
266 repository_views.connect 'projects/:id/repository/:repository_id/:action/*path',
268 repository_views.connect 'projects/:id/repository/:repository_id/:action/*path',
267 :requirements => { :action => /(browse|entry|changes|annotate|diff)/ }
269 :requirements => { :action => /(browse|entry|changes|annotate|diff)/ }
268 repository_views.connect 'projects/:id/repository/:repository_id/show/*path',
270 repository_views.connect 'projects/:id/repository/:repository_id/show/*path',
269 :requirements => { :path => /.+/ }
271 :requirements => { :path => /.+/ }
270
272
271 repository_views.connect 'projects/:id/repository/revisions',
273 repository_views.connect 'projects/:id/repository/revisions',
272 :action => 'revisions'
274 :action => 'revisions'
273 repository_views.connect 'projects/:id/repository/revisions.:format',
275 repository_views.connect 'projects/:id/repository/revisions.:format',
274 :action => 'revisions'
276 :action => 'revisions'
275 repository_views.connect 'projects/:id/repository/revisions/:rev',
277 repository_views.connect 'projects/:id/repository/revisions/:rev',
276 :action => 'revision'
278 :action => 'revision'
277 repository_views.connect 'projects/:id/repository/revisions/:rev/issues',
279 repository_views.connect 'projects/:id/repository/revisions/:rev/issues',
278 :action => 'add_related_issue', :conditions => {:method => :post}
280 :action => 'add_related_issue', :conditions => {:method => :post}
279 repository_views.connect 'projects/:id/repository/revisions/:rev/issues/:issue_id',
281 repository_views.connect 'projects/:id/repository/revisions/:rev/issues/:issue_id',
280 :action => 'remove_related_issue', :conditions => {:method => :delete}
282 :action => 'remove_related_issue', :conditions => {:method => :delete}
281 repository_views.connect 'projects/:id/repository/revisions/:rev/diff',
283 repository_views.connect 'projects/:id/repository/revisions/:rev/diff',
282 :action => 'diff'
284 :action => 'diff'
283 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format',
285 repository_views.connect 'projects/:id/repository/revisions/:rev/diff.:format',
284 :action => 'diff'
286 :action => 'diff'
285 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path',
287 repository_views.connect 'projects/:id/repository/revisions/:rev/raw/*path',
286 :action => 'entry', :format => 'raw'
288 :action => 'entry', :format => 'raw'
287 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path',
289 repository_views.connect 'projects/:id/repository/revisions/:rev/:action/*path',
288 :requirements => {
290 :requirements => {
289 :action => /(browse|show|entry|changes|annotate|diff)/,
291 :action => /(browse|show|entry|changes|annotate|diff)/,
290 :rev => /[a-z0-9\.\-_]+/
292 :rev => /[a-z0-9\.\-_]+/
291 }
293 }
292 repository_views.connect 'projects/:id/repository/raw/*path',
294 repository_views.connect 'projects/:id/repository/raw/*path',
293 :action => 'entry', :format => 'raw'
295 :action => 'entry', :format => 'raw'
294 repository_views.connect 'projects/:id/repository/:action/*path',
296 repository_views.connect 'projects/:id/repository/:action/*path',
295 :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
297 :requirements => { :action => /(browse|show|entry|changes|annotate|diff)/ }
296
298
297 repository_views.connect 'projects/:id/repository/:repository_id',
299 repository_views.connect 'projects/:id/repository/:repository_id',
298 :action => 'show'
300 :action => 'show'
299 end
301 end
300
302
301 repositories.connect 'projects/:id/repository/revision',
303 repositories.connect 'projects/:id/repository/revision',
302 :action => 'revision',
304 :action => 'revision',
303 :conditions => {:method => [:get, :post]}
305 :conditions => {:method => [:get, :post]}
304 end
306 end
305
307
306 # additional routes for having the file name at the end of url
308 # additional routes for having the file name at the end of url
307 map.connect 'attachments/:id/:filename', :controller => 'attachments',
309 map.connect 'attachments/:id/:filename', :controller => 'attachments',
308 :action => 'show', :id => /\d+/, :filename => /.*/,
310 :action => 'show', :id => /\d+/, :filename => /.*/,
309 :conditions => {:method => :get}
311 :conditions => {:method => :get}
310 map.connect 'attachments/download/:id/:filename', :controller => 'attachments',
312 map.connect 'attachments/download/:id/:filename', :controller => 'attachments',
311 :action => 'download', :id => /\d+/, :filename => /.*/,
313 :action => 'download', :id => /\d+/, :filename => /.*/,
312 :conditions => {:method => :get}
314 :conditions => {:method => :get}
313 map.connect 'attachments/download/:id', :controller => 'attachments',
315 map.connect 'attachments/download/:id', :controller => 'attachments',
314 :action => 'download', :id => /\d+/,
316 :action => 'download', :id => /\d+/,
315 :conditions => {:method => :get}
317 :conditions => {:method => :get}
316 map.resources :attachments, :only => [:show, :destroy]
318 map.resources :attachments, :only => [:show, :destroy]
317
319
318 map.resources :groups, :member => {:autocomplete_for_user => :get}
320 map.resources :groups, :member => {:autocomplete_for_user => :get}
319 map.group_users 'groups/:id/users', :controller => 'groups',
321 map.group_users 'groups/:id/users', :controller => 'groups',
320 :action => 'add_users', :id => /\d+/,
322 :action => 'add_users', :id => /\d+/,
321 :conditions => {:method => :post}
323 :conditions => {:method => :post}
322 map.group_user 'groups/:id/users/:user_id', :controller => 'groups',
324 map.group_user 'groups/:id/users/:user_id', :controller => 'groups',
323 :action => 'remove_user', :id => /\d+/,
325 :action => 'remove_user', :id => /\d+/,
324 :conditions => {:method => :delete}
326 :conditions => {:method => :delete}
325 map.connect 'groups/destroy_membership/:id', :controller => 'groups',
327 map.connect 'groups/destroy_membership/:id', :controller => 'groups',
326 :action => 'destroy_membership', :id => /\d+/,
328 :action => 'destroy_membership', :id => /\d+/,
327 :conditions => {:method => :post}
329 :conditions => {:method => :post}
328 map.connect 'groups/edit_membership/:id', :controller => 'groups',
330 map.connect 'groups/edit_membership/:id', :controller => 'groups',
329 :action => 'edit_membership', :id => /\d+/,
331 :action => 'edit_membership', :id => /\d+/,
330 :conditions => {:method => :post}
332 :conditions => {:method => :post}
331
333
332 map.resources :trackers, :except => :show
334 map.resources :trackers, :except => :show
333 map.resources :issue_statuses, :except => :show, :collection => {:update_issue_done_ratio => :post}
335 map.resources :issue_statuses, :except => :show, :collection => {:update_issue_done_ratio => :post}
334 map.resources :custom_fields, :except => :show
336 map.resources :custom_fields, :except => :show
335 map.resources :roles, :except => :show, :collection => {:permissions => [:get, :post]}
337 map.resources :roles, :except => :show, :collection => {:permissions => [:get, :post]}
336 map.resources :enumerations, :except => :show
338 map.resources :enumerations, :except => :show
337
339
338 map.connect 'search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
340 map.connect 'search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
339
341
340 map.connect 'mail_handler', :controller => 'mail_handler',
342 map.connect 'mail_handler', :controller => 'mail_handler',
341 :action => 'index', :conditions => {:method => :post}
343 :action => 'index', :conditions => {:method => :post}
342
344
343 map.connect 'admin', :controller => 'admin', :action => 'index',
345 map.connect 'admin', :controller => 'admin', :action => 'index',
344 :conditions => {:method => :get}
346 :conditions => {:method => :get}
345 map.connect 'admin/projects', :controller => 'admin', :action => 'projects',
347 map.connect 'admin/projects', :controller => 'admin', :action => 'projects',
346 :conditions => {:method => :get}
348 :conditions => {:method => :get}
347 map.connect 'admin/plugins', :controller => 'admin', :action => 'plugins',
349 map.connect 'admin/plugins', :controller => 'admin', :action => 'plugins',
348 :conditions => {:method => :get}
350 :conditions => {:method => :get}
349 map.connect 'admin/info', :controller => 'admin', :action => 'info',
351 map.connect 'admin/info', :controller => 'admin', :action => 'info',
350 :conditions => {:method => :get}
352 :conditions => {:method => :get}
351 map.connect 'admin/test_email', :controller => 'admin', :action => 'test_email',
353 map.connect 'admin/test_email', :controller => 'admin', :action => 'test_email',
352 :conditions => {:method => :get}
354 :conditions => {:method => :get}
353 map.connect 'admin/default_configuration', :controller => 'admin',
355 map.connect 'admin/default_configuration', :controller => 'admin',
354 :action => 'default_configuration', :conditions => {:method => :post}
356 :action => 'default_configuration', :conditions => {:method => :post}
355
357
356 map.resources :auth_sources, :member => {:test_connection => :get}
358 map.resources :auth_sources, :member => {:test_connection => :get}
357
359
358 map.connect 'workflows', :controller => 'workflows',
360 map.connect 'workflows', :controller => 'workflows',
359 :action => 'index', :conditions => {:method => :get}
361 :action => 'index', :conditions => {:method => :get}
360 map.connect 'workflows/edit', :controller => 'workflows',
362 map.connect 'workflows/edit', :controller => 'workflows',
361 :action => 'edit', :conditions => {:method => [:get, :post]}
363 :action => 'edit', :conditions => {:method => [:get, :post]}
362 map.connect 'workflows/copy', :controller => 'workflows',
364 map.connect 'workflows/copy', :controller => 'workflows',
363 :action => 'copy', :conditions => {:method => [:get, :post]}
365 :action => 'copy', :conditions => {:method => [:get, :post]}
364
366
365 map.connect 'settings', :controller => 'settings',
367 map.connect 'settings', :controller => 'settings',
366 :action => 'index', :conditions => {:method => :get}
368 :action => 'index', :conditions => {:method => :get}
367 map.connect 'settings/edit', :controller => 'settings',
369 map.connect 'settings/edit', :controller => 'settings',
368 :action => 'edit', :conditions => {:method => [:get, :post]}
370 :action => 'edit', :conditions => {:method => [:get, :post]}
369 map.connect 'settings/plugin/:id', :controller => 'settings',
371 map.connect 'settings/plugin/:id', :controller => 'settings',
370 :action => 'plugin', :conditions => {:method => [:get, :post]}
372 :action => 'plugin', :conditions => {:method => [:get, :post]}
371
373
372 map.with_options :controller => 'sys' do |sys|
374 map.with_options :controller => 'sys' do |sys|
373 sys.connect 'sys/projects.:format',
375 sys.connect 'sys/projects.:format',
374 :action => 'projects',
376 :action => 'projects',
375 :conditions => {:method => :get}
377 :conditions => {:method => :get}
376 sys.connect 'sys/projects/:id/repository.:format',
378 sys.connect 'sys/projects/:id/repository.:format',
377 :action => 'create_project_repository',
379 :action => 'create_project_repository',
378 :conditions => {:method => :post}
380 :conditions => {:method => :post}
379 sys.connect 'sys/fetch_changesets',
381 sys.connect 'sys/fetch_changesets',
380 :action => 'fetch_changesets',
382 :action => 'fetch_changesets',
381 :conditions => {:method => :get}
383 :conditions => {:method => :get}
382 end
384 end
383
385
384 map.connect 'uploads.:format', :controller => 'attachments', :action => 'upload', :conditions => {:method => :post}
386 map.connect 'uploads.:format', :controller => 'attachments', :action => 'upload', :conditions => {:method => :post}
385
387
386 map.connect 'robots.txt', :controller => 'welcome',
388 map.connect 'robots.txt', :controller => 'welcome',
387 :action => 'robots', :conditions => {:method => :get}
389 :action => 'robots', :conditions => {:method => :get}
388
390
389 # Used for OpenID
391 # Used for OpenID
390 map.root :controller => 'account', :action => 'login'
392 map.root :controller => 'account', :action => 'login'
391 end
393 end
@@ -1,1074 +1,1080
1 html {overflow-y:scroll;}
1 html {overflow-y:scroll;}
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
3
3
4 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
5 h1 {margin:0; padding:0; font-size: 24px;}
5 h1 {margin:0; padding:0; font-size: 24px;}
6 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
8 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
9
9
10 /***** Layout *****/
10 /***** Layout *****/
11 #wrapper {background: white;}
11 #wrapper {background: white;}
12
12
13 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
14 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu ul {margin: 0; padding: 0;}
15 #top-menu li {
15 #top-menu li {
16 float:left;
16 float:left;
17 list-style-type:none;
17 list-style-type:none;
18 margin: 0px 0px 0px 0px;
18 margin: 0px 0px 0px 0px;
19 padding: 0px 0px 0px 0px;
19 padding: 0px 0px 0px 0px;
20 white-space:nowrap;
20 white-space:nowrap;
21 }
21 }
22 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
22 #top-menu a {color: #fff; margin-right: 8px; font-weight: bold;}
23 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
24
24
25 #account {float:right;}
25 #account {float:right;}
26
26
27 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
28 #header a {color:#f8f8f8;}
28 #header a {color:#f8f8f8;}
29 #header h1 a.ancestor { font-size: 80%; }
29 #header h1 a.ancestor { font-size: 80%; }
30 #quick-search {float:right;}
30 #quick-search {float:right;}
31
31
32 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
32 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
33 #main-menu ul {margin: 0; padding: 0;}
33 #main-menu ul {margin: 0; padding: 0;}
34 #main-menu li {
34 #main-menu li {
35 float:left;
35 float:left;
36 list-style-type:none;
36 list-style-type:none;
37 margin: 0px 2px 0px 0px;
37 margin: 0px 2px 0px 0px;
38 padding: 0px 0px 0px 0px;
38 padding: 0px 0px 0px 0px;
39 white-space:nowrap;
39 white-space:nowrap;
40 }
40 }
41 #main-menu li a {
41 #main-menu li a {
42 display: block;
42 display: block;
43 color: #fff;
43 color: #fff;
44 text-decoration: none;
44 text-decoration: none;
45 font-weight: bold;
45 font-weight: bold;
46 margin: 0;
46 margin: 0;
47 padding: 4px 10px 4px 10px;
47 padding: 4px 10px 4px 10px;
48 }
48 }
49 #main-menu li a:hover {background:#759FCF; color:#fff;}
49 #main-menu li a:hover {background:#759FCF; color:#fff;}
50 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
50 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
51
51
52 #admin-menu ul {margin: 0; padding: 0;}
52 #admin-menu ul {margin: 0; padding: 0;}
53 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
53 #admin-menu li {margin: 0; padding: 0 0 6px 0; list-style-type:none;}
54
54
55 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
55 #admin-menu a { background-position: 0% 40%; background-repeat: no-repeat; padding-left: 20px; padding-top: 2px; padding-bottom: 3px;}
56 #admin-menu a.projects { background-image: url(../images/projects.png); }
56 #admin-menu a.projects { background-image: url(../images/projects.png); }
57 #admin-menu a.users { background-image: url(../images/user.png); }
57 #admin-menu a.users { background-image: url(../images/user.png); }
58 #admin-menu a.groups { background-image: url(../images/group.png); }
58 #admin-menu a.groups { background-image: url(../images/group.png); }
59 #admin-menu a.roles { background-image: url(../images/database_key.png); }
59 #admin-menu a.roles { background-image: url(../images/database_key.png); }
60 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
60 #admin-menu a.trackers { background-image: url(../images/ticket.png); }
61 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
61 #admin-menu a.issue_statuses { background-image: url(../images/ticket_edit.png); }
62 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
62 #admin-menu a.workflows { background-image: url(../images/ticket_go.png); }
63 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
63 #admin-menu a.custom_fields { background-image: url(../images/textfield.png); }
64 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
64 #admin-menu a.enumerations { background-image: url(../images/text_list_bullets.png); }
65 #admin-menu a.settings { background-image: url(../images/changeset.png); }
65 #admin-menu a.settings { background-image: url(../images/changeset.png); }
66 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
66 #admin-menu a.plugins { background-image: url(../images/plugin.png); }
67 #admin-menu a.info { background-image: url(../images/help.png); }
67 #admin-menu a.info { background-image: url(../images/help.png); }
68 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
68 #admin-menu a.server_authentication { background-image: url(../images/server_key.png); }
69
69
70 #main {background-color:#EEEEEE;}
70 #main {background-color:#EEEEEE;}
71
71
72 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
72 #sidebar{ float: right; width: 22%; position: relative; z-index: 9; padding: 0; margin: 0;}
73 * html #sidebar{ width: 22%; }
73 * html #sidebar{ width: 22%; }
74 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
74 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
75 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
75 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
76 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
76 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
77 #sidebar .contextual { margin-right: 1em; }
77 #sidebar .contextual { margin-right: 1em; }
78
78
79 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
79 #content { width: 75%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; }
80 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
80 * html #content{ width: 75%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
81 html>body #content { min-height: 600px; }
81 html>body #content { min-height: 600px; }
82 * html body #content { height: 600px; } /* IE */
82 * html body #content { height: 600px; } /* IE */
83
83
84 #main.nosidebar #sidebar{ display: none; }
84 #main.nosidebar #sidebar{ display: none; }
85 #main.nosidebar #content{ width: auto; border-right: 0; }
85 #main.nosidebar #content{ width: auto; border-right: 0; }
86
86
87 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
87 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
88
88
89 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
89 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
90 #login-form table td {padding: 6px;}
90 #login-form table td {padding: 6px;}
91 #login-form label {font-weight: bold;}
91 #login-form label {font-weight: bold;}
92 #login-form input#username, #login-form input#password { width: 300px; }
92 #login-form input#username, #login-form input#password { width: 300px; }
93
93
94 #modalbg {position:absolute; top:0; left:0; width:100%; height:100%; background:#ccc; z-index:49; opacity:0.5;}
94 #modalbg {position:absolute; top:0; left:0; width:100%; height:100%; background:#ccc; z-index:49; opacity:0.5;}
95 html>body #modalbg {position:fixed;}
95 html>body #modalbg {position:fixed;}
96 div.modal { border-radius:5px; position:absolute; top:25%; background:#fff; border:2px solid #759FCF; z-index:50; padding:0px; padding:8px; box-shadow: 1px 1px 8px #888; }
96 div.modal { border-radius:5px; position:absolute; top:25%; background:#fff; border:2px solid #759FCF; z-index:50; padding:0px; padding:8px; box-shadow: 1px 1px 8px #888; }
97 div.modal h3.title {background:#759FCF; color:#fff; border:0; padding-left:8px; margin:-8px; margin-bottom: 1em; border-top-left-radius:2px;border-top-right-radius:2px;}
97 div.modal h3.title {background:#759FCF; color:#fff; border:0; padding-left:8px; margin:-8px; margin-bottom: 1em; border-top-left-radius:2px;border-top-right-radius:2px;}
98 div.modal p.buttons {text-align:right; margin-bottom:0;}
98 div.modal p.buttons {text-align:right; margin-bottom:0;}
99 html>body div.modal {position:fixed;}
99 html>body div.modal {position:fixed;}
100
100
101 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
101 input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; }
102
102
103 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
103 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
104
104
105 /***** Links *****/
105 /***** Links *****/
106 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
106 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
107 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
107 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
108 a img{ border: 0; }
108 a img{ border: 0; }
109
109
110 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
110 a.issue.closed, a.issue.closed:link, a.issue.closed:visited { color: #999; text-decoration: line-through; }
111
111
112 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px; -moz-border-radius:2px;}
112 #sidebar a.selected {line-height:1.7em; padding:1px 3px 2px 2px; margin-left:-2px; background-color:#9DB9D5; color:#fff; border-radius:2px; -moz-border-radius:2px;}
113 #sidebar a.selected:hover {text-decoration:none;}
113 #sidebar a.selected:hover {text-decoration:none;}
114 #admin-menu a {line-height:1.7em;}
114 #admin-menu a {line-height:1.7em;}
115 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
115 #admin-menu a.selected {padding-left: 20px !important; background-position: 2px 40%;}
116
116
117 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
117 a.collapsible {padding-left: 12px; background: url(../images/arrow_expanded.png) no-repeat -3px 40%;}
118 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
118 a.collapsible.collapsed {background: url(../images/arrow_collapsed.png) no-repeat -5px 40%;}
119
119
120 a#toggle-completed-versions {color:#999;}
120 a#toggle-completed-versions {color:#999;}
121 /***** Tables *****/
121 /***** Tables *****/
122 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
122 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
123 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
123 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
124 table.list td { vertical-align: top; }
124 table.list td { vertical-align: top; }
125 table.list td.id { width: 2%; text-align: center;}
125 table.list td.id { width: 2%; text-align: center;}
126 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
126 table.list td.checkbox { width: 15px; padding: 2px 0 0 0; }
127 table.list td.checkbox input {padding:0px;}
127 table.list td.checkbox input {padding:0px;}
128 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
128 table.list td.buttons { width: 15%; white-space:nowrap; text-align: right; }
129 table.list td.buttons a { padding-right: 0.6em; }
129 table.list td.buttons a { padding-right: 0.6em; }
130 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
130 table.list caption { text-align: left; padding: 0.5em 0.5em 0.5em 0; }
131
131
132 tr.project td.name a { white-space:nowrap; }
132 tr.project td.name a { white-space:nowrap; }
133
133
134 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
134 tr.project.idnt td.name span {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
135 tr.project.idnt-1 td.name {padding-left: 0.5em;}
135 tr.project.idnt-1 td.name {padding-left: 0.5em;}
136 tr.project.idnt-2 td.name {padding-left: 2em;}
136 tr.project.idnt-2 td.name {padding-left: 2em;}
137 tr.project.idnt-3 td.name {padding-left: 3.5em;}
137 tr.project.idnt-3 td.name {padding-left: 3.5em;}
138 tr.project.idnt-4 td.name {padding-left: 5em;}
138 tr.project.idnt-4 td.name {padding-left: 5em;}
139 tr.project.idnt-5 td.name {padding-left: 6.5em;}
139 tr.project.idnt-5 td.name {padding-left: 6.5em;}
140 tr.project.idnt-6 td.name {padding-left: 8em;}
140 tr.project.idnt-6 td.name {padding-left: 8em;}
141 tr.project.idnt-7 td.name {padding-left: 9.5em;}
141 tr.project.idnt-7 td.name {padding-left: 9.5em;}
142 tr.project.idnt-8 td.name {padding-left: 11em;}
142 tr.project.idnt-8 td.name {padding-left: 11em;}
143 tr.project.idnt-9 td.name {padding-left: 12.5em;}
143 tr.project.idnt-9 td.name {padding-left: 12.5em;}
144
144
145 tr.issue { text-align: center; white-space: nowrap; }
145 tr.issue { text-align: center; white-space: nowrap; }
146 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text { white-space: normal; }
146 tr.issue td.subject, tr.issue td.category, td.assigned_to, tr.issue td.string, tr.issue td.text { white-space: normal; }
147 tr.issue td.subject { text-align: left; }
147 tr.issue td.subject { text-align: left; }
148 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
148 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
149
149
150 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
150 tr.issue.idnt td.subject a {background: url(../images/bullet_arrow_right.png) no-repeat 0 50%; padding-left: 16px;}
151 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
151 tr.issue.idnt-1 td.subject {padding-left: 0.5em;}
152 tr.issue.idnt-2 td.subject {padding-left: 2em;}
152 tr.issue.idnt-2 td.subject {padding-left: 2em;}
153 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
153 tr.issue.idnt-3 td.subject {padding-left: 3.5em;}
154 tr.issue.idnt-4 td.subject {padding-left: 5em;}
154 tr.issue.idnt-4 td.subject {padding-left: 5em;}
155 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
155 tr.issue.idnt-5 td.subject {padding-left: 6.5em;}
156 tr.issue.idnt-6 td.subject {padding-left: 8em;}
156 tr.issue.idnt-6 td.subject {padding-left: 8em;}
157 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
157 tr.issue.idnt-7 td.subject {padding-left: 9.5em;}
158 tr.issue.idnt-8 td.subject {padding-left: 11em;}
158 tr.issue.idnt-8 td.subject {padding-left: 11em;}
159 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
159 tr.issue.idnt-9 td.subject {padding-left: 12.5em;}
160
160
161 tr.entry { border: 1px solid #f8f8f8; }
161 tr.entry { border: 1px solid #f8f8f8; }
162 tr.entry td { white-space: nowrap; }
162 tr.entry td { white-space: nowrap; }
163 tr.entry td.filename { width: 30%; }
163 tr.entry td.filename { width: 30%; }
164 tr.entry td.filename_no_report { width: 70%; }
164 tr.entry td.filename_no_report { width: 70%; }
165 tr.entry td.size { text-align: right; font-size: 90%; }
165 tr.entry td.size { text-align: right; font-size: 90%; }
166 tr.entry td.revision, tr.entry td.author { text-align: center; }
166 tr.entry td.revision, tr.entry td.author { text-align: center; }
167 tr.entry td.age { text-align: right; }
167 tr.entry td.age { text-align: right; }
168 tr.entry.file td.filename a { margin-left: 16px; }
168 tr.entry.file td.filename a { margin-left: 16px; }
169 tr.entry.file td.filename_no_report a { margin-left: 16px; }
169 tr.entry.file td.filename_no_report a { margin-left: 16px; }
170
170
171 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
171 tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
172 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
172 tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
173
173
174 tr.changeset { height: 20px }
174 tr.changeset { height: 20px }
175 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
175 tr.changeset ul, ol { margin-top: 0px; margin-bottom: 0px; }
176 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
176 tr.changeset td.revision_graph { width: 15%; background-color: #fffffb; }
177 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
177 tr.changeset td.author { text-align: center; width: 15%; white-space:nowrap;}
178 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
178 tr.changeset td.committed_on { text-align: center; width: 15%; white-space:nowrap;}
179
179
180 table.files tr.file td { text-align: center; }
180 table.files tr.file td { text-align: center; }
181 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
181 table.files tr.file td.filename { text-align: left; padding-left: 24px; }
182 table.files tr.file td.digest { font-size: 80%; }
182 table.files tr.file td.digest { font-size: 80%; }
183
183
184 table.members td.roles, table.memberships td.roles { width: 45%; }
184 table.members td.roles, table.memberships td.roles { width: 45%; }
185
185
186 tr.message { height: 2.6em; }
186 tr.message { height: 2.6em; }
187 tr.message td.subject { padding-left: 20px; }
187 tr.message td.subject { padding-left: 20px; }
188 tr.message td.created_on { white-space: nowrap; }
188 tr.message td.created_on { white-space: nowrap; }
189 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
189 tr.message td.last_message { font-size: 80%; white-space: nowrap; }
190 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
190 tr.message.locked td.subject { background: url(../images/locked.png) no-repeat 0 1px; }
191 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
191 tr.message.sticky td.subject { background: url(../images/bullet_go.png) no-repeat 0 1px; font-weight: bold; }
192
192
193 tr.version.closed, tr.version.closed a { color: #999; }
193 tr.version.closed, tr.version.closed a { color: #999; }
194 tr.version td.name { padding-left: 20px; }
194 tr.version td.name { padding-left: 20px; }
195 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
195 tr.version.shared td.name { background: url(../images/link.png) no-repeat 0% 70%; }
196 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
196 tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space:nowrap; }
197
197
198 tr.user td { width:13%; }
198 tr.user td { width:13%; }
199 tr.user td.email { width:18%; }
199 tr.user td.email { width:18%; }
200 tr.user td { white-space: nowrap; }
200 tr.user td { white-space: nowrap; }
201 tr.user.locked, tr.user.registered { color: #aaa; }
201 tr.user.locked, tr.user.registered { color: #aaa; }
202 tr.user.locked a, tr.user.registered a { color: #aaa; }
202 tr.user.locked a, tr.user.registered a { color: #aaa; }
203
203
204 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
204 table.permissions td.role {color:#999;font-size:90%;font-weight:normal !important;text-align:center;vertical-align:bottom;}
205
205
206 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
206 tr.wiki-page-version td.updated_on, tr.wiki-page-version td.author {text-align:center;}
207
207
208 tr.time-entry { text-align: center; white-space: nowrap; }
208 tr.time-entry { text-align: center; white-space: nowrap; }
209 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
209 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
210 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
210 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
211 td.hours .hours-dec { font-size: 0.9em; }
211 td.hours .hours-dec { font-size: 0.9em; }
212
212
213 table.plugins td { vertical-align: middle; }
213 table.plugins td { vertical-align: middle; }
214 table.plugins td.configure { text-align: right; padding-right: 1em; }
214 table.plugins td.configure { text-align: right; padding-right: 1em; }
215 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
215 table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; }
216 table.plugins span.description { display: block; font-size: 0.9em; }
216 table.plugins span.description { display: block; font-size: 0.9em; }
217 table.plugins span.url { display: block; font-size: 0.9em; }
217 table.plugins span.url { display: block; font-size: 0.9em; }
218
218
219 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
219 table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; }
220 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
220 table.list tbody tr.group span.count { color: #aaa; font-size: 80%; }
221 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
221 tr.group a.toggle-all { color: #aaa; font-size: 80%; font-weight: normal; display:none;}
222 tr.group:hover a.toggle-all { display:inline;}
222 tr.group:hover a.toggle-all { display:inline;}
223 a.toggle-all:hover {text-decoration:none;}
223 a.toggle-all:hover {text-decoration:none;}
224
224
225 table.list tbody tr:hover { background-color:#ffffdd; }
225 table.list tbody tr:hover { background-color:#ffffdd; }
226 table.list tbody tr.group:hover { background-color:inherit; }
226 table.list tbody tr.group:hover { background-color:inherit; }
227 table td {padding:2px;}
227 table td {padding:2px;}
228 table p {margin:0;}
228 table p {margin:0;}
229 .odd {background-color:#f6f7f8;}
229 .odd {background-color:#f6f7f8;}
230 .even {background-color: #fff;}
230 .even {background-color: #fff;}
231
231
232 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
232 a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; }
233 a.sort.asc { background-image: url(../images/sort_asc.png); }
233 a.sort.asc { background-image: url(../images/sort_asc.png); }
234 a.sort.desc { background-image: url(../images/sort_desc.png); }
234 a.sort.desc { background-image: url(../images/sort_desc.png); }
235
235
236 table.attributes { width: 100% }
236 table.attributes { width: 100% }
237 table.attributes th { vertical-align: top; text-align: left; }
237 table.attributes th { vertical-align: top; text-align: left; }
238 table.attributes td { vertical-align: top; }
238 table.attributes td { vertical-align: top; }
239
239
240 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
240 table.boards a.board, h3.comments { background: url(../images/comment.png) no-repeat 0% 50%; padding-left: 20px; }
241
241
242 table.query-columns {
242 table.query-columns {
243 border-collapse: collapse;
243 border-collapse: collapse;
244 border: 0;
244 border: 0;
245 }
245 }
246
246
247 table.query-columns td.buttons {
247 table.query-columns td.buttons {
248 vertical-align: middle;
248 vertical-align: middle;
249 text-align: center;
249 text-align: center;
250 }
250 }
251
251
252 td.center {text-align:center;}
252 td.center {text-align:center;}
253
253
254 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
254 h3.version { background: url(../images/package.png) no-repeat 0% 50%; padding-left: 20px; }
255
255
256 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
256 div.issues h3 { background: url(../images/ticket.png) no-repeat 0% 50%; padding-left: 20px; }
257 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
257 div.members h3 { background: url(../images/group.png) no-repeat 0% 50%; padding-left: 20px; }
258 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
258 div.news h3 { background: url(../images/news.png) no-repeat 0% 50%; padding-left: 20px; }
259 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
259 div.projects h3 { background: url(../images/projects.png) no-repeat 0% 50%; padding-left: 20px; }
260
260
261 #watchers ul {margin: 0; padding: 0;}
261 #watchers ul {margin: 0; padding: 0;}
262 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
262 #watchers li {list-style-type:none;margin: 0px 2px 0px 0px; padding: 0px 0px 0px 0px;}
263 #watchers select {width: 95%; display: block;}
263 #watchers select {width: 95%; display: block;}
264 #watchers a.delete {opacity: 0.4;}
264 #watchers a.delete {opacity: 0.4;}
265 #watchers a.delete:hover {opacity: 1;}
265 #watchers a.delete:hover {opacity: 1;}
266 #watchers img.gravatar {margin: 0 4px 2px 0;}
266 #watchers img.gravatar {margin: 0 4px 2px 0;}
267
267
268 span#watchers_inputs {overflow:auto; display:block;}
269 span.search_for_watchers {display:block;}
270 span.search_for_watchers, span.add_attachment {font-size:80%; line-height:2.5em;}
271 span.search_for_watchers a, span.add_attachment a {padding-left:16px; background: url(../images/bullet_add.png) no-repeat 0 50%; }
272
273
268 .highlight { background-color: #FCFD8D;}
274 .highlight { background-color: #FCFD8D;}
269 .highlight.token-1 { background-color: #faa;}
275 .highlight.token-1 { background-color: #faa;}
270 .highlight.token-2 { background-color: #afa;}
276 .highlight.token-2 { background-color: #afa;}
271 .highlight.token-3 { background-color: #aaf;}
277 .highlight.token-3 { background-color: #aaf;}
272
278
273 .box{
279 .box{
274 padding:6px;
280 padding:6px;
275 margin-bottom: 10px;
281 margin-bottom: 10px;
276 background-color:#f6f6f6;
282 background-color:#f6f6f6;
277 color:#505050;
283 color:#505050;
278 line-height:1.5em;
284 line-height:1.5em;
279 border: 1px solid #e4e4e4;
285 border: 1px solid #e4e4e4;
280 }
286 }
281
287
282 div.square {
288 div.square {
283 border: 1px solid #999;
289 border: 1px solid #999;
284 float: left;
290 float: left;
285 margin: .3em .4em 0 .4em;
291 margin: .3em .4em 0 .4em;
286 overflow: hidden;
292 overflow: hidden;
287 width: .6em; height: .6em;
293 width: .6em; height: .6em;
288 }
294 }
289 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
295 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
290 .contextual input, .contextual select {font-size:0.9em;}
296 .contextual input, .contextual select {font-size:0.9em;}
291 .message .contextual { margin-top: 0; }
297 .message .contextual { margin-top: 0; }
292
298
293 .splitcontent {overflow:auto;}
299 .splitcontent {overflow:auto;}
294 .splitcontentleft{float:left; width:49%;}
300 .splitcontentleft{float:left; width:49%;}
295 .splitcontentright{float:right; width:49%;}
301 .splitcontentright{float:right; width:49%;}
296 form {display: inline;}
302 form {display: inline;}
297 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
303 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
298 fieldset {border: 1px solid #e4e4e4; margin:0;}
304 fieldset {border: 1px solid #e4e4e4; margin:0;}
299 legend {color: #484848;}
305 legend {color: #484848;}
300 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
306 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
301 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
307 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
302 blockquote blockquote { margin-left: 0;}
308 blockquote blockquote { margin-left: 0;}
303 acronym { border-bottom: 1px dotted; cursor: help; }
309 acronym { border-bottom: 1px dotted; cursor: help; }
304 textarea.wiki-edit { width: 99%; }
310 textarea.wiki-edit { width: 99%; }
305 li p {margin-top: 0;}
311 li p {margin-top: 0;}
306 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
312 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
307 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
313 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
308 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
314 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
309 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
315 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
310
316
311 div.issue div.subject div div { padding-left: 16px; }
317 div.issue div.subject div div { padding-left: 16px; }
312 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
318 div.issue div.subject p {margin: 0; margin-bottom: 0.1em; font-size: 90%; color: #999;}
313 div.issue div.subject>div>p { margin-top: 0.5em; }
319 div.issue div.subject>div>p { margin-top: 0.5em; }
314 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
320 div.issue div.subject h3 {margin: 0; margin-bottom: 0.1em;}
315 div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px; -moz-border-radius: 2px;}
321 div.issue span.private { position:relative; bottom: 2px; text-transform: uppercase; background: #d22; color: #fff; font-weight:bold; padding: 0px 2px 0px 2px; font-size: 60%; margin-right: 2px; border-radius: 2px; -moz-border-radius: 2px;}
316 div.issue .next-prev-links {color:#999;}
322 div.issue .next-prev-links {color:#999;}
317 div.issue table.attributes th {width:22%;}
323 div.issue table.attributes th {width:22%;}
318 div.issue table.attributes td {width:28%;}
324 div.issue table.attributes td {width:28%;}
319
325
320 #issue_tree table.issues, #relations table.issues { border: 0; }
326 #issue_tree table.issues, #relations table.issues { border: 0; }
321 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
327 #issue_tree td.checkbox, #relations td.checkbox {display:none;}
322 #relations td.buttons {padding:0;}
328 #relations td.buttons {padding:0;}
323
329
324 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
330 fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; }
325 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
331 fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; }
326 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
332 fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); }
327
333
328 fieldset#date-range p { margin: 2px 0 2px 0; }
334 fieldset#date-range p { margin: 2px 0 2px 0; }
329 fieldset#filters table { border-collapse: collapse; }
335 fieldset#filters table { border-collapse: collapse; }
330 fieldset#filters table td { padding: 0; vertical-align: middle; }
336 fieldset#filters table td { padding: 0; vertical-align: middle; }
331 fieldset#filters tr.filter { height: 2em; }
337 fieldset#filters tr.filter { height: 2em; }
332 fieldset#filters td.field { width:200px; }
338 fieldset#filters td.field { width:200px; }
333 fieldset#filters td.operator { width:170px; }
339 fieldset#filters td.operator { width:170px; }
334 fieldset#filters td.values { white-space:nowrap; }
340 fieldset#filters td.values { white-space:nowrap; }
335 fieldset#filters td.values select {min-width:130px;}
341 fieldset#filters td.values select {min-width:130px;}
336 fieldset#filters td.values img { vertical-align: middle; margin-left:1px; }
342 fieldset#filters td.values img { vertical-align: middle; margin-left:1px; }
337 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
343 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
338 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
344 .buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; }
339
345
340 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
346 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
341 div#issue-changesets div.changeset { padding: 4px;}
347 div#issue-changesets div.changeset { padding: 4px;}
342 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
348 div#issue-changesets div.changeset { border-bottom: 1px solid #ddd; }
343 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
349 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
344
350
345 div#activity dl, #search-results { margin-left: 2em; }
351 div#activity dl, #search-results { margin-left: 2em; }
346 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
352 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
347 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
353 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
348 div#activity dt.me .time { border-bottom: 1px solid #999; }
354 div#activity dt.me .time { border-bottom: 1px solid #999; }
349 div#activity dt .time { color: #777; font-size: 80%; }
355 div#activity dt .time { color: #777; font-size: 80%; }
350 div#activity dd .description, #search-results dd .description { font-style: italic; }
356 div#activity dd .description, #search-results dd .description { font-style: italic; }
351 div#activity span.project:after, #search-results span.project:after { content: " -"; }
357 div#activity span.project:after, #search-results span.project:after { content: " -"; }
352 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
358 div#activity dd span.description, #search-results dd span.description { display:block; color: #808080; }
353
359
354 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
360 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
355
361
356 div#search-results-counts {float:right;}
362 div#search-results-counts {float:right;}
357 div#search-results-counts ul { margin-top: 0.5em; }
363 div#search-results-counts ul { margin-top: 0.5em; }
358 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
364 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
359
365
360 dt.issue { background-image: url(../images/ticket.png); }
366 dt.issue { background-image: url(../images/ticket.png); }
361 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
367 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
362 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
368 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
363 dt.issue-note { background-image: url(../images/ticket_note.png); }
369 dt.issue-note { background-image: url(../images/ticket_note.png); }
364 dt.changeset { background-image: url(../images/changeset.png); }
370 dt.changeset { background-image: url(../images/changeset.png); }
365 dt.news { background-image: url(../images/news.png); }
371 dt.news { background-image: url(../images/news.png); }
366 dt.message { background-image: url(../images/message.png); }
372 dt.message { background-image: url(../images/message.png); }
367 dt.reply { background-image: url(../images/comments.png); }
373 dt.reply { background-image: url(../images/comments.png); }
368 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
374 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
369 dt.attachment { background-image: url(../images/attachment.png); }
375 dt.attachment { background-image: url(../images/attachment.png); }
370 dt.document { background-image: url(../images/document.png); }
376 dt.document { background-image: url(../images/document.png); }
371 dt.project { background-image: url(../images/projects.png); }
377 dt.project { background-image: url(../images/projects.png); }
372 dt.time-entry { background-image: url(../images/time.png); }
378 dt.time-entry { background-image: url(../images/time.png); }
373
379
374 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
380 #search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); }
375
381
376 div#roadmap .related-issues { margin-bottom: 1em; }
382 div#roadmap .related-issues { margin-bottom: 1em; }
377 div#roadmap .related-issues td.checkbox { display: none; }
383 div#roadmap .related-issues td.checkbox { display: none; }
378 div#roadmap .wiki h1:first-child { display: none; }
384 div#roadmap .wiki h1:first-child { display: none; }
379 div#roadmap .wiki h1 { font-size: 120%; }
385 div#roadmap .wiki h1 { font-size: 120%; }
380 div#roadmap .wiki h2 { font-size: 110%; }
386 div#roadmap .wiki h2 { font-size: 110%; }
381 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
387 body.controller-versions.action-show div#roadmap .related-issues {width:70%;}
382
388
383 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
389 div#version-summary { float:right; width:28%; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
384 div#version-summary fieldset { margin-bottom: 1em; }
390 div#version-summary fieldset { margin-bottom: 1em; }
385 div#version-summary fieldset.time-tracking table { width:100%; }
391 div#version-summary fieldset.time-tracking table { width:100%; }
386 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
392 div#version-summary th, div#version-summary td.total-hours { text-align: right; }
387
393
388 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
394 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
389 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
395 table#time-report tbody tr.subtotal { font-style: italic; color:#777;}
390 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
396 table#time-report tbody tr.subtotal td.hours { color:#b0b0b0; }
391 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
397 table#time-report tbody tr.total { font-weight: bold; background-color:#EEEEEE; border-top:1px solid #e4e4e4;}
392 table#time-report .hours-dec { font-size: 0.9em; }
398 table#time-report .hours-dec { font-size: 0.9em; }
393
399
394 div.wiki-page .contextual a {opacity: 0.4}
400 div.wiki-page .contextual a {opacity: 0.4}
395 div.wiki-page .contextual a:hover {opacity: 1}
401 div.wiki-page .contextual a:hover {opacity: 1}
396
402
397 form .attributes select { width: 60%; }
403 form .attributes select { width: 60%; }
398 input#issue_subject { width: 99%; }
404 input#issue_subject { width: 99%; }
399 select#issue_done_ratio { width: 95px; }
405 select#issue_done_ratio { width: 95px; }
400
406
401 ul.projects { margin: 0; padding-left: 1em; }
407 ul.projects { margin: 0; padding-left: 1em; }
402 ul.projects.root { margin: 0; padding: 0; }
408 ul.projects.root { margin: 0; padding: 0; }
403 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
409 ul.projects ul.projects { border-left: 3px solid #e0e0e0; }
404 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
410 ul.projects li.root { list-style-type:none; margin-bottom: 1em; }
405 ul.projects li.child { list-style-type:none; margin-top: 1em;}
411 ul.projects li.child { list-style-type:none; margin-top: 1em;}
406 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
412 ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; }
407 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
413 .my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; }
408
414
409 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
415 #tracker_project_ids ul { margin: 0; padding-left: 1em; }
410 #tracker_project_ids li { list-style-type:none; }
416 #tracker_project_ids li { list-style-type:none; }
411
417
412 #related-issues li img {vertical-align:middle;}
418 #related-issues li img {vertical-align:middle;}
413
419
414 ul.properties {padding:0; font-size: 0.9em; color: #777;}
420 ul.properties {padding:0; font-size: 0.9em; color: #777;}
415 ul.properties li {list-style-type:none;}
421 ul.properties li {list-style-type:none;}
416 ul.properties li span {font-style:italic;}
422 ul.properties li span {font-style:italic;}
417
423
418 .total-hours { font-size: 110%; font-weight: bold; }
424 .total-hours { font-size: 110%; font-weight: bold; }
419 .total-hours span.hours-int { font-size: 120%; }
425 .total-hours span.hours-int { font-size: 120%; }
420
426
421 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
427 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
422 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
428 #user_login, #user_firstname, #user_lastname, #user_mail, #my_account_form select, #user_form select, #user_identity_url { width: 90%; }
423
429
424 #workflow_copy_form select { width: 200px; }
430 #workflow_copy_form select { width: 200px; }
425
431
426 textarea#custom_field_possible_values {width: 99%}
432 textarea#custom_field_possible_values {width: 99%}
427 input#content_comments {width: 99%}
433 input#content_comments {width: 99%}
428
434
429 .pagination {font-size: 90%}
435 .pagination {font-size: 90%}
430 p.pagination {margin-top:8px;}
436 p.pagination {margin-top:8px;}
431
437
432 /***** Tabular forms ******/
438 /***** Tabular forms ******/
433 .tabular p{
439 .tabular p{
434 margin: 0;
440 margin: 0;
435 padding: 3px 0 3px 0;
441 padding: 3px 0 3px 0;
436 padding-left: 180px; /* width of left column containing the label elements */
442 padding-left: 180px; /* width of left column containing the label elements */
437 min-height: 1.8em;
443 min-height: 1.8em;
438 clear:left;
444 clear:left;
439 }
445 }
440
446
441 html>body .tabular p {overflow:hidden;}
447 html>body .tabular p {overflow:hidden;}
442
448
443 .tabular label{
449 .tabular label{
444 font-weight: bold;
450 font-weight: bold;
445 float: left;
451 float: left;
446 text-align: right;
452 text-align: right;
447 /* width of left column */
453 /* width of left column */
448 margin-left: -180px;
454 margin-left: -180px;
449 /* width of labels. Should be smaller than left column to create some right margin */
455 /* width of labels. Should be smaller than left column to create some right margin */
450 width: 175px;
456 width: 175px;
451 }
457 }
452
458
453 .tabular label.floating{
459 .tabular label.floating{
454 font-weight: normal;
460 font-weight: normal;
455 margin-left: 0px;
461 margin-left: 0px;
456 text-align: left;
462 text-align: left;
457 width: 270px;
463 width: 270px;
458 }
464 }
459
465
460 .tabular label.block{
466 .tabular label.block{
461 font-weight: normal;
467 font-weight: normal;
462 margin-left: 0px !important;
468 margin-left: 0px !important;
463 text-align: left;
469 text-align: left;
464 float: none;
470 float: none;
465 display: block;
471 display: block;
466 width: auto;
472 width: auto;
467 }
473 }
468
474
469 .tabular label.inline{
475 .tabular label.inline{
470 float:none;
476 float:none;
471 margin-left: 5px !important;
477 margin-left: 5px !important;
472 width: auto;
478 width: auto;
473 }
479 }
474
480
475 label.no-css {
481 label.no-css {
476 font-weight: inherit;
482 font-weight: inherit;
477 float:none;
483 float:none;
478 text-align:left;
484 text-align:left;
479 margin-left:0px;
485 margin-left:0px;
480 width:auto;
486 width:auto;
481 }
487 }
482 input#time_entry_comments { width: 90%;}
488 input#time_entry_comments { width: 90%;}
483
489
484 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
490 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
485
491
486 .tabular.settings p{ padding-left: 300px; }
492 .tabular.settings p{ padding-left: 300px; }
487 .tabular.settings label{ margin-left: -300px; width: 295px; }
493 .tabular.settings label{ margin-left: -300px; width: 295px; }
488 .tabular.settings textarea { width: 99%; }
494 .tabular.settings textarea { width: 99%; }
489
495
490 .settings.enabled_scm table {width:100%}
496 .settings.enabled_scm table {width:100%}
491 .settings.enabled_scm td.scm_name{ font-weight: bold; }
497 .settings.enabled_scm td.scm_name{ font-weight: bold; }
492
498
493 fieldset.settings label { display: block; }
499 fieldset.settings label { display: block; }
494 fieldset#notified_events .parent { padding-left: 20px; }
500 fieldset#notified_events .parent { padding-left: 20px; }
495
501
496 .required {color: #bb0000;}
502 .required {color: #bb0000;}
497 .summary {font-style: italic;}
503 .summary {font-style: italic;}
498
504
499 #attachments_fields input.description {margin-left: 8px; width:340px;}
505 #attachments_fields input.description {margin-left: 8px; width:340px;}
500 #attachments_fields span {display:block; white-space:nowrap;}
506 #attachments_fields span {display:block; white-space:nowrap;}
501 #attachments_fields img {vertical-align: middle;}
507 #attachments_fields img {vertical-align: middle;}
502
508
503 div.attachments { margin-top: 12px; }
509 div.attachments { margin-top: 12px; }
504 div.attachments p { margin:4px 0 2px 0; }
510 div.attachments p { margin:4px 0 2px 0; }
505 div.attachments img { vertical-align: middle; }
511 div.attachments img { vertical-align: middle; }
506 div.attachments span.author { font-size: 0.9em; color: #888; }
512 div.attachments span.author { font-size: 0.9em; color: #888; }
507
513
508 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
514 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
509 .other-formats span + span:before { content: "| "; }
515 .other-formats span + span:before { content: "| "; }
510
516
511 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
517 a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
512
518
513 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
519 em.info {font-style:normal;font-size:90%;color:#888;display:block;}
514 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
520 em.info.error {padding-left:20px; background:url(../images/exclamation.png) no-repeat 0 50%;}
515
521
516 /* Project members tab */
522 /* Project members tab */
517 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
523 div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% }
518 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
524 div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% }
519 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
525 div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; }
520 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
526 div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; }
521 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
527 div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; }
522 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
528 div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; }
523
529
524 #users_for_watcher {height: 200px; overflow:auto;}
530 #users_for_watcher {height: 200px; overflow:auto;}
525 #users_for_watcher label {display: block;}
531 #users_for_watcher label {display: block;}
526
532
527 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
533 table.members td.group { padding-left: 20px; background: url(../images/group.png) no-repeat 0% 50%; }
528
534
529 input#principal_search, input#user_search {width:100%}
535 input#principal_search, input#user_search {width:100%}
530 input#principal_search, input#user_search {
536 input#principal_search, input#user_search {
531 background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
537 background: url(../images/magnifier.png) no-repeat 2px 50%; padding-left:20px;
532 border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%;
538 border:1px solid #9EB1C2; border-radius:3px; height:1.5em; width:95%;
533 }
539 }
534 input#principal_search.ajax-loading, input#user_search.ajax-loading {
540 input#principal_search.ajax-loading, input#user_search.ajax-loading {
535 background-image: url(../images/loading.gif);
541 background-image: url(../images/loading.gif);
536 }
542 }
537
543
538 * html div#tab-content-members fieldset div { height: 450px; }
544 * html div#tab-content-members fieldset div { height: 450px; }
539
545
540 /***** Flash & error messages ****/
546 /***** Flash & error messages ****/
541 #errorExplanation, div.flash, .nodata, .warning, .conflict {
547 #errorExplanation, div.flash, .nodata, .warning, .conflict {
542 padding: 4px 4px 4px 30px;
548 padding: 4px 4px 4px 30px;
543 margin-bottom: 12px;
549 margin-bottom: 12px;
544 font-size: 1.1em;
550 font-size: 1.1em;
545 border: 2px solid;
551 border: 2px solid;
546 }
552 }
547
553
548 div.flash {margin-top: 8px;}
554 div.flash {margin-top: 8px;}
549
555
550 div.flash.error, #errorExplanation {
556 div.flash.error, #errorExplanation {
551 background: url(../images/exclamation.png) 8px 50% no-repeat;
557 background: url(../images/exclamation.png) 8px 50% no-repeat;
552 background-color: #ffe3e3;
558 background-color: #ffe3e3;
553 border-color: #dd0000;
559 border-color: #dd0000;
554 color: #880000;
560 color: #880000;
555 }
561 }
556
562
557 div.flash.notice {
563 div.flash.notice {
558 background: url(../images/true.png) 8px 5px no-repeat;
564 background: url(../images/true.png) 8px 5px no-repeat;
559 background-color: #dfffdf;
565 background-color: #dfffdf;
560 border-color: #9fcf9f;
566 border-color: #9fcf9f;
561 color: #005f00;
567 color: #005f00;
562 }
568 }
563
569
564 div.flash.warning, .conflict {
570 div.flash.warning, .conflict {
565 background: url(../images/warning.png) 8px 5px no-repeat;
571 background: url(../images/warning.png) 8px 5px no-repeat;
566 background-color: #FFEBC1;
572 background-color: #FFEBC1;
567 border-color: #FDBF3B;
573 border-color: #FDBF3B;
568 color: #A6750C;
574 color: #A6750C;
569 text-align: left;
575 text-align: left;
570 }
576 }
571
577
572 .nodata, .warning {
578 .nodata, .warning {
573 text-align: center;
579 text-align: center;
574 background-color: #FFEBC1;
580 background-color: #FFEBC1;
575 border-color: #FDBF3B;
581 border-color: #FDBF3B;
576 color: #A6750C;
582 color: #A6750C;
577 }
583 }
578
584
579 #errorExplanation ul { font-size: 0.9em;}
585 #errorExplanation ul { font-size: 0.9em;}
580 #errorExplanation h2, #errorExplanation p { display: none; }
586 #errorExplanation h2, #errorExplanation p { display: none; }
581
587
582 .conflict-details {font-size:80%;}
588 .conflict-details {font-size:80%;}
583
589
584 /***** Ajax indicator ******/
590 /***** Ajax indicator ******/
585 #ajax-indicator {
591 #ajax-indicator {
586 position: absolute; /* fixed not supported by IE */
592 position: absolute; /* fixed not supported by IE */
587 background-color:#eee;
593 background-color:#eee;
588 border: 1px solid #bbb;
594 border: 1px solid #bbb;
589 top:35%;
595 top:35%;
590 left:40%;
596 left:40%;
591 width:20%;
597 width:20%;
592 font-weight:bold;
598 font-weight:bold;
593 text-align:center;
599 text-align:center;
594 padding:0.6em;
600 padding:0.6em;
595 z-index:100;
601 z-index:100;
596 opacity: 0.5;
602 opacity: 0.5;
597 }
603 }
598
604
599 html>body #ajax-indicator { position: fixed; }
605 html>body #ajax-indicator { position: fixed; }
600
606
601 #ajax-indicator span {
607 #ajax-indicator span {
602 background-position: 0% 40%;
608 background-position: 0% 40%;
603 background-repeat: no-repeat;
609 background-repeat: no-repeat;
604 background-image: url(../images/loading.gif);
610 background-image: url(../images/loading.gif);
605 padding-left: 26px;
611 padding-left: 26px;
606 vertical-align: bottom;
612 vertical-align: bottom;
607 }
613 }
608
614
609 /***** Calendar *****/
615 /***** Calendar *****/
610 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
616 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
611 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
617 table.cal thead th {width: 14%; background-color:#EEEEEE; padding: 4px; }
612 table.cal thead th.week-number {width: auto;}
618 table.cal thead th.week-number {width: auto;}
613 table.cal tbody tr {height: 100px;}
619 table.cal tbody tr {height: 100px;}
614 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
620 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
615 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
621 table.cal td.week-number { background-color:#EEEEEE; padding: 4px; border:none; font-size: 1em;}
616 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
622 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
617 table.cal td.odd p.day-num {color: #bbb;}
623 table.cal td.odd p.day-num {color: #bbb;}
618 table.cal td.today {background:#ffffdd;}
624 table.cal td.today {background:#ffffdd;}
619 table.cal td.today p.day-num {font-weight: bold;}
625 table.cal td.today p.day-num {font-weight: bold;}
620 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
626 table.cal .starting a, p.cal.legend .starting {background: url(../images/bullet_go.png) no-repeat -1px -2px; padding-left:16px;}
621 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
627 table.cal .ending a, p.cal.legend .ending {background: url(../images/bullet_end.png) no-repeat -1px -2px; padding-left:16px;}
622 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
628 table.cal .starting.ending a, p.cal.legend .starting.ending {background: url(../images/bullet_diamond.png) no-repeat -1px -2px; padding-left:16px;}
623 p.cal.legend span {display:block;}
629 p.cal.legend span {display:block;}
624
630
625 /***** Tooltips ******/
631 /***** Tooltips ******/
626 .tooltip{position:relative;z-index:24;}
632 .tooltip{position:relative;z-index:24;}
627 .tooltip:hover{z-index:25;color:#000;}
633 .tooltip:hover{z-index:25;color:#000;}
628 .tooltip span.tip{display: none; text-align:left;}
634 .tooltip span.tip{display: none; text-align:left;}
629
635
630 div.tooltip:hover span.tip{
636 div.tooltip:hover span.tip{
631 display:block;
637 display:block;
632 position:absolute;
638 position:absolute;
633 top:12px; left:24px; width:270px;
639 top:12px; left:24px; width:270px;
634 border:1px solid #555;
640 border:1px solid #555;
635 background-color:#fff;
641 background-color:#fff;
636 padding: 4px;
642 padding: 4px;
637 font-size: 0.8em;
643 font-size: 0.8em;
638 color:#505050;
644 color:#505050;
639 }
645 }
640
646
641 /***** Progress bar *****/
647 /***** Progress bar *****/
642 table.progress {
648 table.progress {
643 border-collapse: collapse;
649 border-collapse: collapse;
644 border-spacing: 0pt;
650 border-spacing: 0pt;
645 empty-cells: show;
651 empty-cells: show;
646 text-align: center;
652 text-align: center;
647 float:left;
653 float:left;
648 margin: 1px 6px 1px 0px;
654 margin: 1px 6px 1px 0px;
649 }
655 }
650
656
651 table.progress td { height: 1em; }
657 table.progress td { height: 1em; }
652 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
658 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
653 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
659 table.progress td.done { background: #D3EDD3 none repeat scroll 0%; }
654 table.progress td.todo { background: #eee none repeat scroll 0%; }
660 table.progress td.todo { background: #eee none repeat scroll 0%; }
655 p.pourcent {font-size: 80%;}
661 p.pourcent {font-size: 80%;}
656 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
662 p.progress-info {clear: left; font-size: 80%; margin-top:-4px; color:#777;}
657
663
658 #roadmap table.progress td { height: 1.2em; }
664 #roadmap table.progress td { height: 1.2em; }
659 /***** Tabs *****/
665 /***** Tabs *****/
660 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
666 #content .tabs {height: 2.6em; margin-bottom:1.2em; position:relative; overflow:hidden;}
661 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
667 #content .tabs ul {margin:0; position:absolute; bottom:0; padding-left:0.5em; width: 2000px; border-bottom: 1px solid #bbbbbb;}
662 #content .tabs ul li {
668 #content .tabs ul li {
663 float:left;
669 float:left;
664 list-style-type:none;
670 list-style-type:none;
665 white-space:nowrap;
671 white-space:nowrap;
666 margin-right:4px;
672 margin-right:4px;
667 background:#fff;
673 background:#fff;
668 position:relative;
674 position:relative;
669 margin-bottom:-1px;
675 margin-bottom:-1px;
670 }
676 }
671 #content .tabs ul li a{
677 #content .tabs ul li a{
672 display:block;
678 display:block;
673 font-size: 0.9em;
679 font-size: 0.9em;
674 text-decoration:none;
680 text-decoration:none;
675 line-height:1.3em;
681 line-height:1.3em;
676 padding:4px 6px 4px 6px;
682 padding:4px 6px 4px 6px;
677 border: 1px solid #ccc;
683 border: 1px solid #ccc;
678 border-bottom: 1px solid #bbbbbb;
684 border-bottom: 1px solid #bbbbbb;
679 background-color: #f6f6f6;
685 background-color: #f6f6f6;
680 color:#999;
686 color:#999;
681 font-weight:bold;
687 font-weight:bold;
682 border-top-left-radius:3px;
688 border-top-left-radius:3px;
683 border-top-right-radius:3px;
689 border-top-right-radius:3px;
684 }
690 }
685
691
686 #content .tabs ul li a:hover {
692 #content .tabs ul li a:hover {
687 background-color: #ffffdd;
693 background-color: #ffffdd;
688 text-decoration:none;
694 text-decoration:none;
689 }
695 }
690
696
691 #content .tabs ul li a.selected {
697 #content .tabs ul li a.selected {
692 background-color: #fff;
698 background-color: #fff;
693 border: 1px solid #bbbbbb;
699 border: 1px solid #bbbbbb;
694 border-bottom: 1px solid #fff;
700 border-bottom: 1px solid #fff;
695 color:#444;
701 color:#444;
696 }
702 }
697
703
698 #content .tabs ul li a.selected:hover {
704 #content .tabs ul li a.selected:hover {
699 background-color: #fff;
705 background-color: #fff;
700 }
706 }
701
707
702 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
708 div.tabs-buttons { position:absolute; right: 0; width: 48px; height: 24px; background: white; bottom: 0; border-bottom: 1px solid #bbbbbb; }
703
709
704 button.tab-left, button.tab-right {
710 button.tab-left, button.tab-right {
705 font-size: 0.9em;
711 font-size: 0.9em;
706 cursor: pointer;
712 cursor: pointer;
707 height:24px;
713 height:24px;
708 border: 1px solid #ccc;
714 border: 1px solid #ccc;
709 border-bottom: 1px solid #bbbbbb;
715 border-bottom: 1px solid #bbbbbb;
710 position:absolute;
716 position:absolute;
711 padding:4px;
717 padding:4px;
712 width: 20px;
718 width: 20px;
713 bottom: -1px;
719 bottom: -1px;
714 }
720 }
715
721
716 button.tab-left {
722 button.tab-left {
717 right: 20px;
723 right: 20px;
718 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
724 background: #eeeeee url(../images/bullet_arrow_left.png) no-repeat 50% 50%;
719 border-top-left-radius:3px;
725 border-top-left-radius:3px;
720 }
726 }
721
727
722 button.tab-right {
728 button.tab-right {
723 right: 0;
729 right: 0;
724 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
730 background: #eeeeee url(../images/bullet_arrow_right.png) no-repeat 50% 50%;
725 border-top-right-radius:3px;
731 border-top-right-radius:3px;
726 }
732 }
727
733
728 /***** Auto-complete *****/
734 /***** Auto-complete *****/
729 div.autocomplete {
735 div.autocomplete {
730 position:absolute;
736 position:absolute;
731 width:400px;
737 width:400px;
732 margin:0;
738 margin:0;
733 padding:0;
739 padding:0;
734 }
740 }
735 div.autocomplete ul {
741 div.autocomplete ul {
736 list-style-type:none;
742 list-style-type:none;
737 margin:0;
743 margin:0;
738 padding:0;
744 padding:0;
739 }
745 }
740 div.autocomplete ul li {
746 div.autocomplete ul li {
741 list-style-type:none;
747 list-style-type:none;
742 display:block;
748 display:block;
743 margin:-1px 0 0 0;
749 margin:-1px 0 0 0;
744 padding:2px;
750 padding:2px;
745 cursor:pointer;
751 cursor:pointer;
746 font-size: 90%;
752 font-size: 90%;
747 border: 1px solid #ccc;
753 border: 1px solid #ccc;
748 border-left: 1px solid #ccc;
754 border-left: 1px solid #ccc;
749 border-right: 1px solid #ccc;
755 border-right: 1px solid #ccc;
750 background-color:white;
756 background-color:white;
751 }
757 }
752 div.autocomplete ul li.selected { background-color: #ffb;}
758 div.autocomplete ul li.selected { background-color: #ffb;}
753 div.autocomplete ul li span.informal {
759 div.autocomplete ul li span.informal {
754 font-size: 80%;
760 font-size: 80%;
755 color: #aaa;
761 color: #aaa;
756 }
762 }
757
763
758 #parent_issue_candidates ul li {width: 500px;}
764 #parent_issue_candidates ul li {width: 500px;}
759 #related_issue_candidates ul li {width: 500px;}
765 #related_issue_candidates ul li {width: 500px;}
760
766
761 /***** Diff *****/
767 /***** Diff *****/
762 .diff_out { background: #fcc; }
768 .diff_out { background: #fcc; }
763 .diff_out span { background: #faa; }
769 .diff_out span { background: #faa; }
764 .diff_in { background: #cfc; }
770 .diff_in { background: #cfc; }
765 .diff_in span { background: #afa; }
771 .diff_in span { background: #afa; }
766
772
767 .text-diff {
773 .text-diff {
768 padding: 1em;
774 padding: 1em;
769 background-color:#f6f6f6;
775 background-color:#f6f6f6;
770 color:#505050;
776 color:#505050;
771 border: 1px solid #e4e4e4;
777 border: 1px solid #e4e4e4;
772 }
778 }
773
779
774 /***** Wiki *****/
780 /***** Wiki *****/
775 div.wiki table {
781 div.wiki table {
776 border-collapse: collapse;
782 border-collapse: collapse;
777 margin-bottom: 1em;
783 margin-bottom: 1em;
778 }
784 }
779
785
780 div.wiki table, div.wiki td, div.wiki th {
786 div.wiki table, div.wiki td, div.wiki th {
781 border: 1px solid #bbb;
787 border: 1px solid #bbb;
782 padding: 4px;
788 padding: 4px;
783 }
789 }
784
790
785 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
791 div.wiki .noborder, div.wiki .noborder td, div.wiki .noborder th {border:0;}
786
792
787 div.wiki .external {
793 div.wiki .external {
788 background-position: 0% 60%;
794 background-position: 0% 60%;
789 background-repeat: no-repeat;
795 background-repeat: no-repeat;
790 padding-left: 12px;
796 padding-left: 12px;
791 background-image: url(../images/external.png);
797 background-image: url(../images/external.png);
792 }
798 }
793
799
794 div.wiki a.new {
800 div.wiki a.new {
795 color: #b73535;
801 color: #b73535;
796 }
802 }
797
803
798 div.wiki ul, div.wiki ol {margin-bottom:1em;}
804 div.wiki ul, div.wiki ol {margin-bottom:1em;}
799
805
800 div.wiki pre {
806 div.wiki pre {
801 margin: 1em 1em 1em 1.6em;
807 margin: 1em 1em 1em 1.6em;
802 padding: 2px 2px 2px 0;
808 padding: 2px 2px 2px 0;
803 background-color: #fafafa;
809 background-color: #fafafa;
804 border: 1px solid #dadada;
810 border: 1px solid #dadada;
805 width:auto;
811 width:auto;
806 overflow-x: auto;
812 overflow-x: auto;
807 overflow-y: hidden;
813 overflow-y: hidden;
808 }
814 }
809
815
810 div.wiki ul.toc {
816 div.wiki ul.toc {
811 background-color: #ffffdd;
817 background-color: #ffffdd;
812 border: 1px solid #e4e4e4;
818 border: 1px solid #e4e4e4;
813 padding: 4px;
819 padding: 4px;
814 line-height: 1.2em;
820 line-height: 1.2em;
815 margin-bottom: 12px;
821 margin-bottom: 12px;
816 margin-right: 12px;
822 margin-right: 12px;
817 margin-left: 0;
823 margin-left: 0;
818 display: table
824 display: table
819 }
825 }
820 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
826 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
821
827
822 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
828 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
823 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
829 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
824 div.wiki ul.toc ul { margin: 0; padding: 0; }
830 div.wiki ul.toc ul { margin: 0; padding: 0; }
825 div.wiki ul.toc li { list-style-type:none; margin: 0;}
831 div.wiki ul.toc li { list-style-type:none; margin: 0;}
826 div.wiki ul.toc li li { margin-left: 1.5em; }
832 div.wiki ul.toc li li { margin-left: 1.5em; }
827 div.wiki ul.toc li li li { font-size: 0.8em; }
833 div.wiki ul.toc li li li { font-size: 0.8em; }
828
834
829 div.wiki ul.toc a {
835 div.wiki ul.toc a {
830 font-size: 0.9em;
836 font-size: 0.9em;
831 font-weight: normal;
837 font-weight: normal;
832 text-decoration: none;
838 text-decoration: none;
833 color: #606060;
839 color: #606060;
834 }
840 }
835 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
841 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
836
842
837 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
843 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
838 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
844 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
839 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
845 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
840
846
841 div.wiki img { vertical-align: middle; }
847 div.wiki img { vertical-align: middle; }
842
848
843 /***** My page layout *****/
849 /***** My page layout *****/
844 .block-receiver {
850 .block-receiver {
845 border:1px dashed #c0c0c0;
851 border:1px dashed #c0c0c0;
846 margin-bottom: 20px;
852 margin-bottom: 20px;
847 padding: 15px 0 15px 0;
853 padding: 15px 0 15px 0;
848 }
854 }
849
855
850 .mypage-box {
856 .mypage-box {
851 margin:0 0 20px 0;
857 margin:0 0 20px 0;
852 color:#505050;
858 color:#505050;
853 line-height:1.5em;
859 line-height:1.5em;
854 }
860 }
855
861
856 .handle {
862 .handle {
857 cursor: move;
863 cursor: move;
858 }
864 }
859
865
860 a.close-icon {
866 a.close-icon {
861 display:block;
867 display:block;
862 margin-top:3px;
868 margin-top:3px;
863 overflow:hidden;
869 overflow:hidden;
864 width:12px;
870 width:12px;
865 height:12px;
871 height:12px;
866 background-repeat: no-repeat;
872 background-repeat: no-repeat;
867 cursor:pointer;
873 cursor:pointer;
868 background-image:url('../images/close.png');
874 background-image:url('../images/close.png');
869 }
875 }
870
876
871 a.close-icon:hover {
877 a.close-icon:hover {
872 background-image:url('../images/close_hl.png');
878 background-image:url('../images/close_hl.png');
873 }
879 }
874
880
875 /***** Gantt chart *****/
881 /***** Gantt chart *****/
876 .gantt_hdr {
882 .gantt_hdr {
877 position:absolute;
883 position:absolute;
878 top:0;
884 top:0;
879 height:16px;
885 height:16px;
880 border-top: 1px solid #c0c0c0;
886 border-top: 1px solid #c0c0c0;
881 border-bottom: 1px solid #c0c0c0;
887 border-bottom: 1px solid #c0c0c0;
882 border-right: 1px solid #c0c0c0;
888 border-right: 1px solid #c0c0c0;
883 text-align: center;
889 text-align: center;
884 overflow: hidden;
890 overflow: hidden;
885 }
891 }
886
892
887 .gantt_subjects { font-size: 0.8em; }
893 .gantt_subjects { font-size: 0.8em; }
888 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
894 .gantt_subjects div { line-height:16px;height:16px;overflow:hidden;white-space:nowrap;text-overflow: ellipsis; }
889
895
890 .task {
896 .task {
891 position: absolute;
897 position: absolute;
892 height:8px;
898 height:8px;
893 font-size:0.8em;
899 font-size:0.8em;
894 color:#888;
900 color:#888;
895 padding:0;
901 padding:0;
896 margin:0;
902 margin:0;
897 line-height:16px;
903 line-height:16px;
898 white-space:nowrap;
904 white-space:nowrap;
899 }
905 }
900
906
901 .task.label {width:100%;}
907 .task.label {width:100%;}
902 .task.label.project, .task.label.version { font-weight: bold; }
908 .task.label.project, .task.label.version { font-weight: bold; }
903
909
904 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
910 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
905 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
911 .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; }
906 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
912 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
907
913
908 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
914 .task_todo.parent { background: #888; border: 1px solid #888; height: 3px;}
909 .task_late.parent, .task_done.parent { height: 3px;}
915 .task_late.parent, .task_done.parent { height: 3px;}
910 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
916 .task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;}
911 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
917 .task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;}
912
918
913 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
919 .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
914 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
920 .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
915 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
921 .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
916 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
922 .version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
917
923
918 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
924 .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;}
919 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
925 .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;}
920 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
926 .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;}
921 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
927 .project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; }
922
928
923 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
929 .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;}
924 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
930 .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;}
925
931
926 /***** Icons *****/
932 /***** Icons *****/
927 .icon {
933 .icon {
928 background-position: 0% 50%;
934 background-position: 0% 50%;
929 background-repeat: no-repeat;
935 background-repeat: no-repeat;
930 padding-left: 20px;
936 padding-left: 20px;
931 padding-top: 2px;
937 padding-top: 2px;
932 padding-bottom: 3px;
938 padding-bottom: 3px;
933 }
939 }
934
940
935 .icon-add { background-image: url(../images/add.png); }
941 .icon-add { background-image: url(../images/add.png); }
936 .icon-edit { background-image: url(../images/edit.png); }
942 .icon-edit { background-image: url(../images/edit.png); }
937 .icon-copy { background-image: url(../images/copy.png); }
943 .icon-copy { background-image: url(../images/copy.png); }
938 .icon-duplicate { background-image: url(../images/duplicate.png); }
944 .icon-duplicate { background-image: url(../images/duplicate.png); }
939 .icon-del { background-image: url(../images/delete.png); }
945 .icon-del { background-image: url(../images/delete.png); }
940 .icon-move { background-image: url(../images/move.png); }
946 .icon-move { background-image: url(../images/move.png); }
941 .icon-save { background-image: url(../images/save.png); }
947 .icon-save { background-image: url(../images/save.png); }
942 .icon-cancel { background-image: url(../images/cancel.png); }
948 .icon-cancel { background-image: url(../images/cancel.png); }
943 .icon-multiple { background-image: url(../images/table_multiple.png); }
949 .icon-multiple { background-image: url(../images/table_multiple.png); }
944 .icon-folder { background-image: url(../images/folder.png); }
950 .icon-folder { background-image: url(../images/folder.png); }
945 .open .icon-folder { background-image: url(../images/folder_open.png); }
951 .open .icon-folder { background-image: url(../images/folder_open.png); }
946 .icon-package { background-image: url(../images/package.png); }
952 .icon-package { background-image: url(../images/package.png); }
947 .icon-user { background-image: url(../images/user.png); }
953 .icon-user { background-image: url(../images/user.png); }
948 .icon-projects { background-image: url(../images/projects.png); }
954 .icon-projects { background-image: url(../images/projects.png); }
949 .icon-help { background-image: url(../images/help.png); }
955 .icon-help { background-image: url(../images/help.png); }
950 .icon-attachment { background-image: url(../images/attachment.png); }
956 .icon-attachment { background-image: url(../images/attachment.png); }
951 .icon-history { background-image: url(../images/history.png); }
957 .icon-history { background-image: url(../images/history.png); }
952 .icon-time { background-image: url(../images/time.png); }
958 .icon-time { background-image: url(../images/time.png); }
953 .icon-time-add { background-image: url(../images/time_add.png); }
959 .icon-time-add { background-image: url(../images/time_add.png); }
954 .icon-stats { background-image: url(../images/stats.png); }
960 .icon-stats { background-image: url(../images/stats.png); }
955 .icon-warning { background-image: url(../images/warning.png); }
961 .icon-warning { background-image: url(../images/warning.png); }
956 .icon-fav { background-image: url(../images/fav.png); }
962 .icon-fav { background-image: url(../images/fav.png); }
957 .icon-fav-off { background-image: url(../images/fav_off.png); }
963 .icon-fav-off { background-image: url(../images/fav_off.png); }
958 .icon-reload { background-image: url(../images/reload.png); }
964 .icon-reload { background-image: url(../images/reload.png); }
959 .icon-lock { background-image: url(../images/locked.png); }
965 .icon-lock { background-image: url(../images/locked.png); }
960 .icon-unlock { background-image: url(../images/unlock.png); }
966 .icon-unlock { background-image: url(../images/unlock.png); }
961 .icon-checked { background-image: url(../images/true.png); }
967 .icon-checked { background-image: url(../images/true.png); }
962 .icon-details { background-image: url(../images/zoom_in.png); }
968 .icon-details { background-image: url(../images/zoom_in.png); }
963 .icon-report { background-image: url(../images/report.png); }
969 .icon-report { background-image: url(../images/report.png); }
964 .icon-comment { background-image: url(../images/comment.png); }
970 .icon-comment { background-image: url(../images/comment.png); }
965 .icon-summary { background-image: url(../images/lightning.png); }
971 .icon-summary { background-image: url(../images/lightning.png); }
966 .icon-server-authentication { background-image: url(../images/server_key.png); }
972 .icon-server-authentication { background-image: url(../images/server_key.png); }
967 .icon-issue { background-image: url(../images/ticket.png); }
973 .icon-issue { background-image: url(../images/ticket.png); }
968 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
974 .icon-zoom-in { background-image: url(../images/zoom_in.png); }
969 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
975 .icon-zoom-out { background-image: url(../images/zoom_out.png); }
970 .icon-passwd { background-image: url(../images/textfield_key.png); }
976 .icon-passwd { background-image: url(../images/textfield_key.png); }
971 .icon-test { background-image: url(../images/bullet_go.png); }
977 .icon-test { background-image: url(../images/bullet_go.png); }
972
978
973 .icon-file { background-image: url(../images/files/default.png); }
979 .icon-file { background-image: url(../images/files/default.png); }
974 .icon-file.text-plain { background-image: url(../images/files/text.png); }
980 .icon-file.text-plain { background-image: url(../images/files/text.png); }
975 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
981 .icon-file.text-x-c { background-image: url(../images/files/c.png); }
976 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
982 .icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); }
977 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
983 .icon-file.text-x-java { background-image: url(../images/files/java.png); }
978 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
984 .icon-file.text-x-javascript { background-image: url(../images/files/js.png); }
979 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
985 .icon-file.text-x-php { background-image: url(../images/files/php.png); }
980 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
986 .icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); }
981 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
987 .icon-file.text-xml { background-image: url(../images/files/xml.png); }
982 .icon-file.text-css { background-image: url(../images/files/css.png); }
988 .icon-file.text-css { background-image: url(../images/files/css.png); }
983 .icon-file.text-html { background-image: url(../images/files/html.png); }
989 .icon-file.text-html { background-image: url(../images/files/html.png); }
984 .icon-file.image-gif { background-image: url(../images/files/image.png); }
990 .icon-file.image-gif { background-image: url(../images/files/image.png); }
985 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
991 .icon-file.image-jpeg { background-image: url(../images/files/image.png); }
986 .icon-file.image-png { background-image: url(../images/files/image.png); }
992 .icon-file.image-png { background-image: url(../images/files/image.png); }
987 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
993 .icon-file.image-tiff { background-image: url(../images/files/image.png); }
988 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
994 .icon-file.application-pdf { background-image: url(../images/files/pdf.png); }
989 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
995 .icon-file.application-zip { background-image: url(../images/files/zip.png); }
990 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
996 .icon-file.application-x-gzip { background-image: url(../images/files/zip.png); }
991
997
992 img.gravatar {
998 img.gravatar {
993 padding: 2px;
999 padding: 2px;
994 border: solid 1px #d5d5d5;
1000 border: solid 1px #d5d5d5;
995 background: #fff;
1001 background: #fff;
996 vertical-align: middle;
1002 vertical-align: middle;
997 }
1003 }
998
1004
999 div.issue img.gravatar {
1005 div.issue img.gravatar {
1000 float: left;
1006 float: left;
1001 margin: 0 6px 0 0;
1007 margin: 0 6px 0 0;
1002 padding: 5px;
1008 padding: 5px;
1003 }
1009 }
1004
1010
1005 div.issue table img.gravatar {
1011 div.issue table img.gravatar {
1006 height: 14px;
1012 height: 14px;
1007 width: 14px;
1013 width: 14px;
1008 padding: 2px;
1014 padding: 2px;
1009 float: left;
1015 float: left;
1010 margin: 0 0.5em 0 0;
1016 margin: 0 0.5em 0 0;
1011 }
1017 }
1012
1018
1013 h2 img.gravatar {
1019 h2 img.gravatar {
1014 margin: -2px 4px -4px 0;
1020 margin: -2px 4px -4px 0;
1015 }
1021 }
1016
1022
1017 h3 img.gravatar {
1023 h3 img.gravatar {
1018 margin: -4px 4px -4px 0;
1024 margin: -4px 4px -4px 0;
1019 }
1025 }
1020
1026
1021 h4 img.gravatar {
1027 h4 img.gravatar {
1022 margin: -6px 4px -4px 0;
1028 margin: -6px 4px -4px 0;
1023 }
1029 }
1024
1030
1025 td.username img.gravatar {
1031 td.username img.gravatar {
1026 margin: 0 0.5em 0 0;
1032 margin: 0 0.5em 0 0;
1027 vertical-align: top;
1033 vertical-align: top;
1028 }
1034 }
1029
1035
1030 #activity dt img.gravatar {
1036 #activity dt img.gravatar {
1031 float: left;
1037 float: left;
1032 margin: 0 1em 1em 0;
1038 margin: 0 1em 1em 0;
1033 }
1039 }
1034
1040
1035 /* Used on 12px Gravatar img tags without the icon background */
1041 /* Used on 12px Gravatar img tags without the icon background */
1036 .icon-gravatar {
1042 .icon-gravatar {
1037 float: left;
1043 float: left;
1038 margin-right: 4px;
1044 margin-right: 4px;
1039 }
1045 }
1040
1046
1041 #activity dt,
1047 #activity dt,
1042 .journal {
1048 .journal {
1043 clear: left;
1049 clear: left;
1044 }
1050 }
1045
1051
1046 .journal-link {
1052 .journal-link {
1047 float: right;
1053 float: right;
1048 }
1054 }
1049
1055
1050 h2 img { vertical-align:middle; }
1056 h2 img { vertical-align:middle; }
1051
1057
1052 .hascontextmenu { cursor: context-menu; }
1058 .hascontextmenu { cursor: context-menu; }
1053
1059
1054 /***** Media print specific styles *****/
1060 /***** Media print specific styles *****/
1055 @media print {
1061 @media print {
1056 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1062 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
1057 #main { background: #fff; }
1063 #main { background: #fff; }
1058 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1064 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;}
1059 #wiki_add_attachment { display:none; }
1065 #wiki_add_attachment { display:none; }
1060 .hide-when-print { display: none; }
1066 .hide-when-print { display: none; }
1061 .autoscroll {overflow-x: visible;}
1067 .autoscroll {overflow-x: visible;}
1062 table.list {margin-top:0.5em;}
1068 table.list {margin-top:0.5em;}
1063 table.list th, table.list td {border: 1px solid #aaa;}
1069 table.list th, table.list td {border: 1px solid #aaa;}
1064 }
1070 }
1065
1071
1066 /* Accessibility specific styles */
1072 /* Accessibility specific styles */
1067 .hidden-for-sighted {
1073 .hidden-for-sighted {
1068 position:absolute;
1074 position:absolute;
1069 left:-10000px;
1075 left:-10000px;
1070 top:auto;
1076 top:auto;
1071 width:1px;
1077 width:1px;
1072 height:1px;
1078 height:1px;
1073 overflow:hidden;
1079 overflow:hidden;
1074 }
1080 }
@@ -1,3153 +1,3168
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang
2 # Copyright (C) 2006-2012 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 require 'issues_controller'
19 require 'issues_controller'
20
20
21 class IssuesControllerTest < ActionController::TestCase
21 class IssuesControllerTest < ActionController::TestCase
22 fixtures :projects,
22 fixtures :projects,
23 :users,
23 :users,
24 :roles,
24 :roles,
25 :members,
25 :members,
26 :member_roles,
26 :member_roles,
27 :issues,
27 :issues,
28 :issue_statuses,
28 :issue_statuses,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries,
44 :queries,
45 :repositories,
45 :repositories,
46 :changesets
46 :changesets
47
47
48 include Redmine::I18n
48 include Redmine::I18n
49
49
50 def setup
50 def setup
51 @controller = IssuesController.new
51 @controller = IssuesController.new
52 @request = ActionController::TestRequest.new
52 @request = ActionController::TestRequest.new
53 @response = ActionController::TestResponse.new
53 @response = ActionController::TestResponse.new
54 User.current = nil
54 User.current = nil
55 end
55 end
56
56
57 def test_index
57 def test_index
58 with_settings :default_language => "en" do
58 with_settings :default_language => "en" do
59 get :index
59 get :index
60 assert_response :success
60 assert_response :success
61 assert_template 'index'
61 assert_template 'index'
62 assert_not_nil assigns(:issues)
62 assert_not_nil assigns(:issues)
63 assert_nil assigns(:project)
63 assert_nil assigns(:project)
64 assert_tag :tag => 'a', :content => /Can't print recipes/
64 assert_tag :tag => 'a', :content => /Can't print recipes/
65 assert_tag :tag => 'a', :content => /Subproject issue/
65 assert_tag :tag => 'a', :content => /Subproject issue/
66 # private projects hidden
66 # private projects hidden
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 assert_no_tag :tag => 'a', :content => /Issue on project 2/
69 # project column
69 # project column
70 assert_tag :tag => 'th', :content => /Project/
70 assert_tag :tag => 'th', :content => /Project/
71 end
71 end
72 end
72 end
73
73
74 def test_index_should_not_list_issues_when_module_disabled
74 def test_index_should_not_list_issues_when_module_disabled
75 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
76 get :index
76 get :index
77 assert_response :success
77 assert_response :success
78 assert_template 'index'
78 assert_template 'index'
79 assert_not_nil assigns(:issues)
79 assert_not_nil assigns(:issues)
80 assert_nil assigns(:project)
80 assert_nil assigns(:project)
81 assert_no_tag :tag => 'a', :content => /Can't print recipes/
81 assert_no_tag :tag => 'a', :content => /Can't print recipes/
82 assert_tag :tag => 'a', :content => /Subproject issue/
82 assert_tag :tag => 'a', :content => /Subproject issue/
83 end
83 end
84
84
85 def test_index_should_list_visible_issues_only
85 def test_index_should_list_visible_issues_only
86 get :index, :per_page => 100
86 get :index, :per_page => 100
87 assert_response :success
87 assert_response :success
88 assert_not_nil assigns(:issues)
88 assert_not_nil assigns(:issues)
89 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
89 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
90 end
90 end
91
91
92 def test_index_with_project
92 def test_index_with_project
93 Setting.display_subprojects_issues = 0
93 Setting.display_subprojects_issues = 0
94 get :index, :project_id => 1
94 get :index, :project_id => 1
95 assert_response :success
95 assert_response :success
96 assert_template 'index'
96 assert_template 'index'
97 assert_not_nil assigns(:issues)
97 assert_not_nil assigns(:issues)
98 assert_tag :tag => 'a', :content => /Can't print recipes/
98 assert_tag :tag => 'a', :content => /Can't print recipes/
99 assert_no_tag :tag => 'a', :content => /Subproject issue/
99 assert_no_tag :tag => 'a', :content => /Subproject issue/
100 end
100 end
101
101
102 def test_index_with_project_and_subprojects
102 def test_index_with_project_and_subprojects
103 Setting.display_subprojects_issues = 1
103 Setting.display_subprojects_issues = 1
104 get :index, :project_id => 1
104 get :index, :project_id => 1
105 assert_response :success
105 assert_response :success
106 assert_template 'index'
106 assert_template 'index'
107 assert_not_nil assigns(:issues)
107 assert_not_nil assigns(:issues)
108 assert_tag :tag => 'a', :content => /Can't print recipes/
108 assert_tag :tag => 'a', :content => /Can't print recipes/
109 assert_tag :tag => 'a', :content => /Subproject issue/
109 assert_tag :tag => 'a', :content => /Subproject issue/
110 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
110 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
111 end
111 end
112
112
113 def test_index_with_project_and_subprojects_should_show_private_subprojects
113 def test_index_with_project_and_subprojects_should_show_private_subprojects
114 @request.session[:user_id] = 2
114 @request.session[:user_id] = 2
115 Setting.display_subprojects_issues = 1
115 Setting.display_subprojects_issues = 1
116 get :index, :project_id => 1
116 get :index, :project_id => 1
117 assert_response :success
117 assert_response :success
118 assert_template 'index'
118 assert_template 'index'
119 assert_not_nil assigns(:issues)
119 assert_not_nil assigns(:issues)
120 assert_tag :tag => 'a', :content => /Can't print recipes/
120 assert_tag :tag => 'a', :content => /Can't print recipes/
121 assert_tag :tag => 'a', :content => /Subproject issue/
121 assert_tag :tag => 'a', :content => /Subproject issue/
122 assert_tag :tag => 'a', :content => /Issue of a private subproject/
122 assert_tag :tag => 'a', :content => /Issue of a private subproject/
123 end
123 end
124
124
125 def test_index_with_project_and_default_filter
125 def test_index_with_project_and_default_filter
126 get :index, :project_id => 1, :set_filter => 1
126 get :index, :project_id => 1, :set_filter => 1
127 assert_response :success
127 assert_response :success
128 assert_template 'index'
128 assert_template 'index'
129 assert_not_nil assigns(:issues)
129 assert_not_nil assigns(:issues)
130
130
131 query = assigns(:query)
131 query = assigns(:query)
132 assert_not_nil query
132 assert_not_nil query
133 # default filter
133 # default filter
134 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
134 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
135 end
135 end
136
136
137 def test_index_with_project_and_filter
137 def test_index_with_project_and_filter
138 get :index, :project_id => 1, :set_filter => 1,
138 get :index, :project_id => 1, :set_filter => 1,
139 :f => ['tracker_id'],
139 :f => ['tracker_id'],
140 :op => {'tracker_id' => '='},
140 :op => {'tracker_id' => '='},
141 :v => {'tracker_id' => ['1']}
141 :v => {'tracker_id' => ['1']}
142 assert_response :success
142 assert_response :success
143 assert_template 'index'
143 assert_template 'index'
144 assert_not_nil assigns(:issues)
144 assert_not_nil assigns(:issues)
145
145
146 query = assigns(:query)
146 query = assigns(:query)
147 assert_not_nil query
147 assert_not_nil query
148 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
148 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
149 end
149 end
150
150
151 def test_index_with_short_filters
151 def test_index_with_short_filters
152 to_test = {
152 to_test = {
153 'status_id' => {
153 'status_id' => {
154 'o' => { :op => 'o', :values => [''] },
154 'o' => { :op => 'o', :values => [''] },
155 'c' => { :op => 'c', :values => [''] },
155 'c' => { :op => 'c', :values => [''] },
156 '7' => { :op => '=', :values => ['7'] },
156 '7' => { :op => '=', :values => ['7'] },
157 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
157 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
158 '=7' => { :op => '=', :values => ['7'] },
158 '=7' => { :op => '=', :values => ['7'] },
159 '!3' => { :op => '!', :values => ['3'] },
159 '!3' => { :op => '!', :values => ['3'] },
160 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
160 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
161 'subject' => {
161 'subject' => {
162 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
162 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
163 'o' => { :op => '=', :values => ['o'] },
163 'o' => { :op => '=', :values => ['o'] },
164 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
164 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
165 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
165 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
166 'tracker_id' => {
166 'tracker_id' => {
167 '3' => { :op => '=', :values => ['3'] },
167 '3' => { :op => '=', :values => ['3'] },
168 '=3' => { :op => '=', :values => ['3'] }},
168 '=3' => { :op => '=', :values => ['3'] }},
169 'start_date' => {
169 'start_date' => {
170 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
172 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
172 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
173 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
173 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
174 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
174 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
175 '<t+2' => { :op => '<t+', :values => ['2'] },
175 '<t+2' => { :op => '<t+', :values => ['2'] },
176 '>t+2' => { :op => '>t+', :values => ['2'] },
176 '>t+2' => { :op => '>t+', :values => ['2'] },
177 't+2' => { :op => 't+', :values => ['2'] },
177 't+2' => { :op => 't+', :values => ['2'] },
178 't' => { :op => 't', :values => [''] },
178 't' => { :op => 't', :values => [''] },
179 'w' => { :op => 'w', :values => [''] },
179 'w' => { :op => 'w', :values => [''] },
180 '>t-2' => { :op => '>t-', :values => ['2'] },
180 '>t-2' => { :op => '>t-', :values => ['2'] },
181 '<t-2' => { :op => '<t-', :values => ['2'] },
181 '<t-2' => { :op => '<t-', :values => ['2'] },
182 't-2' => { :op => 't-', :values => ['2'] }},
182 't-2' => { :op => 't-', :values => ['2'] }},
183 'created_on' => {
183 'created_on' => {
184 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
184 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
185 '<t-2' => { :op => '<t-', :values => ['2'] },
185 '<t-2' => { :op => '<t-', :values => ['2'] },
186 '>t-2' => { :op => '>t-', :values => ['2'] },
186 '>t-2' => { :op => '>t-', :values => ['2'] },
187 't-2' => { :op => 't-', :values => ['2'] }},
187 't-2' => { :op => 't-', :values => ['2'] }},
188 'cf_1' => {
188 'cf_1' => {
189 'c' => { :op => '=', :values => ['c'] },
189 'c' => { :op => '=', :values => ['c'] },
190 '!c' => { :op => '!', :values => ['c'] },
190 '!c' => { :op => '!', :values => ['c'] },
191 '!*' => { :op => '!*', :values => [''] },
191 '!*' => { :op => '!*', :values => [''] },
192 '*' => { :op => '*', :values => [''] }},
192 '*' => { :op => '*', :values => [''] }},
193 'estimated_hours' => {
193 'estimated_hours' => {
194 '=13.4' => { :op => '=', :values => ['13.4'] },
194 '=13.4' => { :op => '=', :values => ['13.4'] },
195 '>=45' => { :op => '>=', :values => ['45'] },
195 '>=45' => { :op => '>=', :values => ['45'] },
196 '<=125' => { :op => '<=', :values => ['125'] },
196 '<=125' => { :op => '<=', :values => ['125'] },
197 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
197 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
198 '!*' => { :op => '!*', :values => [''] },
198 '!*' => { :op => '!*', :values => [''] },
199 '*' => { :op => '*', :values => [''] }}
199 '*' => { :op => '*', :values => [''] }}
200 }
200 }
201
201
202 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
202 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
203
203
204 to_test.each do |field, expression_and_expected|
204 to_test.each do |field, expression_and_expected|
205 expression_and_expected.each do |filter_expression, expected|
205 expression_and_expected.each do |filter_expression, expected|
206
206
207 get :index, :set_filter => 1, field => filter_expression
207 get :index, :set_filter => 1, field => filter_expression
208
208
209 assert_response :success
209 assert_response :success
210 assert_template 'index'
210 assert_template 'index'
211 assert_not_nil assigns(:issues)
211 assert_not_nil assigns(:issues)
212
212
213 query = assigns(:query)
213 query = assigns(:query)
214 assert_not_nil query
214 assert_not_nil query
215 assert query.has_filter?(field)
215 assert query.has_filter?(field)
216 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
216 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
217 end
217 end
218 end
218 end
219 end
219 end
220
220
221 def test_index_with_project_and_empty_filters
221 def test_index_with_project_and_empty_filters
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
223 assert_response :success
223 assert_response :success
224 assert_template 'index'
224 assert_template 'index'
225 assert_not_nil assigns(:issues)
225 assert_not_nil assigns(:issues)
226
226
227 query = assigns(:query)
227 query = assigns(:query)
228 assert_not_nil query
228 assert_not_nil query
229 # no filter
229 # no filter
230 assert_equal({}, query.filters)
230 assert_equal({}, query.filters)
231 end
231 end
232
232
233 def test_index_with_query
233 def test_index_with_query
234 get :index, :project_id => 1, :query_id => 5
234 get :index, :project_id => 1, :query_id => 5
235 assert_response :success
235 assert_response :success
236 assert_template 'index'
236 assert_template 'index'
237 assert_not_nil assigns(:issues)
237 assert_not_nil assigns(:issues)
238 assert_nil assigns(:issue_count_by_group)
238 assert_nil assigns(:issue_count_by_group)
239 end
239 end
240
240
241 def test_index_with_query_grouped_by_tracker
241 def test_index_with_query_grouped_by_tracker
242 get :index, :project_id => 1, :query_id => 6
242 get :index, :project_id => 1, :query_id => 6
243 assert_response :success
243 assert_response :success
244 assert_template 'index'
244 assert_template 'index'
245 assert_not_nil assigns(:issues)
245 assert_not_nil assigns(:issues)
246 assert_not_nil assigns(:issue_count_by_group)
246 assert_not_nil assigns(:issue_count_by_group)
247 end
247 end
248
248
249 def test_index_with_query_grouped_by_list_custom_field
249 def test_index_with_query_grouped_by_list_custom_field
250 get :index, :project_id => 1, :query_id => 9
250 get :index, :project_id => 1, :query_id => 9
251 assert_response :success
251 assert_response :success
252 assert_template 'index'
252 assert_template 'index'
253 assert_not_nil assigns(:issues)
253 assert_not_nil assigns(:issues)
254 assert_not_nil assigns(:issue_count_by_group)
254 assert_not_nil assigns(:issue_count_by_group)
255 end
255 end
256
256
257 def test_index_with_query_id_and_project_id_should_set_session_query
257 def test_index_with_query_id_and_project_id_should_set_session_query
258 get :index, :project_id => 1, :query_id => 4
258 get :index, :project_id => 1, :query_id => 4
259 assert_response :success
259 assert_response :success
260 assert_kind_of Hash, session[:query]
260 assert_kind_of Hash, session[:query]
261 assert_equal 4, session[:query][:id]
261 assert_equal 4, session[:query][:id]
262 assert_equal 1, session[:query][:project_id]
262 assert_equal 1, session[:query][:project_id]
263 end
263 end
264
264
265 def test_index_with_invalid_query_id_should_respond_404
265 def test_index_with_invalid_query_id_should_respond_404
266 get :index, :project_id => 1, :query_id => 999
266 get :index, :project_id => 1, :query_id => 999
267 assert_response 404
267 assert_response 404
268 end
268 end
269
269
270 def test_index_with_cross_project_query_in_session_should_show_project_issues
270 def test_index_with_cross_project_query_in_session_should_show_project_issues
271 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
271 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
272 @request.session[:query] = {:id => q.id, :project_id => 1}
272 @request.session[:query] = {:id => q.id, :project_id => 1}
273
273
274 with_settings :display_subprojects_issues => '0' do
274 with_settings :display_subprojects_issues => '0' do
275 get :index, :project_id => 1
275 get :index, :project_id => 1
276 end
276 end
277 assert_response :success
277 assert_response :success
278 assert_not_nil assigns(:query)
278 assert_not_nil assigns(:query)
279 assert_equal q.id, assigns(:query).id
279 assert_equal q.id, assigns(:query).id
280 assert_equal 1, assigns(:query).project_id
280 assert_equal 1, assigns(:query).project_id
281 assert_equal [1], assigns(:issues).map(&:project_id).uniq
281 assert_equal [1], assigns(:issues).map(&:project_id).uniq
282 end
282 end
283
283
284 def test_private_query_should_not_be_available_to_other_users
284 def test_private_query_should_not_be_available_to_other_users
285 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
285 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
286 @request.session[:user_id] = 3
286 @request.session[:user_id] = 3
287
287
288 get :index, :query_id => q.id
288 get :index, :query_id => q.id
289 assert_response 403
289 assert_response 403
290 end
290 end
291
291
292 def test_private_query_should_be_available_to_its_user
292 def test_private_query_should_be_available_to_its_user
293 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
293 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
294 @request.session[:user_id] = 2
294 @request.session[:user_id] = 2
295
295
296 get :index, :query_id => q.id
296 get :index, :query_id => q.id
297 assert_response :success
297 assert_response :success
298 end
298 end
299
299
300 def test_public_query_should_be_available_to_other_users
300 def test_public_query_should_be_available_to_other_users
301 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
301 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
302 @request.session[:user_id] = 3
302 @request.session[:user_id] = 3
303
303
304 get :index, :query_id => q.id
304 get :index, :query_id => q.id
305 assert_response :success
305 assert_response :success
306 end
306 end
307
307
308 def test_index_csv
308 def test_index_csv
309 get :index, :format => 'csv'
309 get :index, :format => 'csv'
310 assert_response :success
310 assert_response :success
311 assert_not_nil assigns(:issues)
311 assert_not_nil assigns(:issues)
312 assert_equal 'text/csv', @response.content_type
312 assert_equal 'text/csv', @response.content_type
313 assert @response.body.starts_with?("#,")
313 assert @response.body.starts_with?("#,")
314 lines = @response.body.chomp.split("\n")
314 lines = @response.body.chomp.split("\n")
315 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
315 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
316 end
316 end
317
317
318 def test_index_csv_with_project
318 def test_index_csv_with_project
319 get :index, :project_id => 1, :format => 'csv'
319 get :index, :project_id => 1, :format => 'csv'
320 assert_response :success
320 assert_response :success
321 assert_not_nil assigns(:issues)
321 assert_not_nil assigns(:issues)
322 assert_equal 'text/csv', @response.content_type
322 assert_equal 'text/csv', @response.content_type
323 end
323 end
324
324
325 def test_index_csv_with_description
325 def test_index_csv_with_description
326 get :index, :format => 'csv', :description => '1'
326 get :index, :format => 'csv', :description => '1'
327 assert_response :success
327 assert_response :success
328 assert_not_nil assigns(:issues)
328 assert_not_nil assigns(:issues)
329 assert_equal 'text/csv', @response.content_type
329 assert_equal 'text/csv', @response.content_type
330 assert @response.body.starts_with?("#,")
330 assert @response.body.starts_with?("#,")
331 lines = @response.body.chomp.split("\n")
331 lines = @response.body.chomp.split("\n")
332 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
332 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
333 end
333 end
334
334
335 def test_index_csv_with_spent_time_column
335 def test_index_csv_with_spent_time_column
336 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
336 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
337 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
337 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
338
338
339 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
339 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
340 assert_response :success
340 assert_response :success
341 assert_equal 'text/csv', @response.content_type
341 assert_equal 'text/csv', @response.content_type
342 lines = @response.body.chomp.split("\n")
342 lines = @response.body.chomp.split("\n")
343 assert_include "#{issue.id},#{issue.subject},7.33", lines
343 assert_include "#{issue.id},#{issue.subject},7.33", lines
344 end
344 end
345
345
346 def test_index_csv_with_all_columns
346 def test_index_csv_with_all_columns
347 get :index, :format => 'csv', :columns => 'all'
347 get :index, :format => 'csv', :columns => 'all'
348 assert_response :success
348 assert_response :success
349 assert_not_nil assigns(:issues)
349 assert_not_nil assigns(:issues)
350 assert_equal 'text/csv', @response.content_type
350 assert_equal 'text/csv', @response.content_type
351 assert @response.body.starts_with?("#,")
351 assert @response.body.starts_with?("#,")
352 lines = @response.body.chomp.split("\n")
352 lines = @response.body.chomp.split("\n")
353 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
353 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
354 end
354 end
355
355
356 def test_index_csv_with_multi_column_field
356 def test_index_csv_with_multi_column_field
357 CustomField.find(1).update_attribute :multiple, true
357 CustomField.find(1).update_attribute :multiple, true
358 issue = Issue.find(1)
358 issue = Issue.find(1)
359 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
359 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
360 issue.save!
360 issue.save!
361
361
362 get :index, :format => 'csv', :columns => 'all'
362 get :index, :format => 'csv', :columns => 'all'
363 assert_response :success
363 assert_response :success
364 lines = @response.body.chomp.split("\n")
364 lines = @response.body.chomp.split("\n")
365 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
365 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
366 end
366 end
367
367
368 def test_index_csv_big_5
368 def test_index_csv_big_5
369 with_settings :default_language => "zh-TW" do
369 with_settings :default_language => "zh-TW" do
370 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
370 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
371 str_big5 = "\xa4@\xa4\xeb"
371 str_big5 = "\xa4@\xa4\xeb"
372 if str_utf8.respond_to?(:force_encoding)
372 if str_utf8.respond_to?(:force_encoding)
373 str_utf8.force_encoding('UTF-8')
373 str_utf8.force_encoding('UTF-8')
374 str_big5.force_encoding('Big5')
374 str_big5.force_encoding('Big5')
375 end
375 end
376 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
376 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
377 :status_id => 1, :priority => IssuePriority.all.first,
377 :status_id => 1, :priority => IssuePriority.all.first,
378 :subject => str_utf8)
378 :subject => str_utf8)
379 assert issue.save
379 assert issue.save
380
380
381 get :index, :project_id => 1,
381 get :index, :project_id => 1,
382 :f => ['subject'],
382 :f => ['subject'],
383 :op => '=', :values => [str_utf8],
383 :op => '=', :values => [str_utf8],
384 :format => 'csv'
384 :format => 'csv'
385 assert_equal 'text/csv', @response.content_type
385 assert_equal 'text/csv', @response.content_type
386 lines = @response.body.chomp.split("\n")
386 lines = @response.body.chomp.split("\n")
387 s1 = "\xaa\xac\xbaA"
387 s1 = "\xaa\xac\xbaA"
388 if str_utf8.respond_to?(:force_encoding)
388 if str_utf8.respond_to?(:force_encoding)
389 s1.force_encoding('Big5')
389 s1.force_encoding('Big5')
390 end
390 end
391 assert lines[0].include?(s1)
391 assert lines[0].include?(s1)
392 assert lines[1].include?(str_big5)
392 assert lines[1].include?(str_big5)
393 end
393 end
394 end
394 end
395
395
396 def test_index_csv_cannot_convert_should_be_replaced_big_5
396 def test_index_csv_cannot_convert_should_be_replaced_big_5
397 with_settings :default_language => "zh-TW" do
397 with_settings :default_language => "zh-TW" do
398 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
398 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
399 if str_utf8.respond_to?(:force_encoding)
399 if str_utf8.respond_to?(:force_encoding)
400 str_utf8.force_encoding('UTF-8')
400 str_utf8.force_encoding('UTF-8')
401 end
401 end
402 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
402 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
403 :status_id => 1, :priority => IssuePriority.all.first,
403 :status_id => 1, :priority => IssuePriority.all.first,
404 :subject => str_utf8)
404 :subject => str_utf8)
405 assert issue.save
405 assert issue.save
406
406
407 get :index, :project_id => 1,
407 get :index, :project_id => 1,
408 :f => ['subject'],
408 :f => ['subject'],
409 :op => '=', :values => [str_utf8],
409 :op => '=', :values => [str_utf8],
410 :c => ['status', 'subject'],
410 :c => ['status', 'subject'],
411 :format => 'csv',
411 :format => 'csv',
412 :set_filter => 1
412 :set_filter => 1
413 assert_equal 'text/csv', @response.content_type
413 assert_equal 'text/csv', @response.content_type
414 lines = @response.body.chomp.split("\n")
414 lines = @response.body.chomp.split("\n")
415 s1 = "\xaa\xac\xbaA" # status
415 s1 = "\xaa\xac\xbaA" # status
416 if str_utf8.respond_to?(:force_encoding)
416 if str_utf8.respond_to?(:force_encoding)
417 s1.force_encoding('Big5')
417 s1.force_encoding('Big5')
418 end
418 end
419 assert lines[0].include?(s1)
419 assert lines[0].include?(s1)
420 s2 = lines[1].split(",")[2]
420 s2 = lines[1].split(",")[2]
421 if s1.respond_to?(:force_encoding)
421 if s1.respond_to?(:force_encoding)
422 s3 = "\xa5H?" # subject
422 s3 = "\xa5H?" # subject
423 s3.force_encoding('Big5')
423 s3.force_encoding('Big5')
424 assert_equal s3, s2
424 assert_equal s3, s2
425 elsif RUBY_PLATFORM == 'java'
425 elsif RUBY_PLATFORM == 'java'
426 assert_equal "??", s2
426 assert_equal "??", s2
427 else
427 else
428 assert_equal "\xa5H???", s2
428 assert_equal "\xa5H???", s2
429 end
429 end
430 end
430 end
431 end
431 end
432
432
433 def test_index_csv_tw
433 def test_index_csv_tw
434 with_settings :default_language => "zh-TW" do
434 with_settings :default_language => "zh-TW" do
435 str1 = "test_index_csv_tw"
435 str1 = "test_index_csv_tw"
436 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
436 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
437 :status_id => 1, :priority => IssuePriority.all.first,
437 :status_id => 1, :priority => IssuePriority.all.first,
438 :subject => str1, :estimated_hours => '1234.5')
438 :subject => str1, :estimated_hours => '1234.5')
439 assert issue.save
439 assert issue.save
440 assert_equal 1234.5, issue.estimated_hours
440 assert_equal 1234.5, issue.estimated_hours
441
441
442 get :index, :project_id => 1,
442 get :index, :project_id => 1,
443 :f => ['subject'],
443 :f => ['subject'],
444 :op => '=', :values => [str1],
444 :op => '=', :values => [str1],
445 :c => ['estimated_hours', 'subject'],
445 :c => ['estimated_hours', 'subject'],
446 :format => 'csv',
446 :format => 'csv',
447 :set_filter => 1
447 :set_filter => 1
448 assert_equal 'text/csv', @response.content_type
448 assert_equal 'text/csv', @response.content_type
449 lines = @response.body.chomp.split("\n")
449 lines = @response.body.chomp.split("\n")
450 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
450 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
451
451
452 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
452 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
453 if str_tw.respond_to?(:force_encoding)
453 if str_tw.respond_to?(:force_encoding)
454 str_tw.force_encoding('UTF-8')
454 str_tw.force_encoding('UTF-8')
455 end
455 end
456 assert_equal str_tw, l(:general_lang_name)
456 assert_equal str_tw, l(:general_lang_name)
457 assert_equal ',', l(:general_csv_separator)
457 assert_equal ',', l(:general_csv_separator)
458 assert_equal '.', l(:general_csv_decimal_separator)
458 assert_equal '.', l(:general_csv_decimal_separator)
459 end
459 end
460 end
460 end
461
461
462 def test_index_csv_fr
462 def test_index_csv_fr
463 with_settings :default_language => "fr" do
463 with_settings :default_language => "fr" do
464 str1 = "test_index_csv_fr"
464 str1 = "test_index_csv_fr"
465 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
465 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
466 :status_id => 1, :priority => IssuePriority.all.first,
466 :status_id => 1, :priority => IssuePriority.all.first,
467 :subject => str1, :estimated_hours => '1234.5')
467 :subject => str1, :estimated_hours => '1234.5')
468 assert issue.save
468 assert issue.save
469 assert_equal 1234.5, issue.estimated_hours
469 assert_equal 1234.5, issue.estimated_hours
470
470
471 get :index, :project_id => 1,
471 get :index, :project_id => 1,
472 :f => ['subject'],
472 :f => ['subject'],
473 :op => '=', :values => [str1],
473 :op => '=', :values => [str1],
474 :c => ['estimated_hours', 'subject'],
474 :c => ['estimated_hours', 'subject'],
475 :format => 'csv',
475 :format => 'csv',
476 :set_filter => 1
476 :set_filter => 1
477 assert_equal 'text/csv', @response.content_type
477 assert_equal 'text/csv', @response.content_type
478 lines = @response.body.chomp.split("\n")
478 lines = @response.body.chomp.split("\n")
479 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
479 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
480
480
481 str_fr = "Fran\xc3\xa7ais"
481 str_fr = "Fran\xc3\xa7ais"
482 if str_fr.respond_to?(:force_encoding)
482 if str_fr.respond_to?(:force_encoding)
483 str_fr.force_encoding('UTF-8')
483 str_fr.force_encoding('UTF-8')
484 end
484 end
485 assert_equal str_fr, l(:general_lang_name)
485 assert_equal str_fr, l(:general_lang_name)
486 assert_equal ';', l(:general_csv_separator)
486 assert_equal ';', l(:general_csv_separator)
487 assert_equal ',', l(:general_csv_decimal_separator)
487 assert_equal ',', l(:general_csv_decimal_separator)
488 end
488 end
489 end
489 end
490
490
491 def test_index_pdf
491 def test_index_pdf
492 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
492 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
493 with_settings :default_language => lang do
493 with_settings :default_language => lang do
494
494
495 get :index
495 get :index
496 assert_response :success
496 assert_response :success
497 assert_template 'index'
497 assert_template 'index'
498
498
499 if lang == "ja"
499 if lang == "ja"
500 if RUBY_PLATFORM != 'java'
500 if RUBY_PLATFORM != 'java'
501 assert_equal "CP932", l(:general_pdf_encoding)
501 assert_equal "CP932", l(:general_pdf_encoding)
502 end
502 end
503 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
503 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
504 next
504 next
505 end
505 end
506 end
506 end
507
507
508 get :index, :format => 'pdf'
508 get :index, :format => 'pdf'
509 assert_response :success
509 assert_response :success
510 assert_not_nil assigns(:issues)
510 assert_not_nil assigns(:issues)
511 assert_equal 'application/pdf', @response.content_type
511 assert_equal 'application/pdf', @response.content_type
512
512
513 get :index, :project_id => 1, :format => 'pdf'
513 get :index, :project_id => 1, :format => 'pdf'
514 assert_response :success
514 assert_response :success
515 assert_not_nil assigns(:issues)
515 assert_not_nil assigns(:issues)
516 assert_equal 'application/pdf', @response.content_type
516 assert_equal 'application/pdf', @response.content_type
517
517
518 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
518 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
519 assert_response :success
519 assert_response :success
520 assert_not_nil assigns(:issues)
520 assert_not_nil assigns(:issues)
521 assert_equal 'application/pdf', @response.content_type
521 assert_equal 'application/pdf', @response.content_type
522 end
522 end
523 end
523 end
524 end
524 end
525
525
526 def test_index_pdf_with_query_grouped_by_list_custom_field
526 def test_index_pdf_with_query_grouped_by_list_custom_field
527 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
527 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
528 assert_response :success
528 assert_response :success
529 assert_not_nil assigns(:issues)
529 assert_not_nil assigns(:issues)
530 assert_not_nil assigns(:issue_count_by_group)
530 assert_not_nil assigns(:issue_count_by_group)
531 assert_equal 'application/pdf', @response.content_type
531 assert_equal 'application/pdf', @response.content_type
532 end
532 end
533
533
534 def test_index_atom
534 def test_index_atom
535 get :index, :project_id => 'ecookbook', :format => 'atom'
535 get :index, :project_id => 'ecookbook', :format => 'atom'
536 assert_response :success
536 assert_response :success
537 assert_template 'common/feed'
537 assert_template 'common/feed'
538
538
539 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
539 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
540 :attributes => {:rel => 'self', :href => 'http://test.host/projects/ecookbook/issues.atom'}
540 :attributes => {:rel => 'self', :href => 'http://test.host/projects/ecookbook/issues.atom'}
541 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
541 assert_tag :tag => 'link', :parent => {:tag => 'feed', :parent => nil },
542 :attributes => {:rel => 'alternate', :href => 'http://test.host/projects/ecookbook/issues'}
542 :attributes => {:rel => 'alternate', :href => 'http://test.host/projects/ecookbook/issues'}
543
543
544 assert_tag :tag => 'entry', :child => {
544 assert_tag :tag => 'entry', :child => {
545 :tag => 'link',
545 :tag => 'link',
546 :attributes => {:href => 'http://test.host/issues/1'}}
546 :attributes => {:href => 'http://test.host/issues/1'}}
547 end
547 end
548
548
549 def test_index_sort
549 def test_index_sort
550 get :index, :sort => 'tracker,id:desc'
550 get :index, :sort => 'tracker,id:desc'
551 assert_response :success
551 assert_response :success
552
552
553 sort_params = @request.session['issues_index_sort']
553 sort_params = @request.session['issues_index_sort']
554 assert sort_params.is_a?(String)
554 assert sort_params.is_a?(String)
555 assert_equal 'tracker,id:desc', sort_params
555 assert_equal 'tracker,id:desc', sort_params
556
556
557 issues = assigns(:issues)
557 issues = assigns(:issues)
558 assert_not_nil issues
558 assert_not_nil issues
559 assert !issues.empty?
559 assert !issues.empty?
560 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
560 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
561 end
561 end
562
562
563 def test_index_sort_by_field_not_included_in_columns
563 def test_index_sort_by_field_not_included_in_columns
564 Setting.issue_list_default_columns = %w(subject author)
564 Setting.issue_list_default_columns = %w(subject author)
565 get :index, :sort => 'tracker'
565 get :index, :sort => 'tracker'
566 end
566 end
567
567
568 def test_index_sort_by_assigned_to
568 def test_index_sort_by_assigned_to
569 get :index, :sort => 'assigned_to'
569 get :index, :sort => 'assigned_to'
570 assert_response :success
570 assert_response :success
571 assignees = assigns(:issues).collect(&:assigned_to).compact
571 assignees = assigns(:issues).collect(&:assigned_to).compact
572 assert_equal assignees.sort, assignees
572 assert_equal assignees.sort, assignees
573 end
573 end
574
574
575 def test_index_sort_by_assigned_to_desc
575 def test_index_sort_by_assigned_to_desc
576 get :index, :sort => 'assigned_to:desc'
576 get :index, :sort => 'assigned_to:desc'
577 assert_response :success
577 assert_response :success
578 assignees = assigns(:issues).collect(&:assigned_to).compact
578 assignees = assigns(:issues).collect(&:assigned_to).compact
579 assert_equal assignees.sort.reverse, assignees
579 assert_equal assignees.sort.reverse, assignees
580 end
580 end
581
581
582 def test_index_group_by_assigned_to
582 def test_index_group_by_assigned_to
583 get :index, :group_by => 'assigned_to', :sort => 'priority'
583 get :index, :group_by => 'assigned_to', :sort => 'priority'
584 assert_response :success
584 assert_response :success
585 end
585 end
586
586
587 def test_index_sort_by_author
587 def test_index_sort_by_author
588 get :index, :sort => 'author'
588 get :index, :sort => 'author'
589 assert_response :success
589 assert_response :success
590 authors = assigns(:issues).collect(&:author)
590 authors = assigns(:issues).collect(&:author)
591 assert_equal authors.sort, authors
591 assert_equal authors.sort, authors
592 end
592 end
593
593
594 def test_index_sort_by_author_desc
594 def test_index_sort_by_author_desc
595 get :index, :sort => 'author:desc'
595 get :index, :sort => 'author:desc'
596 assert_response :success
596 assert_response :success
597 authors = assigns(:issues).collect(&:author)
597 authors = assigns(:issues).collect(&:author)
598 assert_equal authors.sort.reverse, authors
598 assert_equal authors.sort.reverse, authors
599 end
599 end
600
600
601 def test_index_group_by_author
601 def test_index_group_by_author
602 get :index, :group_by => 'author', :sort => 'priority'
602 get :index, :group_by => 'author', :sort => 'priority'
603 assert_response :success
603 assert_response :success
604 end
604 end
605
605
606 def test_index_sort_by_spent_hours
606 def test_index_sort_by_spent_hours
607 get :index, :sort => 'spent_hours:desc'
607 get :index, :sort => 'spent_hours:desc'
608 assert_response :success
608 assert_response :success
609 hours = assigns(:issues).collect(&:spent_hours)
609 hours = assigns(:issues).collect(&:spent_hours)
610 assert_equal hours.sort.reverse, hours
610 assert_equal hours.sort.reverse, hours
611 end
611 end
612
612
613 def test_index_with_columns
613 def test_index_with_columns
614 columns = ['tracker', 'subject', 'assigned_to']
614 columns = ['tracker', 'subject', 'assigned_to']
615 get :index, :set_filter => 1, :c => columns
615 get :index, :set_filter => 1, :c => columns
616 assert_response :success
616 assert_response :success
617
617
618 # query should use specified columns
618 # query should use specified columns
619 query = assigns(:query)
619 query = assigns(:query)
620 assert_kind_of Query, query
620 assert_kind_of Query, query
621 assert_equal columns, query.column_names.map(&:to_s)
621 assert_equal columns, query.column_names.map(&:to_s)
622
622
623 # columns should be stored in session
623 # columns should be stored in session
624 assert_kind_of Hash, session[:query]
624 assert_kind_of Hash, session[:query]
625 assert_kind_of Array, session[:query][:column_names]
625 assert_kind_of Array, session[:query][:column_names]
626 assert_equal columns, session[:query][:column_names].map(&:to_s)
626 assert_equal columns, session[:query][:column_names].map(&:to_s)
627
627
628 # ensure only these columns are kept in the selected columns list
628 # ensure only these columns are kept in the selected columns list
629 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
629 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
630 :children => { :count => 3 }
630 :children => { :count => 3 }
631 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
631 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
632 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
632 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
633 end
633 end
634
634
635 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
635 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
636 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
636 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
637 get :index, :set_filter => 1
637 get :index, :set_filter => 1
638
638
639 # query should use specified columns
639 # query should use specified columns
640 query = assigns(:query)
640 query = assigns(:query)
641 assert_kind_of Query, query
641 assert_kind_of Query, query
642 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
642 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
643 end
643 end
644
644
645 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
645 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
646 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
646 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
647 columns = ['tracker', 'subject', 'assigned_to']
647 columns = ['tracker', 'subject', 'assigned_to']
648 get :index, :set_filter => 1, :c => columns
648 get :index, :set_filter => 1, :c => columns
649
649
650 # query should use specified columns
650 # query should use specified columns
651 query = assigns(:query)
651 query = assigns(:query)
652 assert_kind_of Query, query
652 assert_kind_of Query, query
653 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
653 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
654 end
654 end
655
655
656 def test_index_with_custom_field_column
656 def test_index_with_custom_field_column
657 columns = %w(tracker subject cf_2)
657 columns = %w(tracker subject cf_2)
658 get :index, :set_filter => 1, :c => columns
658 get :index, :set_filter => 1, :c => columns
659 assert_response :success
659 assert_response :success
660
660
661 # query should use specified columns
661 # query should use specified columns
662 query = assigns(:query)
662 query = assigns(:query)
663 assert_kind_of Query, query
663 assert_kind_of Query, query
664 assert_equal columns, query.column_names.map(&:to_s)
664 assert_equal columns, query.column_names.map(&:to_s)
665
665
666 assert_tag :td,
666 assert_tag :td,
667 :attributes => {:class => 'cf_2 string'},
667 :attributes => {:class => 'cf_2 string'},
668 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
668 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
669 end
669 end
670
670
671 def test_index_with_multi_custom_field_column
671 def test_index_with_multi_custom_field_column
672 field = CustomField.find(1)
672 field = CustomField.find(1)
673 field.update_attribute :multiple, true
673 field.update_attribute :multiple, true
674 issue = Issue.find(1)
674 issue = Issue.find(1)
675 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
675 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
676 issue.save!
676 issue.save!
677
677
678 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
678 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
679 assert_response :success
679 assert_response :success
680
680
681 assert_tag :td,
681 assert_tag :td,
682 :attributes => {:class => /cf_1/},
682 :attributes => {:class => /cf_1/},
683 :content => 'MySQL, Oracle'
683 :content => 'MySQL, Oracle'
684 end
684 end
685
685
686 def test_index_with_multi_user_custom_field_column
686 def test_index_with_multi_user_custom_field_column
687 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
687 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
688 :tracker_ids => [1], :is_for_all => true)
688 :tracker_ids => [1], :is_for_all => true)
689 issue = Issue.find(1)
689 issue = Issue.find(1)
690 issue.custom_field_values = {field.id => ['2', '3']}
690 issue.custom_field_values = {field.id => ['2', '3']}
691 issue.save!
691 issue.save!
692
692
693 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
693 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
694 assert_response :success
694 assert_response :success
695
695
696 assert_tag :td,
696 assert_tag :td,
697 :attributes => {:class => /cf_#{field.id}/},
697 :attributes => {:class => /cf_#{field.id}/},
698 :child => {:tag => 'a', :content => 'John Smith'}
698 :child => {:tag => 'a', :content => 'John Smith'}
699 end
699 end
700
700
701 def test_index_with_date_column
701 def test_index_with_date_column
702 Issue.find(1).update_attribute :start_date, '1987-08-24'
702 Issue.find(1).update_attribute :start_date, '1987-08-24'
703
703
704 with_settings :date_format => '%d/%m/%Y' do
704 with_settings :date_format => '%d/%m/%Y' do
705 get :index, :set_filter => 1, :c => %w(start_date)
705 get :index, :set_filter => 1, :c => %w(start_date)
706 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
706 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
707 end
707 end
708 end
708 end
709
709
710 def test_index_with_done_ratio
710 def test_index_with_done_ratio
711 Issue.find(1).update_attribute :done_ratio, 40
711 Issue.find(1).update_attribute :done_ratio, 40
712
712
713 get :index, :set_filter => 1, :c => %w(done_ratio)
713 get :index, :set_filter => 1, :c => %w(done_ratio)
714 assert_tag 'td', :attributes => {:class => /done_ratio/},
714 assert_tag 'td', :attributes => {:class => /done_ratio/},
715 :child => {:tag => 'table', :attributes => {:class => 'progress'},
715 :child => {:tag => 'table', :attributes => {:class => 'progress'},
716 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
716 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
717 }
717 }
718 end
718 end
719
719
720 def test_index_with_spent_hours_column
720 def test_index_with_spent_hours_column
721 get :index, :set_filter => 1, :c => %w(subject spent_hours)
721 get :index, :set_filter => 1, :c => %w(subject spent_hours)
722
722
723 assert_tag 'tr', :attributes => {:id => 'issue-3'},
723 assert_tag 'tr', :attributes => {:id => 'issue-3'},
724 :child => {
724 :child => {
725 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
725 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
726 }
726 }
727 end
727 end
728
728
729 def test_index_should_not_show_spent_hours_column_without_permission
729 def test_index_should_not_show_spent_hours_column_without_permission
730 Role.anonymous.remove_permission! :view_time_entries
730 Role.anonymous.remove_permission! :view_time_entries
731 get :index, :set_filter => 1, :c => %w(subject spent_hours)
731 get :index, :set_filter => 1, :c => %w(subject spent_hours)
732
732
733 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
733 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
734 end
734 end
735
735
736 def test_index_with_fixed_version
736 def test_index_with_fixed_version
737 get :index, :set_filter => 1, :c => %w(fixed_version)
737 get :index, :set_filter => 1, :c => %w(fixed_version)
738 assert_tag 'td', :attributes => {:class => /fixed_version/},
738 assert_tag 'td', :attributes => {:class => /fixed_version/},
739 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
739 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
740 end
740 end
741
741
742 def test_index_send_html_if_query_is_invalid
742 def test_index_send_html_if_query_is_invalid
743 get :index, :f => ['start_date'], :op => {:start_date => '='}
743 get :index, :f => ['start_date'], :op => {:start_date => '='}
744 assert_equal 'text/html', @response.content_type
744 assert_equal 'text/html', @response.content_type
745 assert_template 'index'
745 assert_template 'index'
746 end
746 end
747
747
748 def test_index_send_nothing_if_query_is_invalid
748 def test_index_send_nothing_if_query_is_invalid
749 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
749 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
750 assert_equal 'text/csv', @response.content_type
750 assert_equal 'text/csv', @response.content_type
751 assert @response.body.blank?
751 assert @response.body.blank?
752 end
752 end
753
753
754 def test_show_by_anonymous
754 def test_show_by_anonymous
755 get :show, :id => 1
755 get :show, :id => 1
756 assert_response :success
756 assert_response :success
757 assert_template 'show'
757 assert_template 'show'
758 assert_not_nil assigns(:issue)
758 assert_not_nil assigns(:issue)
759 assert_equal Issue.find(1), assigns(:issue)
759 assert_equal Issue.find(1), assigns(:issue)
760
760
761 # anonymous role is allowed to add a note
761 # anonymous role is allowed to add a note
762 assert_tag :tag => 'form',
762 assert_tag :tag => 'form',
763 :descendant => { :tag => 'fieldset',
763 :descendant => { :tag => 'fieldset',
764 :child => { :tag => 'legend',
764 :child => { :tag => 'legend',
765 :content => /Notes/ } }
765 :content => /Notes/ } }
766 assert_tag :tag => 'title',
766 assert_tag :tag => 'title',
767 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
767 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
768 end
768 end
769
769
770 def test_show_by_manager
770 def test_show_by_manager
771 @request.session[:user_id] = 2
771 @request.session[:user_id] = 2
772 get :show, :id => 1
772 get :show, :id => 1
773 assert_response :success
773 assert_response :success
774
774
775 assert_tag :tag => 'a',
775 assert_tag :tag => 'a',
776 :content => /Quote/
776 :content => /Quote/
777
777
778 assert_tag :tag => 'form',
778 assert_tag :tag => 'form',
779 :descendant => { :tag => 'fieldset',
779 :descendant => { :tag => 'fieldset',
780 :child => { :tag => 'legend',
780 :child => { :tag => 'legend',
781 :content => /Change properties/ } },
781 :content => /Change properties/ } },
782 :descendant => { :tag => 'fieldset',
782 :descendant => { :tag => 'fieldset',
783 :child => { :tag => 'legend',
783 :child => { :tag => 'legend',
784 :content => /Log time/ } },
784 :content => /Log time/ } },
785 :descendant => { :tag => 'fieldset',
785 :descendant => { :tag => 'fieldset',
786 :child => { :tag => 'legend',
786 :child => { :tag => 'legend',
787 :content => /Notes/ } }
787 :content => /Notes/ } }
788 end
788 end
789
789
790 def test_show_should_display_update_form
790 def test_show_should_display_update_form
791 @request.session[:user_id] = 2
791 @request.session[:user_id] = 2
792 get :show, :id => 1
792 get :show, :id => 1
793 assert_response :success
793 assert_response :success
794
794
795 assert_tag 'form', :attributes => {:id => 'issue-form'}
795 assert_tag 'form', :attributes => {:id => 'issue-form'}
796 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
796 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
797 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
797 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
798 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
798 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
799 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
799 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
800 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
800 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
801 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
801 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
802 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
802 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
803 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
803 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
804 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
804 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
805 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
805 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
806 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
806 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
807 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
807 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
808 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
808 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
809 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
809 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
810 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
810 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
811 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
811 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
812 assert_tag 'textarea', :attributes => {:name => 'notes'}
812 assert_tag 'textarea', :attributes => {:name => 'notes'}
813 end
813 end
814
814
815 def test_show_should_display_update_form_with_minimal_permissions
815 def test_show_should_display_update_form_with_minimal_permissions
816 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
816 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
817 Workflow.delete_all :role_id => 1
817 Workflow.delete_all :role_id => 1
818
818
819 @request.session[:user_id] = 2
819 @request.session[:user_id] = 2
820 get :show, :id => 1
820 get :show, :id => 1
821 assert_response :success
821 assert_response :success
822
822
823 assert_tag 'form', :attributes => {:id => 'issue-form'}
823 assert_tag 'form', :attributes => {:id => 'issue-form'}
824 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
824 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
825 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
825 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
826 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
826 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
827 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
827 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
828 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
828 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
829 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
829 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
830 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
830 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
831 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
831 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
832 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
832 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
833 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
833 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
834 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
834 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
835 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
835 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
836 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
836 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
837 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
837 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
838 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
838 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
839 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
839 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
840 assert_tag 'textarea', :attributes => {:name => 'notes'}
840 assert_tag 'textarea', :attributes => {:name => 'notes'}
841 end
841 end
842
842
843 def test_show_should_display_update_form_with_workflow_permissions
843 def test_show_should_display_update_form_with_workflow_permissions
844 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
844 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
845
845
846 @request.session[:user_id] = 2
846 @request.session[:user_id] = 2
847 get :show, :id => 1
847 get :show, :id => 1
848 assert_response :success
848 assert_response :success
849
849
850 assert_tag 'form', :attributes => {:id => 'issue-form'}
850 assert_tag 'form', :attributes => {:id => 'issue-form'}
851 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
851 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
852 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
852 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
853 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
853 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
854 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
854 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
855 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
855 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
856 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
856 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
857 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
857 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
858 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
858 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
859 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
859 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
860 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
860 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
861 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
861 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
862 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
862 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
863 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
863 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
864 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
864 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
865 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
865 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
866 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
866 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
867 assert_tag 'textarea', :attributes => {:name => 'notes'}
867 assert_tag 'textarea', :attributes => {:name => 'notes'}
868 end
868 end
869
869
870 def test_show_should_not_display_update_form_without_permissions
870 def test_show_should_not_display_update_form_without_permissions
871 Role.find(1).update_attribute :permissions, [:view_issues]
871 Role.find(1).update_attribute :permissions, [:view_issues]
872
872
873 @request.session[:user_id] = 2
873 @request.session[:user_id] = 2
874 get :show, :id => 1
874 get :show, :id => 1
875 assert_response :success
875 assert_response :success
876
876
877 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
877 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
878 end
878 end
879
879
880 def test_update_form_should_not_display_inactive_enumerations
880 def test_update_form_should_not_display_inactive_enumerations
881 @request.session[:user_id] = 2
881 @request.session[:user_id] = 2
882 get :show, :id => 1
882 get :show, :id => 1
883 assert_response :success
883 assert_response :success
884
884
885 assert ! IssuePriority.find(15).active?
885 assert ! IssuePriority.find(15).active?
886 assert_no_tag :option, :attributes => {:value => '15'},
886 assert_no_tag :option, :attributes => {:value => '15'},
887 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
887 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
888 end
888 end
889
889
890 def test_update_form_should_allow_attachment_upload
890 def test_update_form_should_allow_attachment_upload
891 @request.session[:user_id] = 2
891 @request.session[:user_id] = 2
892 get :show, :id => 1
892 get :show, :id => 1
893
893
894 assert_tag :tag => 'form',
894 assert_tag :tag => 'form',
895 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
895 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
896 :descendant => {
896 :descendant => {
897 :tag => 'input',
897 :tag => 'input',
898 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
898 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
899 }
899 }
900 end
900 end
901
901
902 def test_show_should_deny_anonymous_access_without_permission
902 def test_show_should_deny_anonymous_access_without_permission
903 Role.anonymous.remove_permission!(:view_issues)
903 Role.anonymous.remove_permission!(:view_issues)
904 get :show, :id => 1
904 get :show, :id => 1
905 assert_response :redirect
905 assert_response :redirect
906 end
906 end
907
907
908 def test_show_should_deny_anonymous_access_to_private_issue
908 def test_show_should_deny_anonymous_access_to_private_issue
909 Issue.update_all(["is_private = ?", true], "id = 1")
909 Issue.update_all(["is_private = ?", true], "id = 1")
910 get :show, :id => 1
910 get :show, :id => 1
911 assert_response :redirect
911 assert_response :redirect
912 end
912 end
913
913
914 def test_show_should_deny_non_member_access_without_permission
914 def test_show_should_deny_non_member_access_without_permission
915 Role.non_member.remove_permission!(:view_issues)
915 Role.non_member.remove_permission!(:view_issues)
916 @request.session[:user_id] = 9
916 @request.session[:user_id] = 9
917 get :show, :id => 1
917 get :show, :id => 1
918 assert_response 403
918 assert_response 403
919 end
919 end
920
920
921 def test_show_should_deny_non_member_access_to_private_issue
921 def test_show_should_deny_non_member_access_to_private_issue
922 Issue.update_all(["is_private = ?", true], "id = 1")
922 Issue.update_all(["is_private = ?", true], "id = 1")
923 @request.session[:user_id] = 9
923 @request.session[:user_id] = 9
924 get :show, :id => 1
924 get :show, :id => 1
925 assert_response 403
925 assert_response 403
926 end
926 end
927
927
928 def test_show_should_deny_member_access_without_permission
928 def test_show_should_deny_member_access_without_permission
929 Role.find(1).remove_permission!(:view_issues)
929 Role.find(1).remove_permission!(:view_issues)
930 @request.session[:user_id] = 2
930 @request.session[:user_id] = 2
931 get :show, :id => 1
931 get :show, :id => 1
932 assert_response 403
932 assert_response 403
933 end
933 end
934
934
935 def test_show_should_deny_member_access_to_private_issue_without_permission
935 def test_show_should_deny_member_access_to_private_issue_without_permission
936 Issue.update_all(["is_private = ?", true], "id = 1")
936 Issue.update_all(["is_private = ?", true], "id = 1")
937 @request.session[:user_id] = 3
937 @request.session[:user_id] = 3
938 get :show, :id => 1
938 get :show, :id => 1
939 assert_response 403
939 assert_response 403
940 end
940 end
941
941
942 def test_show_should_allow_author_access_to_private_issue
942 def test_show_should_allow_author_access_to_private_issue
943 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
943 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
944 @request.session[:user_id] = 3
944 @request.session[:user_id] = 3
945 get :show, :id => 1
945 get :show, :id => 1
946 assert_response :success
946 assert_response :success
947 end
947 end
948
948
949 def test_show_should_allow_assignee_access_to_private_issue
949 def test_show_should_allow_assignee_access_to_private_issue
950 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
950 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
951 @request.session[:user_id] = 3
951 @request.session[:user_id] = 3
952 get :show, :id => 1
952 get :show, :id => 1
953 assert_response :success
953 assert_response :success
954 end
954 end
955
955
956 def test_show_should_allow_member_access_to_private_issue_with_permission
956 def test_show_should_allow_member_access_to_private_issue_with_permission
957 Issue.update_all(["is_private = ?", true], "id = 1")
957 Issue.update_all(["is_private = ?", true], "id = 1")
958 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
958 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
959 @request.session[:user_id] = 3
959 @request.session[:user_id] = 3
960 get :show, :id => 1
960 get :show, :id => 1
961 assert_response :success
961 assert_response :success
962 end
962 end
963
963
964 def test_show_should_not_disclose_relations_to_invisible_issues
964 def test_show_should_not_disclose_relations_to_invisible_issues
965 Setting.cross_project_issue_relations = '1'
965 Setting.cross_project_issue_relations = '1'
966 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
966 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
967 # Relation to a private project issue
967 # Relation to a private project issue
968 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
968 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
969
969
970 get :show, :id => 1
970 get :show, :id => 1
971 assert_response :success
971 assert_response :success
972
972
973 assert_tag :div, :attributes => { :id => 'relations' },
973 assert_tag :div, :attributes => { :id => 'relations' },
974 :descendant => { :tag => 'a', :content => /#2$/ }
974 :descendant => { :tag => 'a', :content => /#2$/ }
975 assert_no_tag :div, :attributes => { :id => 'relations' },
975 assert_no_tag :div, :attributes => { :id => 'relations' },
976 :descendant => { :tag => 'a', :content => /#4$/ }
976 :descendant => { :tag => 'a', :content => /#4$/ }
977 end
977 end
978
978
979 def test_show_should_list_subtasks
979 def test_show_should_list_subtasks
980 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
980 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
981
981
982 get :show, :id => 1
982 get :show, :id => 1
983 assert_response :success
983 assert_response :success
984 assert_tag 'div', :attributes => {:id => 'issue_tree'},
984 assert_tag 'div', :attributes => {:id => 'issue_tree'},
985 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
985 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
986 end
986 end
987
987
988 def test_show_should_list_parents
988 def test_show_should_list_parents
989 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
989 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
990
990
991 get :show, :id => issue.id
991 get :show, :id => issue.id
992 assert_response :success
992 assert_response :success
993 assert_tag 'div', :attributes => {:class => 'subject'},
993 assert_tag 'div', :attributes => {:class => 'subject'},
994 :descendant => {:tag => 'h3', :content => 'Child Issue'}
994 :descendant => {:tag => 'h3', :content => 'Child Issue'}
995 assert_tag 'div', :attributes => {:class => 'subject'},
995 assert_tag 'div', :attributes => {:class => 'subject'},
996 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
996 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
997 end
997 end
998
998
999 def test_show_should_not_display_prev_next_links_without_query_in_session
999 def test_show_should_not_display_prev_next_links_without_query_in_session
1000 get :show, :id => 1
1000 get :show, :id => 1
1001 assert_response :success
1001 assert_response :success
1002 assert_nil assigns(:prev_issue_id)
1002 assert_nil assigns(:prev_issue_id)
1003 assert_nil assigns(:next_issue_id)
1003 assert_nil assigns(:next_issue_id)
1004
1004
1005 assert_no_tag 'div', :attributes => {:class => /next-prev-links/}
1005 assert_no_tag 'div', :attributes => {:class => /next-prev-links/}
1006 end
1006 end
1007
1007
1008 def test_show_should_display_prev_next_links_with_query_in_session
1008 def test_show_should_display_prev_next_links_with_query_in_session
1009 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1009 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1010 @request.session['issues_index_sort'] = 'id'
1010 @request.session['issues_index_sort'] = 'id'
1011
1011
1012 with_settings :display_subprojects_issues => '0' do
1012 with_settings :display_subprojects_issues => '0' do
1013 get :show, :id => 3
1013 get :show, :id => 3
1014 end
1014 end
1015
1015
1016 assert_response :success
1016 assert_response :success
1017 # Previous and next issues for all projects
1017 # Previous and next issues for all projects
1018 assert_equal 2, assigns(:prev_issue_id)
1018 assert_equal 2, assigns(:prev_issue_id)
1019 assert_equal 5, assigns(:next_issue_id)
1019 assert_equal 5, assigns(:next_issue_id)
1020
1020
1021 assert_tag 'div', :attributes => {:class => /next-prev-links/}
1021 assert_tag 'div', :attributes => {:class => /next-prev-links/}
1022 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1022 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1023 assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
1023 assert_tag 'a', :attributes => {:href => '/issues/5'}, :content => /Next/
1024
1024
1025 count = Issue.open.visible.count
1025 count = Issue.open.visible.count
1026 assert_tag 'span', :attributes => {:class => 'position'}, :content => "3 of #{count}"
1026 assert_tag 'span', :attributes => {:class => 'position'}, :content => "3 of #{count}"
1027 end
1027 end
1028
1028
1029 def test_show_should_display_prev_next_links_with_saved_query_in_session
1029 def test_show_should_display_prev_next_links_with_saved_query_in_session
1030 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1,
1030 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1,
1031 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1031 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1032 :sort_criteria => [['id', 'asc']])
1032 :sort_criteria => [['id', 'asc']])
1033 @request.session[:query] = {:id => query.id, :project_id => nil}
1033 @request.session[:query] = {:id => query.id, :project_id => nil}
1034
1034
1035 get :show, :id => 11
1035 get :show, :id => 11
1036
1036
1037 assert_response :success
1037 assert_response :success
1038 assert_equal query, assigns(:query)
1038 assert_equal query, assigns(:query)
1039 # Previous and next issues for all projects
1039 # Previous and next issues for all projects
1040 assert_equal 8, assigns(:prev_issue_id)
1040 assert_equal 8, assigns(:prev_issue_id)
1041 assert_equal 12, assigns(:next_issue_id)
1041 assert_equal 12, assigns(:next_issue_id)
1042
1042
1043 assert_tag 'a', :attributes => {:href => '/issues/8'}, :content => /Previous/
1043 assert_tag 'a', :attributes => {:href => '/issues/8'}, :content => /Previous/
1044 assert_tag 'a', :attributes => {:href => '/issues/12'}, :content => /Next/
1044 assert_tag 'a', :attributes => {:href => '/issues/12'}, :content => /Next/
1045 end
1045 end
1046
1046
1047 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1047 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1048 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1048 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1049
1049
1050 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1050 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1051 @request.session['issues_index_sort'] = assoc_sort
1051 @request.session['issues_index_sort'] = assoc_sort
1052
1052
1053 get :show, :id => 3
1053 get :show, :id => 3
1054 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1054 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1055
1055
1056 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Previous/
1056 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Previous/
1057 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Next/
1057 assert_tag 'div', :attributes => {:class => /next-prev-links/}, :content => /Next/
1058 end
1058 end
1059 end
1059 end
1060
1060
1061 def test_show_should_display_prev_next_links_with_project_query_in_session
1061 def test_show_should_display_prev_next_links_with_project_query_in_session
1062 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1062 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1063 @request.session['issues_index_sort'] = 'id'
1063 @request.session['issues_index_sort'] = 'id'
1064
1064
1065 with_settings :display_subprojects_issues => '0' do
1065 with_settings :display_subprojects_issues => '0' do
1066 get :show, :id => 3
1066 get :show, :id => 3
1067 end
1067 end
1068
1068
1069 assert_response :success
1069 assert_response :success
1070 # Previous and next issues inside project
1070 # Previous and next issues inside project
1071 assert_equal 2, assigns(:prev_issue_id)
1071 assert_equal 2, assigns(:prev_issue_id)
1072 assert_equal 7, assigns(:next_issue_id)
1072 assert_equal 7, assigns(:next_issue_id)
1073
1073
1074 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1074 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Previous/
1075 assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
1075 assert_tag 'a', :attributes => {:href => '/issues/7'}, :content => /Next/
1076 end
1076 end
1077
1077
1078 def test_show_should_not_display_prev_link_for_first_issue
1078 def test_show_should_not_display_prev_link_for_first_issue
1079 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1079 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1080 @request.session['issues_index_sort'] = 'id'
1080 @request.session['issues_index_sort'] = 'id'
1081
1081
1082 with_settings :display_subprojects_issues => '0' do
1082 with_settings :display_subprojects_issues => '0' do
1083 get :show, :id => 1
1083 get :show, :id => 1
1084 end
1084 end
1085
1085
1086 assert_response :success
1086 assert_response :success
1087 assert_nil assigns(:prev_issue_id)
1087 assert_nil assigns(:prev_issue_id)
1088 assert_equal 2, assigns(:next_issue_id)
1088 assert_equal 2, assigns(:next_issue_id)
1089
1089
1090 assert_no_tag 'a', :content => /Previous/
1090 assert_no_tag 'a', :content => /Previous/
1091 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
1091 assert_tag 'a', :attributes => {:href => '/issues/2'}, :content => /Next/
1092 end
1092 end
1093
1093
1094 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1094 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1095 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1095 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1096 @request.session['issues_index_sort'] = 'id'
1096 @request.session['issues_index_sort'] = 'id'
1097
1097
1098 get :show, :id => 1
1098 get :show, :id => 1
1099
1099
1100 assert_response :success
1100 assert_response :success
1101 assert_nil assigns(:prev_issue_id)
1101 assert_nil assigns(:prev_issue_id)
1102 assert_nil assigns(:next_issue_id)
1102 assert_nil assigns(:next_issue_id)
1103
1103
1104 assert_no_tag 'a', :content => /Previous/
1104 assert_no_tag 'a', :content => /Previous/
1105 assert_no_tag 'a', :content => /Next/
1105 assert_no_tag 'a', :content => /Next/
1106 end
1106 end
1107
1107
1108 def test_show_should_display_visible_changesets_from_other_projects
1108 def test_show_should_display_visible_changesets_from_other_projects
1109 project = Project.find(2)
1109 project = Project.find(2)
1110 issue = project.issues.first
1110 issue = project.issues.first
1111 issue.changeset_ids = [102]
1111 issue.changeset_ids = [102]
1112 issue.save!
1112 issue.save!
1113 project.disable_module! :repository
1113 project.disable_module! :repository
1114
1114
1115 @request.session[:user_id] = 2
1115 @request.session[:user_id] = 2
1116 get :show, :id => issue.id
1116 get :show, :id => issue.id
1117 assert_tag 'a', :attributes => {:href => "/projects/ecookbook/repository/revisions/3"}
1117 assert_tag 'a', :attributes => {:href => "/projects/ecookbook/repository/revisions/3"}
1118 end
1118 end
1119
1119
1120 def test_show_with_multi_custom_field
1120 def test_show_with_multi_custom_field
1121 field = CustomField.find(1)
1121 field = CustomField.find(1)
1122 field.update_attribute :multiple, true
1122 field.update_attribute :multiple, true
1123 issue = Issue.find(1)
1123 issue = Issue.find(1)
1124 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1124 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1125 issue.save!
1125 issue.save!
1126
1126
1127 get :show, :id => 1
1127 get :show, :id => 1
1128 assert_response :success
1128 assert_response :success
1129
1129
1130 assert_tag :td, :content => 'MySQL, Oracle'
1130 assert_tag :td, :content => 'MySQL, Oracle'
1131 end
1131 end
1132
1132
1133 def test_show_with_multi_user_custom_field
1133 def test_show_with_multi_user_custom_field
1134 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1134 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1135 :tracker_ids => [1], :is_for_all => true)
1135 :tracker_ids => [1], :is_for_all => true)
1136 issue = Issue.find(1)
1136 issue = Issue.find(1)
1137 issue.custom_field_values = {field.id => ['2', '3']}
1137 issue.custom_field_values = {field.id => ['2', '3']}
1138 issue.save!
1138 issue.save!
1139
1139
1140 get :show, :id => 1
1140 get :show, :id => 1
1141 assert_response :success
1141 assert_response :success
1142
1142
1143 # TODO: should display links
1143 # TODO: should display links
1144 assert_tag :td, :content => 'Dave Lopper, John Smith'
1144 assert_tag :td, :content => 'Dave Lopper, John Smith'
1145 end
1145 end
1146
1146
1147 def test_show_atom
1147 def test_show_atom
1148 get :show, :id => 2, :format => 'atom'
1148 get :show, :id => 2, :format => 'atom'
1149 assert_response :success
1149 assert_response :success
1150 assert_template 'journals/index'
1150 assert_template 'journals/index'
1151 # Inline image
1151 # Inline image
1152 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1152 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1153 end
1153 end
1154
1154
1155 def test_show_export_to_pdf
1155 def test_show_export_to_pdf
1156 get :show, :id => 3, :format => 'pdf'
1156 get :show, :id => 3, :format => 'pdf'
1157 assert_response :success
1157 assert_response :success
1158 assert_equal 'application/pdf', @response.content_type
1158 assert_equal 'application/pdf', @response.content_type
1159 assert @response.body.starts_with?('%PDF')
1159 assert @response.body.starts_with?('%PDF')
1160 assert_not_nil assigns(:issue)
1160 assert_not_nil assigns(:issue)
1161 end
1161 end
1162
1162
1163 def test_get_new
1163 def test_get_new
1164 @request.session[:user_id] = 2
1164 @request.session[:user_id] = 2
1165 get :new, :project_id => 1, :tracker_id => 1
1165 get :new, :project_id => 1, :tracker_id => 1
1166 assert_response :success
1166 assert_response :success
1167 assert_template 'new'
1167 assert_template 'new'
1168
1168
1169 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1169 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1170 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1170 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1171 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1171 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1172 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1172 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1173 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1173 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1174 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1174 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1175 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1175 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1176 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1176 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1177 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1177 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1178 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1178 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1179 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1179 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1180 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1180 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1181 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1181 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1182 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1182 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1183 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1183 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1184 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1184 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1185
1185
1186 # Be sure we don't display inactive IssuePriorities
1186 # Be sure we don't display inactive IssuePriorities
1187 assert ! IssuePriority.find(15).active?
1187 assert ! IssuePriority.find(15).active?
1188 assert_no_tag :option, :attributes => {:value => '15'},
1188 assert_no_tag :option, :attributes => {:value => '15'},
1189 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1189 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1190 end
1190 end
1191
1191
1192 def test_get_new_with_minimal_permissions
1192 def test_get_new_with_minimal_permissions
1193 Role.find(1).update_attribute :permissions, [:add_issues]
1193 Role.find(1).update_attribute :permissions, [:add_issues]
1194 Workflow.delete_all :role_id => 1
1194 Workflow.delete_all :role_id => 1
1195
1195
1196 @request.session[:user_id] = 2
1196 @request.session[:user_id] = 2
1197 get :new, :project_id => 1, :tracker_id => 1
1197 get :new, :project_id => 1, :tracker_id => 1
1198 assert_response :success
1198 assert_response :success
1199 assert_template 'new'
1199 assert_template 'new'
1200
1200
1201 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1201 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1202 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1202 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1203 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1203 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1204 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1204 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1205 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1205 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1206 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1206 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1207 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1207 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1208 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1208 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1209 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1209 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1210 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1210 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1211 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1211 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1212 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1212 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1213 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1213 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1214 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1214 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1215 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1215 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1216 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1216 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1217 end
1217 end
1218
1218
1219 def test_get_new_with_list_custom_field
1219 def test_get_new_with_list_custom_field
1220 @request.session[:user_id] = 2
1220 @request.session[:user_id] = 2
1221 get :new, :project_id => 1, :tracker_id => 1
1221 get :new, :project_id => 1, :tracker_id => 1
1222 assert_response :success
1222 assert_response :success
1223 assert_template 'new'
1223 assert_template 'new'
1224
1224
1225 assert_tag 'select',
1225 assert_tag 'select',
1226 :attributes => {:name => 'issue[custom_field_values][1]'},
1226 :attributes => {:name => 'issue[custom_field_values][1]'},
1227 :children => {:count => 4},
1227 :children => {:count => 4},
1228 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1228 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1229 end
1229 end
1230
1230
1231 def test_get_new_with_multi_custom_field
1231 def test_get_new_with_multi_custom_field
1232 field = IssueCustomField.find(1)
1232 field = IssueCustomField.find(1)
1233 field.update_attribute :multiple, true
1233 field.update_attribute :multiple, true
1234
1234
1235 @request.session[:user_id] = 2
1235 @request.session[:user_id] = 2
1236 get :new, :project_id => 1, :tracker_id => 1
1236 get :new, :project_id => 1, :tracker_id => 1
1237 assert_response :success
1237 assert_response :success
1238 assert_template 'new'
1238 assert_template 'new'
1239
1239
1240 assert_tag 'select',
1240 assert_tag 'select',
1241 :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'},
1241 :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'},
1242 :children => {:count => 3},
1242 :children => {:count => 3},
1243 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1243 :child => {:tag => 'option', :attributes => {:value => 'MySQL'}, :content => 'MySQL'}
1244 assert_tag 'input',
1244 assert_tag 'input',
1245 :attributes => {:name => 'issue[custom_field_values][1][]', :value => ''}
1245 :attributes => {:name => 'issue[custom_field_values][1][]', :value => ''}
1246 end
1246 end
1247
1247
1248 def test_get_new_with_multi_user_custom_field
1248 def test_get_new_with_multi_user_custom_field
1249 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1249 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1250 :tracker_ids => [1], :is_for_all => true)
1250 :tracker_ids => [1], :is_for_all => true)
1251
1251
1252 @request.session[:user_id] = 2
1252 @request.session[:user_id] = 2
1253 get :new, :project_id => 1, :tracker_id => 1
1253 get :new, :project_id => 1, :tracker_id => 1
1254 assert_response :success
1254 assert_response :success
1255 assert_template 'new'
1255 assert_template 'new'
1256
1256
1257 assert_tag 'select',
1257 assert_tag 'select',
1258 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :multiple => 'multiple'},
1258 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :multiple => 'multiple'},
1259 :children => {:count => Project.find(1).users.count},
1259 :children => {:count => Project.find(1).users.count},
1260 :child => {:tag => 'option', :attributes => {:value => '2'}, :content => 'John Smith'}
1260 :child => {:tag => 'option', :attributes => {:value => '2'}, :content => 'John Smith'}
1261 assert_tag 'input',
1261 assert_tag 'input',
1262 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :value => ''}
1262 :attributes => {:name => "issue[custom_field_values][#{field.id}][]", :value => ''}
1263 end
1263 end
1264
1264
1265 def test_get_new_without_default_start_date_is_creation_date
1265 def test_get_new_without_default_start_date_is_creation_date
1266 Setting.default_issue_start_date_to_creation_date = 0
1266 Setting.default_issue_start_date_to_creation_date = 0
1267
1267
1268 @request.session[:user_id] = 2
1268 @request.session[:user_id] = 2
1269 get :new, :project_id => 1, :tracker_id => 1
1269 get :new, :project_id => 1, :tracker_id => 1
1270 assert_response :success
1270 assert_response :success
1271 assert_template 'new'
1271 assert_template 'new'
1272
1272
1273 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1273 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1274 :value => nil }
1274 :value => nil }
1275 end
1275 end
1276
1276
1277 def test_get_new_with_default_start_date_is_creation_date
1277 def test_get_new_with_default_start_date_is_creation_date
1278 Setting.default_issue_start_date_to_creation_date = 1
1278 Setting.default_issue_start_date_to_creation_date = 1
1279
1279
1280 @request.session[:user_id] = 2
1280 @request.session[:user_id] = 2
1281 get :new, :project_id => 1, :tracker_id => 1
1281 get :new, :project_id => 1, :tracker_id => 1
1282 assert_response :success
1282 assert_response :success
1283 assert_template 'new'
1283 assert_template 'new'
1284
1284
1285 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1285 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
1286 :value => Date.today.to_s }
1286 :value => Date.today.to_s }
1287 end
1287 end
1288
1288
1289 def test_get_new_form_should_allow_attachment_upload
1289 def test_get_new_form_should_allow_attachment_upload
1290 @request.session[:user_id] = 2
1290 @request.session[:user_id] = 2
1291 get :new, :project_id => 1, :tracker_id => 1
1291 get :new, :project_id => 1, :tracker_id => 1
1292
1292
1293 assert_tag :tag => 'form',
1293 assert_tag :tag => 'form',
1294 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1294 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
1295 :descendant => {
1295 :descendant => {
1296 :tag => 'input',
1296 :tag => 'input',
1297 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1297 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
1298 }
1298 }
1299 end
1299 end
1300
1300
1301 def test_get_new_should_prefill_the_form_from_params
1301 def test_get_new_should_prefill_the_form_from_params
1302 @request.session[:user_id] = 2
1302 @request.session[:user_id] = 2
1303 get :new, :project_id => 1,
1303 get :new, :project_id => 1,
1304 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1304 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1305
1305
1306 issue = assigns(:issue)
1306 issue = assigns(:issue)
1307 assert_equal 3, issue.tracker_id
1307 assert_equal 3, issue.tracker_id
1308 assert_equal 'Prefilled', issue.description
1308 assert_equal 'Prefilled', issue.description
1309 assert_equal 'Custom field value', issue.custom_field_value(2)
1309 assert_equal 'Custom field value', issue.custom_field_value(2)
1310
1310
1311 assert_tag 'select',
1311 assert_tag 'select',
1312 :attributes => {:name => 'issue[tracker_id]'},
1312 :attributes => {:name => 'issue[tracker_id]'},
1313 :child => {:tag => 'option', :attributes => {:value => '3', :selected => 'selected'}}
1313 :child => {:tag => 'option', :attributes => {:value => '3', :selected => 'selected'}}
1314 assert_tag 'textarea',
1314 assert_tag 'textarea',
1315 :attributes => {:name => 'issue[description]'}, :content => 'Prefilled'
1315 :attributes => {:name => 'issue[description]'}, :content => 'Prefilled'
1316 assert_tag 'input',
1316 assert_tag 'input',
1317 :attributes => {:name => 'issue[custom_field_values][2]', :value => 'Custom field value'}
1317 :attributes => {:name => 'issue[custom_field_values][2]', :value => 'Custom field value'}
1318 end
1318 end
1319
1319
1320 def test_get_new_without_tracker_id
1320 def test_get_new_without_tracker_id
1321 @request.session[:user_id] = 2
1321 @request.session[:user_id] = 2
1322 get :new, :project_id => 1
1322 get :new, :project_id => 1
1323 assert_response :success
1323 assert_response :success
1324 assert_template 'new'
1324 assert_template 'new'
1325
1325
1326 issue = assigns(:issue)
1326 issue = assigns(:issue)
1327 assert_not_nil issue
1327 assert_not_nil issue
1328 assert_equal Project.find(1).trackers.first, issue.tracker
1328 assert_equal Project.find(1).trackers.first, issue.tracker
1329 end
1329 end
1330
1330
1331 def test_get_new_with_no_default_status_should_display_an_error
1331 def test_get_new_with_no_default_status_should_display_an_error
1332 @request.session[:user_id] = 2
1332 @request.session[:user_id] = 2
1333 IssueStatus.delete_all
1333 IssueStatus.delete_all
1334
1334
1335 get :new, :project_id => 1
1335 get :new, :project_id => 1
1336 assert_response 500
1336 assert_response 500
1337 assert_error_tag :content => /No default issue/
1337 assert_error_tag :content => /No default issue/
1338 end
1338 end
1339
1339
1340 def test_get_new_with_no_tracker_should_display_an_error
1340 def test_get_new_with_no_tracker_should_display_an_error
1341 @request.session[:user_id] = 2
1341 @request.session[:user_id] = 2
1342 Tracker.delete_all
1342 Tracker.delete_all
1343
1343
1344 get :new, :project_id => 1
1344 get :new, :project_id => 1
1345 assert_response 500
1345 assert_response 500
1346 assert_error_tag :content => /No tracker/
1346 assert_error_tag :content => /No tracker/
1347 end
1347 end
1348
1348
1349 def test_update_new_form
1349 def test_update_new_form
1350 @request.session[:user_id] = 2
1350 @request.session[:user_id] = 2
1351 xhr :post, :new, :project_id => 1,
1351 xhr :post, :new, :project_id => 1,
1352 :issue => {:tracker_id => 2,
1352 :issue => {:tracker_id => 2,
1353 :subject => 'This is the test_new issue',
1353 :subject => 'This is the test_new issue',
1354 :description => 'This is the description',
1354 :description => 'This is the description',
1355 :priority_id => 5}
1355 :priority_id => 5}
1356 assert_response :success
1356 assert_response :success
1357 assert_template 'attributes'
1357 assert_template 'attributes'
1358
1358
1359 issue = assigns(:issue)
1359 issue = assigns(:issue)
1360 assert_kind_of Issue, issue
1360 assert_kind_of Issue, issue
1361 assert_equal 1, issue.project_id
1361 assert_equal 1, issue.project_id
1362 assert_equal 2, issue.tracker_id
1362 assert_equal 2, issue.tracker_id
1363 assert_equal 'This is the test_new issue', issue.subject
1363 assert_equal 'This is the test_new issue', issue.subject
1364 end
1364 end
1365
1365
1366 def test_post_create
1366 def test_post_create
1367 @request.session[:user_id] = 2
1367 @request.session[:user_id] = 2
1368 assert_difference 'Issue.count' do
1368 assert_difference 'Issue.count' do
1369 post :create, :project_id => 1,
1369 post :create, :project_id => 1,
1370 :issue => {:tracker_id => 3,
1370 :issue => {:tracker_id => 3,
1371 :status_id => 2,
1371 :status_id => 2,
1372 :subject => 'This is the test_new issue',
1372 :subject => 'This is the test_new issue',
1373 :description => 'This is the description',
1373 :description => 'This is the description',
1374 :priority_id => 5,
1374 :priority_id => 5,
1375 :start_date => '2010-11-07',
1375 :start_date => '2010-11-07',
1376 :estimated_hours => '',
1376 :estimated_hours => '',
1377 :custom_field_values => {'2' => 'Value for field 2'}}
1377 :custom_field_values => {'2' => 'Value for field 2'}}
1378 end
1378 end
1379 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1379 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1380
1380
1381 issue = Issue.find_by_subject('This is the test_new issue')
1381 issue = Issue.find_by_subject('This is the test_new issue')
1382 assert_not_nil issue
1382 assert_not_nil issue
1383 assert_equal 2, issue.author_id
1383 assert_equal 2, issue.author_id
1384 assert_equal 3, issue.tracker_id
1384 assert_equal 3, issue.tracker_id
1385 assert_equal 2, issue.status_id
1385 assert_equal 2, issue.status_id
1386 assert_equal Date.parse('2010-11-07'), issue.start_date
1386 assert_equal Date.parse('2010-11-07'), issue.start_date
1387 assert_nil issue.estimated_hours
1387 assert_nil issue.estimated_hours
1388 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1388 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1389 assert_not_nil v
1389 assert_not_nil v
1390 assert_equal 'Value for field 2', v.value
1390 assert_equal 'Value for field 2', v.value
1391 end
1391 end
1392
1392
1393 def test_post_new_with_group_assignment
1393 def test_post_new_with_group_assignment
1394 group = Group.find(11)
1394 group = Group.find(11)
1395 project = Project.find(1)
1395 project = Project.find(1)
1396 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1396 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1397
1397
1398 with_settings :issue_group_assignment => '1' do
1398 with_settings :issue_group_assignment => '1' do
1399 @request.session[:user_id] = 2
1399 @request.session[:user_id] = 2
1400 assert_difference 'Issue.count' do
1400 assert_difference 'Issue.count' do
1401 post :create, :project_id => project.id,
1401 post :create, :project_id => project.id,
1402 :issue => {:tracker_id => 3,
1402 :issue => {:tracker_id => 3,
1403 :status_id => 1,
1403 :status_id => 1,
1404 :subject => 'This is the test_new_with_group_assignment issue',
1404 :subject => 'This is the test_new_with_group_assignment issue',
1405 :assigned_to_id => group.id}
1405 :assigned_to_id => group.id}
1406 end
1406 end
1407 end
1407 end
1408 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1408 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1409
1409
1410 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1410 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1411 assert_not_nil issue
1411 assert_not_nil issue
1412 assert_equal group, issue.assigned_to
1412 assert_equal group, issue.assigned_to
1413 end
1413 end
1414
1414
1415 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1415 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1416 Setting.default_issue_start_date_to_creation_date = 0
1416 Setting.default_issue_start_date_to_creation_date = 0
1417
1417
1418 @request.session[:user_id] = 2
1418 @request.session[:user_id] = 2
1419 assert_difference 'Issue.count' do
1419 assert_difference 'Issue.count' do
1420 post :create, :project_id => 1,
1420 post :create, :project_id => 1,
1421 :issue => {:tracker_id => 3,
1421 :issue => {:tracker_id => 3,
1422 :status_id => 2,
1422 :status_id => 2,
1423 :subject => 'This is the test_new issue',
1423 :subject => 'This is the test_new issue',
1424 :description => 'This is the description',
1424 :description => 'This is the description',
1425 :priority_id => 5,
1425 :priority_id => 5,
1426 :estimated_hours => '',
1426 :estimated_hours => '',
1427 :custom_field_values => {'2' => 'Value for field 2'}}
1427 :custom_field_values => {'2' => 'Value for field 2'}}
1428 end
1428 end
1429 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1429 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1430
1430
1431 issue = Issue.find_by_subject('This is the test_new issue')
1431 issue = Issue.find_by_subject('This is the test_new issue')
1432 assert_not_nil issue
1432 assert_not_nil issue
1433 assert_nil issue.start_date
1433 assert_nil issue.start_date
1434 end
1434 end
1435
1435
1436 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1436 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1437 Setting.default_issue_start_date_to_creation_date = 1
1437 Setting.default_issue_start_date_to_creation_date = 1
1438
1438
1439 @request.session[:user_id] = 2
1439 @request.session[:user_id] = 2
1440 assert_difference 'Issue.count' do
1440 assert_difference 'Issue.count' do
1441 post :create, :project_id => 1,
1441 post :create, :project_id => 1,
1442 :issue => {:tracker_id => 3,
1442 :issue => {:tracker_id => 3,
1443 :status_id => 2,
1443 :status_id => 2,
1444 :subject => 'This is the test_new issue',
1444 :subject => 'This is the test_new issue',
1445 :description => 'This is the description',
1445 :description => 'This is the description',
1446 :priority_id => 5,
1446 :priority_id => 5,
1447 :estimated_hours => '',
1447 :estimated_hours => '',
1448 :custom_field_values => {'2' => 'Value for field 2'}}
1448 :custom_field_values => {'2' => 'Value for field 2'}}
1449 end
1449 end
1450 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1450 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1451
1451
1452 issue = Issue.find_by_subject('This is the test_new issue')
1452 issue = Issue.find_by_subject('This is the test_new issue')
1453 assert_not_nil issue
1453 assert_not_nil issue
1454 assert_equal Date.today, issue.start_date
1454 assert_equal Date.today, issue.start_date
1455 end
1455 end
1456
1456
1457 def test_post_create_and_continue
1457 def test_post_create_and_continue
1458 @request.session[:user_id] = 2
1458 @request.session[:user_id] = 2
1459 assert_difference 'Issue.count' do
1459 assert_difference 'Issue.count' do
1460 post :create, :project_id => 1,
1460 post :create, :project_id => 1,
1461 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1461 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1462 :continue => ''
1462 :continue => ''
1463 end
1463 end
1464
1464
1465 issue = Issue.first(:order => 'id DESC')
1465 issue = Issue.first(:order => 'id DESC')
1466 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1466 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1467 assert_not_nil flash[:notice], "flash was not set"
1467 assert_not_nil flash[:notice], "flash was not set"
1468 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1468 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1469 end
1469 end
1470
1470
1471 def test_post_create_without_custom_fields_param
1471 def test_post_create_without_custom_fields_param
1472 @request.session[:user_id] = 2
1472 @request.session[:user_id] = 2
1473 assert_difference 'Issue.count' do
1473 assert_difference 'Issue.count' do
1474 post :create, :project_id => 1,
1474 post :create, :project_id => 1,
1475 :issue => {:tracker_id => 1,
1475 :issue => {:tracker_id => 1,
1476 :subject => 'This is the test_new issue',
1476 :subject => 'This is the test_new issue',
1477 :description => 'This is the description',
1477 :description => 'This is the description',
1478 :priority_id => 5}
1478 :priority_id => 5}
1479 end
1479 end
1480 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1480 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1481 end
1481 end
1482
1482
1483 def test_post_create_with_multi_custom_field
1483 def test_post_create_with_multi_custom_field
1484 field = IssueCustomField.find_by_name('Database')
1484 field = IssueCustomField.find_by_name('Database')
1485 field.update_attribute(:multiple, true)
1485 field.update_attribute(:multiple, true)
1486
1486
1487 @request.session[:user_id] = 2
1487 @request.session[:user_id] = 2
1488 assert_difference 'Issue.count' do
1488 assert_difference 'Issue.count' do
1489 post :create, :project_id => 1,
1489 post :create, :project_id => 1,
1490 :issue => {:tracker_id => 1,
1490 :issue => {:tracker_id => 1,
1491 :subject => 'This is the test_new issue',
1491 :subject => 'This is the test_new issue',
1492 :description => 'This is the description',
1492 :description => 'This is the description',
1493 :priority_id => 5,
1493 :priority_id => 5,
1494 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1494 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1495 end
1495 end
1496 assert_response 302
1496 assert_response 302
1497 issue = Issue.first(:order => 'id DESC')
1497 issue = Issue.first(:order => 'id DESC')
1498 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1498 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1499 end
1499 end
1500
1500
1501 def test_post_create_with_empty_multi_custom_field
1501 def test_post_create_with_empty_multi_custom_field
1502 field = IssueCustomField.find_by_name('Database')
1502 field = IssueCustomField.find_by_name('Database')
1503 field.update_attribute(:multiple, true)
1503 field.update_attribute(:multiple, true)
1504
1504
1505 @request.session[:user_id] = 2
1505 @request.session[:user_id] = 2
1506 assert_difference 'Issue.count' do
1506 assert_difference 'Issue.count' do
1507 post :create, :project_id => 1,
1507 post :create, :project_id => 1,
1508 :issue => {:tracker_id => 1,
1508 :issue => {:tracker_id => 1,
1509 :subject => 'This is the test_new issue',
1509 :subject => 'This is the test_new issue',
1510 :description => 'This is the description',
1510 :description => 'This is the description',
1511 :priority_id => 5,
1511 :priority_id => 5,
1512 :custom_field_values => {'1' => ['']}}
1512 :custom_field_values => {'1' => ['']}}
1513 end
1513 end
1514 assert_response 302
1514 assert_response 302
1515 issue = Issue.first(:order => 'id DESC')
1515 issue = Issue.first(:order => 'id DESC')
1516 assert_equal [''], issue.custom_field_value(1).sort
1516 assert_equal [''], issue.custom_field_value(1).sort
1517 end
1517 end
1518
1518
1519 def test_post_create_with_multi_user_custom_field
1519 def test_post_create_with_multi_user_custom_field
1520 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1520 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1521 :tracker_ids => [1], :is_for_all => true)
1521 :tracker_ids => [1], :is_for_all => true)
1522
1522
1523 @request.session[:user_id] = 2
1523 @request.session[:user_id] = 2
1524 assert_difference 'Issue.count' do
1524 assert_difference 'Issue.count' do
1525 post :create, :project_id => 1,
1525 post :create, :project_id => 1,
1526 :issue => {:tracker_id => 1,
1526 :issue => {:tracker_id => 1,
1527 :subject => 'This is the test_new issue',
1527 :subject => 'This is the test_new issue',
1528 :description => 'This is the description',
1528 :description => 'This is the description',
1529 :priority_id => 5,
1529 :priority_id => 5,
1530 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1530 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1531 end
1531 end
1532 assert_response 302
1532 assert_response 302
1533 issue = Issue.first(:order => 'id DESC')
1533 issue = Issue.first(:order => 'id DESC')
1534 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1534 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1535 end
1535 end
1536
1536
1537 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1537 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1538 field = IssueCustomField.find_by_name('Database')
1538 field = IssueCustomField.find_by_name('Database')
1539 field.update_attribute(:is_required, true)
1539 field.update_attribute(:is_required, true)
1540
1540
1541 @request.session[:user_id] = 2
1541 @request.session[:user_id] = 2
1542 assert_no_difference 'Issue.count' do
1542 assert_no_difference 'Issue.count' do
1543 post :create, :project_id => 1,
1543 post :create, :project_id => 1,
1544 :issue => {:tracker_id => 1,
1544 :issue => {:tracker_id => 1,
1545 :subject => 'This is the test_new issue',
1545 :subject => 'This is the test_new issue',
1546 :description => 'This is the description',
1546 :description => 'This is the description',
1547 :priority_id => 5}
1547 :priority_id => 5}
1548 end
1548 end
1549 assert_response :success
1549 assert_response :success
1550 assert_template 'new'
1550 assert_template 'new'
1551 issue = assigns(:issue)
1551 issue = assigns(:issue)
1552 assert_not_nil issue
1552 assert_not_nil issue
1553 assert_error_tag :content => /Database can't be blank/
1553 assert_error_tag :content => /Database can't be blank/
1554 end
1554 end
1555
1555
1556 def test_post_create_with_watchers
1556 def test_post_create_with_watchers
1557 @request.session[:user_id] = 2
1557 @request.session[:user_id] = 2
1558 ActionMailer::Base.deliveries.clear
1558 ActionMailer::Base.deliveries.clear
1559
1559
1560 assert_difference 'Watcher.count', 2 do
1560 assert_difference 'Watcher.count', 2 do
1561 post :create, :project_id => 1,
1561 post :create, :project_id => 1,
1562 :issue => {:tracker_id => 1,
1562 :issue => {:tracker_id => 1,
1563 :subject => 'This is a new issue with watchers',
1563 :subject => 'This is a new issue with watchers',
1564 :description => 'This is the description',
1564 :description => 'This is the description',
1565 :priority_id => 5,
1565 :priority_id => 5,
1566 :watcher_user_ids => ['2', '3']}
1566 :watcher_user_ids => ['2', '3']}
1567 end
1567 end
1568 issue = Issue.find_by_subject('This is a new issue with watchers')
1568 issue = Issue.find_by_subject('This is a new issue with watchers')
1569 assert_not_nil issue
1569 assert_not_nil issue
1570 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1570 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1571
1571
1572 # Watchers added
1572 # Watchers added
1573 assert_equal [2, 3], issue.watcher_user_ids.sort
1573 assert_equal [2, 3], issue.watcher_user_ids.sort
1574 assert issue.watched_by?(User.find(3))
1574 assert issue.watched_by?(User.find(3))
1575 # Watchers notified
1575 # Watchers notified
1576 mail = ActionMailer::Base.deliveries.last
1576 mail = ActionMailer::Base.deliveries.last
1577 assert_not_nil mail
1577 assert_not_nil mail
1578 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1578 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1579 end
1579 end
1580
1580
1581 def test_post_create_subissue
1581 def test_post_create_subissue
1582 @request.session[:user_id] = 2
1582 @request.session[:user_id] = 2
1583
1583
1584 assert_difference 'Issue.count' do
1584 assert_difference 'Issue.count' do
1585 post :create, :project_id => 1,
1585 post :create, :project_id => 1,
1586 :issue => {:tracker_id => 1,
1586 :issue => {:tracker_id => 1,
1587 :subject => 'This is a child issue',
1587 :subject => 'This is a child issue',
1588 :parent_issue_id => 2}
1588 :parent_issue_id => 2}
1589 end
1589 end
1590 issue = Issue.find_by_subject('This is a child issue')
1590 issue = Issue.find_by_subject('This is a child issue')
1591 assert_not_nil issue
1591 assert_not_nil issue
1592 assert_equal Issue.find(2), issue.parent
1592 assert_equal Issue.find(2), issue.parent
1593 end
1593 end
1594
1594
1595 def test_post_create_subissue_with_non_numeric_parent_id
1595 def test_post_create_subissue_with_non_numeric_parent_id
1596 @request.session[:user_id] = 2
1596 @request.session[:user_id] = 2
1597
1597
1598 assert_difference 'Issue.count' do
1598 assert_difference 'Issue.count' do
1599 post :create, :project_id => 1,
1599 post :create, :project_id => 1,
1600 :issue => {:tracker_id => 1,
1600 :issue => {:tracker_id => 1,
1601 :subject => 'This is a child issue',
1601 :subject => 'This is a child issue',
1602 :parent_issue_id => 'ABC'}
1602 :parent_issue_id => 'ABC'}
1603 end
1603 end
1604 issue = Issue.find_by_subject('This is a child issue')
1604 issue = Issue.find_by_subject('This is a child issue')
1605 assert_not_nil issue
1605 assert_not_nil issue
1606 assert_nil issue.parent
1606 assert_nil issue.parent
1607 end
1607 end
1608
1608
1609 def test_post_create_private
1609 def test_post_create_private
1610 @request.session[:user_id] = 2
1610 @request.session[:user_id] = 2
1611
1611
1612 assert_difference 'Issue.count' do
1612 assert_difference 'Issue.count' do
1613 post :create, :project_id => 1,
1613 post :create, :project_id => 1,
1614 :issue => {:tracker_id => 1,
1614 :issue => {:tracker_id => 1,
1615 :subject => 'This is a private issue',
1615 :subject => 'This is a private issue',
1616 :is_private => '1'}
1616 :is_private => '1'}
1617 end
1617 end
1618 issue = Issue.first(:order => 'id DESC')
1618 issue = Issue.first(:order => 'id DESC')
1619 assert issue.is_private?
1619 assert issue.is_private?
1620 end
1620 end
1621
1621
1622 def test_post_create_private_with_set_own_issues_private_permission
1622 def test_post_create_private_with_set_own_issues_private_permission
1623 role = Role.find(1)
1623 role = Role.find(1)
1624 role.remove_permission! :set_issues_private
1624 role.remove_permission! :set_issues_private
1625 role.add_permission! :set_own_issues_private
1625 role.add_permission! :set_own_issues_private
1626
1626
1627 @request.session[:user_id] = 2
1627 @request.session[:user_id] = 2
1628
1628
1629 assert_difference 'Issue.count' do
1629 assert_difference 'Issue.count' do
1630 post :create, :project_id => 1,
1630 post :create, :project_id => 1,
1631 :issue => {:tracker_id => 1,
1631 :issue => {:tracker_id => 1,
1632 :subject => 'This is a private issue',
1632 :subject => 'This is a private issue',
1633 :is_private => '1'}
1633 :is_private => '1'}
1634 end
1634 end
1635 issue = Issue.first(:order => 'id DESC')
1635 issue = Issue.first(:order => 'id DESC')
1636 assert issue.is_private?
1636 assert issue.is_private?
1637 end
1637 end
1638
1638
1639 def test_post_create_should_send_a_notification
1639 def test_post_create_should_send_a_notification
1640 ActionMailer::Base.deliveries.clear
1640 ActionMailer::Base.deliveries.clear
1641 @request.session[:user_id] = 2
1641 @request.session[:user_id] = 2
1642 assert_difference 'Issue.count' do
1642 assert_difference 'Issue.count' do
1643 post :create, :project_id => 1,
1643 post :create, :project_id => 1,
1644 :issue => {:tracker_id => 3,
1644 :issue => {:tracker_id => 3,
1645 :subject => 'This is the test_new issue',
1645 :subject => 'This is the test_new issue',
1646 :description => 'This is the description',
1646 :description => 'This is the description',
1647 :priority_id => 5,
1647 :priority_id => 5,
1648 :estimated_hours => '',
1648 :estimated_hours => '',
1649 :custom_field_values => {'2' => 'Value for field 2'}}
1649 :custom_field_values => {'2' => 'Value for field 2'}}
1650 end
1650 end
1651 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1651 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1652
1652
1653 assert_equal 1, ActionMailer::Base.deliveries.size
1653 assert_equal 1, ActionMailer::Base.deliveries.size
1654 end
1654 end
1655
1655
1656 def test_post_create_should_preserve_fields_values_on_validation_failure
1656 def test_post_create_should_preserve_fields_values_on_validation_failure
1657 @request.session[:user_id] = 2
1657 @request.session[:user_id] = 2
1658 post :create, :project_id => 1,
1658 post :create, :project_id => 1,
1659 :issue => {:tracker_id => 1,
1659 :issue => {:tracker_id => 1,
1660 # empty subject
1660 # empty subject
1661 :subject => '',
1661 :subject => '',
1662 :description => 'This is a description',
1662 :description => 'This is a description',
1663 :priority_id => 6,
1663 :priority_id => 6,
1664 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1664 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1665 assert_response :success
1665 assert_response :success
1666 assert_template 'new'
1666 assert_template 'new'
1667
1667
1668 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1668 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1669 :content => 'This is a description'
1669 :content => 'This is a description'
1670 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1670 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1671 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1671 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1672 :value => '6' },
1672 :value => '6' },
1673 :content => 'High' }
1673 :content => 'High' }
1674 # Custom fields
1674 # Custom fields
1675 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1675 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1676 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1676 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1677 :value => 'Oracle' },
1677 :value => 'Oracle' },
1678 :content => 'Oracle' }
1678 :content => 'Oracle' }
1679 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1679 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1680 :value => 'Value for field 2'}
1680 :value => 'Value for field 2'}
1681 end
1681 end
1682
1682
1683 def test_post_create_with_failure_should_preserve_watchers
1684 assert !User.find(8).member_of?(Project.find(1))
1685
1686 @request.session[:user_id] = 2
1687 post :create, :project_id => 1,
1688 :issue => {:tracker_id => 1,
1689 :watcher_user_ids => ['3', '8']}
1690 assert_response :success
1691 assert_template 'new'
1692
1693 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '2', :checked => nil}
1694 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '3', :checked => 'checked'}
1695 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '8', :checked => 'checked'}
1696 end
1697
1683 def test_post_create_should_ignore_non_safe_attributes
1698 def test_post_create_should_ignore_non_safe_attributes
1684 @request.session[:user_id] = 2
1699 @request.session[:user_id] = 2
1685 assert_nothing_raised do
1700 assert_nothing_raised do
1686 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1701 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1687 end
1702 end
1688 end
1703 end
1689
1704
1690 def test_post_create_with_attachment
1705 def test_post_create_with_attachment
1691 set_tmp_attachments_directory
1706 set_tmp_attachments_directory
1692 @request.session[:user_id] = 2
1707 @request.session[:user_id] = 2
1693
1708
1694 assert_difference 'Issue.count' do
1709 assert_difference 'Issue.count' do
1695 assert_difference 'Attachment.count' do
1710 assert_difference 'Attachment.count' do
1696 post :create, :project_id => 1,
1711 post :create, :project_id => 1,
1697 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1712 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1698 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1713 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1699 end
1714 end
1700 end
1715 end
1701
1716
1702 issue = Issue.first(:order => 'id DESC')
1717 issue = Issue.first(:order => 'id DESC')
1703 attachment = Attachment.first(:order => 'id DESC')
1718 attachment = Attachment.first(:order => 'id DESC')
1704
1719
1705 assert_equal issue, attachment.container
1720 assert_equal issue, attachment.container
1706 assert_equal 2, attachment.author_id
1721 assert_equal 2, attachment.author_id
1707 assert_equal 'testfile.txt', attachment.filename
1722 assert_equal 'testfile.txt', attachment.filename
1708 assert_equal 'text/plain', attachment.content_type
1723 assert_equal 'text/plain', attachment.content_type
1709 assert_equal 'test file', attachment.description
1724 assert_equal 'test file', attachment.description
1710 assert_equal 59, attachment.filesize
1725 assert_equal 59, attachment.filesize
1711 assert File.exists?(attachment.diskfile)
1726 assert File.exists?(attachment.diskfile)
1712 assert_equal 59, File.size(attachment.diskfile)
1727 assert_equal 59, File.size(attachment.diskfile)
1713 end
1728 end
1714
1729
1715 def test_post_create_with_failure_should_save_attachments
1730 def test_post_create_with_failure_should_save_attachments
1716 set_tmp_attachments_directory
1731 set_tmp_attachments_directory
1717 @request.session[:user_id] = 2
1732 @request.session[:user_id] = 2
1718
1733
1719 assert_no_difference 'Issue.count' do
1734 assert_no_difference 'Issue.count' do
1720 assert_difference 'Attachment.count' do
1735 assert_difference 'Attachment.count' do
1721 post :create, :project_id => 1,
1736 post :create, :project_id => 1,
1722 :issue => { :tracker_id => '1', :subject => '' },
1737 :issue => { :tracker_id => '1', :subject => '' },
1723 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1738 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1724 assert_response :success
1739 assert_response :success
1725 assert_template 'new'
1740 assert_template 'new'
1726 end
1741 end
1727 end
1742 end
1728
1743
1729 attachment = Attachment.first(:order => 'id DESC')
1744 attachment = Attachment.first(:order => 'id DESC')
1730 assert_equal 'testfile.txt', attachment.filename
1745 assert_equal 'testfile.txt', attachment.filename
1731 assert File.exists?(attachment.diskfile)
1746 assert File.exists?(attachment.diskfile)
1732 assert_nil attachment.container
1747 assert_nil attachment.container
1733
1748
1734 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1749 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1735 assert_tag 'span', :content => /testfile.txt/
1750 assert_tag 'span', :content => /testfile.txt/
1736 end
1751 end
1737
1752
1738 def test_post_create_with_failure_should_keep_saved_attachments
1753 def test_post_create_with_failure_should_keep_saved_attachments
1739 set_tmp_attachments_directory
1754 set_tmp_attachments_directory
1740 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1755 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1741 @request.session[:user_id] = 2
1756 @request.session[:user_id] = 2
1742
1757
1743 assert_no_difference 'Issue.count' do
1758 assert_no_difference 'Issue.count' do
1744 assert_no_difference 'Attachment.count' do
1759 assert_no_difference 'Attachment.count' do
1745 post :create, :project_id => 1,
1760 post :create, :project_id => 1,
1746 :issue => { :tracker_id => '1', :subject => '' },
1761 :issue => { :tracker_id => '1', :subject => '' },
1747 :attachments => {'p0' => {'token' => attachment.token}}
1762 :attachments => {'p0' => {'token' => attachment.token}}
1748 assert_response :success
1763 assert_response :success
1749 assert_template 'new'
1764 assert_template 'new'
1750 end
1765 end
1751 end
1766 end
1752
1767
1753 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1768 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
1754 assert_tag 'span', :content => /testfile.txt/
1769 assert_tag 'span', :content => /testfile.txt/
1755 end
1770 end
1756
1771
1757 def test_post_create_should_attach_saved_attachments
1772 def test_post_create_should_attach_saved_attachments
1758 set_tmp_attachments_directory
1773 set_tmp_attachments_directory
1759 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1774 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
1760 @request.session[:user_id] = 2
1775 @request.session[:user_id] = 2
1761
1776
1762 assert_difference 'Issue.count' do
1777 assert_difference 'Issue.count' do
1763 assert_no_difference 'Attachment.count' do
1778 assert_no_difference 'Attachment.count' do
1764 post :create, :project_id => 1,
1779 post :create, :project_id => 1,
1765 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
1780 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
1766 :attachments => {'p0' => {'token' => attachment.token}}
1781 :attachments => {'p0' => {'token' => attachment.token}}
1767 assert_response 302
1782 assert_response 302
1768 end
1783 end
1769 end
1784 end
1770
1785
1771 issue = Issue.first(:order => 'id DESC')
1786 issue = Issue.first(:order => 'id DESC')
1772 assert_equal 1, issue.attachments.count
1787 assert_equal 1, issue.attachments.count
1773
1788
1774 attachment.reload
1789 attachment.reload
1775 assert_equal issue, attachment.container
1790 assert_equal issue, attachment.container
1776 end
1791 end
1777
1792
1778 context "without workflow privilege" do
1793 context "without workflow privilege" do
1779 setup do
1794 setup do
1780 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1795 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1781 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1796 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1782 end
1797 end
1783
1798
1784 context "#new" do
1799 context "#new" do
1785 should "propose default status only" do
1800 should "propose default status only" do
1786 get :new, :project_id => 1
1801 get :new, :project_id => 1
1787 assert_response :success
1802 assert_response :success
1788 assert_template 'new'
1803 assert_template 'new'
1789 assert_tag :tag => 'select',
1804 assert_tag :tag => 'select',
1790 :attributes => {:name => 'issue[status_id]'},
1805 :attributes => {:name => 'issue[status_id]'},
1791 :children => {:count => 1},
1806 :children => {:count => 1},
1792 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1807 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1793 end
1808 end
1794
1809
1795 should "accept default status" do
1810 should "accept default status" do
1796 assert_difference 'Issue.count' do
1811 assert_difference 'Issue.count' do
1797 post :create, :project_id => 1,
1812 post :create, :project_id => 1,
1798 :issue => {:tracker_id => 1,
1813 :issue => {:tracker_id => 1,
1799 :subject => 'This is an issue',
1814 :subject => 'This is an issue',
1800 :status_id => 1}
1815 :status_id => 1}
1801 end
1816 end
1802 issue = Issue.last(:order => 'id')
1817 issue = Issue.last(:order => 'id')
1803 assert_equal IssueStatus.default, issue.status
1818 assert_equal IssueStatus.default, issue.status
1804 end
1819 end
1805
1820
1806 should "ignore unauthorized status" do
1821 should "ignore unauthorized status" do
1807 assert_difference 'Issue.count' do
1822 assert_difference 'Issue.count' do
1808 post :create, :project_id => 1,
1823 post :create, :project_id => 1,
1809 :issue => {:tracker_id => 1,
1824 :issue => {:tracker_id => 1,
1810 :subject => 'This is an issue',
1825 :subject => 'This is an issue',
1811 :status_id => 3}
1826 :status_id => 3}
1812 end
1827 end
1813 issue = Issue.last(:order => 'id')
1828 issue = Issue.last(:order => 'id')
1814 assert_equal IssueStatus.default, issue.status
1829 assert_equal IssueStatus.default, issue.status
1815 end
1830 end
1816 end
1831 end
1817
1832
1818 context "#update" do
1833 context "#update" do
1819 should "ignore status change" do
1834 should "ignore status change" do
1820 assert_difference 'Journal.count' do
1835 assert_difference 'Journal.count' do
1821 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1836 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1822 end
1837 end
1823 assert_equal 1, Issue.find(1).status_id
1838 assert_equal 1, Issue.find(1).status_id
1824 end
1839 end
1825
1840
1826 should "ignore attributes changes" do
1841 should "ignore attributes changes" do
1827 assert_difference 'Journal.count' do
1842 assert_difference 'Journal.count' do
1828 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1843 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1829 end
1844 end
1830 issue = Issue.find(1)
1845 issue = Issue.find(1)
1831 assert_equal "Can't print recipes", issue.subject
1846 assert_equal "Can't print recipes", issue.subject
1832 assert_nil issue.assigned_to
1847 assert_nil issue.assigned_to
1833 end
1848 end
1834 end
1849 end
1835 end
1850 end
1836
1851
1837 context "with workflow privilege" do
1852 context "with workflow privilege" do
1838 setup do
1853 setup do
1839 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1854 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1840 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1855 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1841 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1856 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1842 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1857 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1843 end
1858 end
1844
1859
1845 context "#update" do
1860 context "#update" do
1846 should "accept authorized status" do
1861 should "accept authorized status" do
1847 assert_difference 'Journal.count' do
1862 assert_difference 'Journal.count' do
1848 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1863 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1849 end
1864 end
1850 assert_equal 3, Issue.find(1).status_id
1865 assert_equal 3, Issue.find(1).status_id
1851 end
1866 end
1852
1867
1853 should "ignore unauthorized status" do
1868 should "ignore unauthorized status" do
1854 assert_difference 'Journal.count' do
1869 assert_difference 'Journal.count' do
1855 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1870 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1856 end
1871 end
1857 assert_equal 1, Issue.find(1).status_id
1872 assert_equal 1, Issue.find(1).status_id
1858 end
1873 end
1859
1874
1860 should "accept authorized attributes changes" do
1875 should "accept authorized attributes changes" do
1861 assert_difference 'Journal.count' do
1876 assert_difference 'Journal.count' do
1862 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1877 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1863 end
1878 end
1864 issue = Issue.find(1)
1879 issue = Issue.find(1)
1865 assert_equal 2, issue.assigned_to_id
1880 assert_equal 2, issue.assigned_to_id
1866 end
1881 end
1867
1882
1868 should "ignore unauthorized attributes changes" do
1883 should "ignore unauthorized attributes changes" do
1869 assert_difference 'Journal.count' do
1884 assert_difference 'Journal.count' do
1870 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1885 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1871 end
1886 end
1872 issue = Issue.find(1)
1887 issue = Issue.find(1)
1873 assert_equal "Can't print recipes", issue.subject
1888 assert_equal "Can't print recipes", issue.subject
1874 end
1889 end
1875 end
1890 end
1876
1891
1877 context "and :edit_issues permission" do
1892 context "and :edit_issues permission" do
1878 setup do
1893 setup do
1879 Role.anonymous.add_permission! :add_issues, :edit_issues
1894 Role.anonymous.add_permission! :add_issues, :edit_issues
1880 end
1895 end
1881
1896
1882 should "accept authorized status" do
1897 should "accept authorized status" do
1883 assert_difference 'Journal.count' do
1898 assert_difference 'Journal.count' do
1884 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1899 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1885 end
1900 end
1886 assert_equal 3, Issue.find(1).status_id
1901 assert_equal 3, Issue.find(1).status_id
1887 end
1902 end
1888
1903
1889 should "ignore unauthorized status" do
1904 should "ignore unauthorized status" do
1890 assert_difference 'Journal.count' do
1905 assert_difference 'Journal.count' do
1891 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1906 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1892 end
1907 end
1893 assert_equal 1, Issue.find(1).status_id
1908 assert_equal 1, Issue.find(1).status_id
1894 end
1909 end
1895
1910
1896 should "accept authorized attributes changes" do
1911 should "accept authorized attributes changes" do
1897 assert_difference 'Journal.count' do
1912 assert_difference 'Journal.count' do
1898 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1913 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1899 end
1914 end
1900 issue = Issue.find(1)
1915 issue = Issue.find(1)
1901 assert_equal "changed", issue.subject
1916 assert_equal "changed", issue.subject
1902 assert_equal 2, issue.assigned_to_id
1917 assert_equal 2, issue.assigned_to_id
1903 end
1918 end
1904 end
1919 end
1905 end
1920 end
1906
1921
1907 def test_new_as_copy
1922 def test_new_as_copy
1908 @request.session[:user_id] = 2
1923 @request.session[:user_id] = 2
1909 get :new, :project_id => 1, :copy_from => 1
1924 get :new, :project_id => 1, :copy_from => 1
1910
1925
1911 assert_response :success
1926 assert_response :success
1912 assert_template 'new'
1927 assert_template 'new'
1913
1928
1914 assert_not_nil assigns(:issue)
1929 assert_not_nil assigns(:issue)
1915 orig = Issue.find(1)
1930 orig = Issue.find(1)
1916 assert_equal 1, assigns(:issue).project_id
1931 assert_equal 1, assigns(:issue).project_id
1917 assert_equal orig.subject, assigns(:issue).subject
1932 assert_equal orig.subject, assigns(:issue).subject
1918 assert assigns(:issue).copy?
1933 assert assigns(:issue).copy?
1919
1934
1920 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
1935 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
1921 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
1936 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
1922 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1937 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1923 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'}
1938 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'}
1924 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1939 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
1925 :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'}
1940 :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'}
1926 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
1941 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
1927 end
1942 end
1928
1943
1929 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
1944 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
1930 @request.session[:user_id] = 2
1945 @request.session[:user_id] = 2
1931 issue = Issue.find(3)
1946 issue = Issue.find(3)
1932 assert issue.attachments.count > 0
1947 assert issue.attachments.count > 0
1933 get :new, :project_id => 1, :copy_from => 3
1948 get :new, :project_id => 1, :copy_from => 3
1934
1949
1935 assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1950 assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1936 end
1951 end
1937
1952
1938 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
1953 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
1939 @request.session[:user_id] = 2
1954 @request.session[:user_id] = 2
1940 issue = Issue.find(3)
1955 issue = Issue.find(3)
1941 issue.attachments.delete_all
1956 issue.attachments.delete_all
1942 get :new, :project_id => 1, :copy_from => 3
1957 get :new, :project_id => 1, :copy_from => 3
1943
1958
1944 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1959 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
1945 end
1960 end
1946
1961
1947 def test_new_as_copy_with_invalid_issue_should_respond_with_404
1962 def test_new_as_copy_with_invalid_issue_should_respond_with_404
1948 @request.session[:user_id] = 2
1963 @request.session[:user_id] = 2
1949 get :new, :project_id => 1, :copy_from => 99999
1964 get :new, :project_id => 1, :copy_from => 99999
1950 assert_response 404
1965 assert_response 404
1951 end
1966 end
1952
1967
1953 def test_create_as_copy_on_different_project
1968 def test_create_as_copy_on_different_project
1954 @request.session[:user_id] = 2
1969 @request.session[:user_id] = 2
1955 assert_difference 'Issue.count' do
1970 assert_difference 'Issue.count' do
1956 post :create, :project_id => 1, :copy_from => 1,
1971 post :create, :project_id => 1, :copy_from => 1,
1957 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
1972 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
1958
1973
1959 assert_not_nil assigns(:issue)
1974 assert_not_nil assigns(:issue)
1960 assert assigns(:issue).copy?
1975 assert assigns(:issue).copy?
1961 end
1976 end
1962 issue = Issue.first(:order => 'id DESC')
1977 issue = Issue.first(:order => 'id DESC')
1963 assert_redirected_to "/issues/#{issue.id}"
1978 assert_redirected_to "/issues/#{issue.id}"
1964
1979
1965 assert_equal 2, issue.project_id
1980 assert_equal 2, issue.project_id
1966 assert_equal 3, issue.tracker_id
1981 assert_equal 3, issue.tracker_id
1967 assert_equal 'Copy', issue.subject
1982 assert_equal 'Copy', issue.subject
1968 end
1983 end
1969
1984
1970 def test_create_as_copy_should_copy_attachments
1985 def test_create_as_copy_should_copy_attachments
1971 @request.session[:user_id] = 2
1986 @request.session[:user_id] = 2
1972 issue = Issue.find(3)
1987 issue = Issue.find(3)
1973 count = issue.attachments.count
1988 count = issue.attachments.count
1974 assert count > 0
1989 assert count > 0
1975
1990
1976 assert_difference 'Issue.count' do
1991 assert_difference 'Issue.count' do
1977 assert_difference 'Attachment.count', count do
1992 assert_difference 'Attachment.count', count do
1978 assert_no_difference 'Journal.count' do
1993 assert_no_difference 'Journal.count' do
1979 post :create, :project_id => 1, :copy_from => 3,
1994 post :create, :project_id => 1, :copy_from => 3,
1980 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
1995 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
1981 :copy_attachments => '1'
1996 :copy_attachments => '1'
1982 end
1997 end
1983 end
1998 end
1984 end
1999 end
1985 copy = Issue.first(:order => 'id DESC')
2000 copy = Issue.first(:order => 'id DESC')
1986 assert_equal count, copy.attachments.count
2001 assert_equal count, copy.attachments.count
1987 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2002 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
1988 end
2003 end
1989
2004
1990 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2005 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
1991 @request.session[:user_id] = 2
2006 @request.session[:user_id] = 2
1992 issue = Issue.find(3)
2007 issue = Issue.find(3)
1993 count = issue.attachments.count
2008 count = issue.attachments.count
1994 assert count > 0
2009 assert count > 0
1995
2010
1996 assert_difference 'Issue.count' do
2011 assert_difference 'Issue.count' do
1997 assert_no_difference 'Attachment.count' do
2012 assert_no_difference 'Attachment.count' do
1998 assert_no_difference 'Journal.count' do
2013 assert_no_difference 'Journal.count' do
1999 post :create, :project_id => 1, :copy_from => 3,
2014 post :create, :project_id => 1, :copy_from => 3,
2000 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
2015 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
2001 end
2016 end
2002 end
2017 end
2003 end
2018 end
2004 copy = Issue.first(:order => 'id DESC')
2019 copy = Issue.first(:order => 'id DESC')
2005 assert_equal 0, copy.attachments.count
2020 assert_equal 0, copy.attachments.count
2006 end
2021 end
2007
2022
2008 def test_create_as_copy_with_attachments_should_add_new_files
2023 def test_create_as_copy_with_attachments_should_add_new_files
2009 @request.session[:user_id] = 2
2024 @request.session[:user_id] = 2
2010 issue = Issue.find(3)
2025 issue = Issue.find(3)
2011 count = issue.attachments.count
2026 count = issue.attachments.count
2012 assert count > 0
2027 assert count > 0
2013
2028
2014 assert_difference 'Issue.count' do
2029 assert_difference 'Issue.count' do
2015 assert_difference 'Attachment.count', count + 1 do
2030 assert_difference 'Attachment.count', count + 1 do
2016 assert_no_difference 'Journal.count' do
2031 assert_no_difference 'Journal.count' do
2017 post :create, :project_id => 1, :copy_from => 3,
2032 post :create, :project_id => 1, :copy_from => 3,
2018 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2033 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2019 :copy_attachments => '1',
2034 :copy_attachments => '1',
2020 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2035 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2021 end
2036 end
2022 end
2037 end
2023 end
2038 end
2024 copy = Issue.first(:order => 'id DESC')
2039 copy = Issue.first(:order => 'id DESC')
2025 assert_equal count + 1, copy.attachments.count
2040 assert_equal count + 1, copy.attachments.count
2026 end
2041 end
2027
2042
2028 def test_create_as_copy_with_failure
2043 def test_create_as_copy_with_failure
2029 @request.session[:user_id] = 2
2044 @request.session[:user_id] = 2
2030 post :create, :project_id => 1, :copy_from => 1,
2045 post :create, :project_id => 1, :copy_from => 1,
2031 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2046 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2032
2047
2033 assert_response :success
2048 assert_response :success
2034 assert_template 'new'
2049 assert_template 'new'
2035
2050
2036 assert_not_nil assigns(:issue)
2051 assert_not_nil assigns(:issue)
2037 assert assigns(:issue).copy?
2052 assert assigns(:issue).copy?
2038
2053
2039 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
2054 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
2040 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
2055 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
2041 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2056 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2042 :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'}
2057 :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'}
2043 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2058 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2044 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'}
2059 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'}
2045 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
2060 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
2046 end
2061 end
2047
2062
2048 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2063 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2049 @request.session[:user_id] = 2
2064 @request.session[:user_id] = 2
2050 assert !User.find(2).member_of?(Project.find(4))
2065 assert !User.find(2).member_of?(Project.find(4))
2051
2066
2052 assert_difference 'Issue.count' do
2067 assert_difference 'Issue.count' do
2053 post :create, :project_id => 1, :copy_from => 1,
2068 post :create, :project_id => 1, :copy_from => 1,
2054 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2069 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2055 end
2070 end
2056 issue = Issue.first(:order => 'id DESC')
2071 issue = Issue.first(:order => 'id DESC')
2057 assert_equal 1, issue.project_id
2072 assert_equal 1, issue.project_id
2058 end
2073 end
2059
2074
2060 def test_get_edit
2075 def test_get_edit
2061 @request.session[:user_id] = 2
2076 @request.session[:user_id] = 2
2062 get :edit, :id => 1
2077 get :edit, :id => 1
2063 assert_response :success
2078 assert_response :success
2064 assert_template 'edit'
2079 assert_template 'edit'
2065 assert_not_nil assigns(:issue)
2080 assert_not_nil assigns(:issue)
2066 assert_equal Issue.find(1), assigns(:issue)
2081 assert_equal Issue.find(1), assigns(:issue)
2067
2082
2068 # Be sure we don't display inactive IssuePriorities
2083 # Be sure we don't display inactive IssuePriorities
2069 assert ! IssuePriority.find(15).active?
2084 assert ! IssuePriority.find(15).active?
2070 assert_no_tag :option, :attributes => {:value => '15'},
2085 assert_no_tag :option, :attributes => {:value => '15'},
2071 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2086 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2072 end
2087 end
2073
2088
2074 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2089 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2075 @request.session[:user_id] = 2
2090 @request.session[:user_id] = 2
2076 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2091 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2077
2092
2078 get :edit, :id => 1
2093 get :edit, :id => 1
2079 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2094 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2080 end
2095 end
2081
2096
2082 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2097 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2083 @request.session[:user_id] = 2
2098 @request.session[:user_id] = 2
2084 Role.find_by_name('Manager').remove_permission! :log_time
2099 Role.find_by_name('Manager').remove_permission! :log_time
2085
2100
2086 get :edit, :id => 1
2101 get :edit, :id => 1
2087 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2102 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2088 end
2103 end
2089
2104
2090 def test_get_edit_with_params
2105 def test_get_edit_with_params
2091 @request.session[:user_id] = 2
2106 @request.session[:user_id] = 2
2092 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2107 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2093 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
2108 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
2094 assert_response :success
2109 assert_response :success
2095 assert_template 'edit'
2110 assert_template 'edit'
2096
2111
2097 issue = assigns(:issue)
2112 issue = assigns(:issue)
2098 assert_not_nil issue
2113 assert_not_nil issue
2099
2114
2100 assert_equal 5, issue.status_id
2115 assert_equal 5, issue.status_id
2101 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
2116 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
2102 :child => { :tag => 'option',
2117 :child => { :tag => 'option',
2103 :content => 'Closed',
2118 :content => 'Closed',
2104 :attributes => { :selected => 'selected' } }
2119 :attributes => { :selected => 'selected' } }
2105
2120
2106 assert_equal 7, issue.priority_id
2121 assert_equal 7, issue.priority_id
2107 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
2122 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
2108 :child => { :tag => 'option',
2123 :child => { :tag => 'option',
2109 :content => 'Urgent',
2124 :content => 'Urgent',
2110 :attributes => { :selected => 'selected' } }
2125 :attributes => { :selected => 'selected' } }
2111
2126
2112 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
2127 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
2113 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
2128 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
2114 :child => { :tag => 'option',
2129 :child => { :tag => 'option',
2115 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
2130 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
2116 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
2131 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
2117 end
2132 end
2118
2133
2119 def test_get_edit_with_multi_custom_field
2134 def test_get_edit_with_multi_custom_field
2120 field = CustomField.find(1)
2135 field = CustomField.find(1)
2121 field.update_attribute :multiple, true
2136 field.update_attribute :multiple, true
2122 issue = Issue.find(1)
2137 issue = Issue.find(1)
2123 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2138 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2124 issue.save!
2139 issue.save!
2125
2140
2126 @request.session[:user_id] = 2
2141 @request.session[:user_id] = 2
2127 get :edit, :id => 1
2142 get :edit, :id => 1
2128 assert_response :success
2143 assert_response :success
2129 assert_template 'edit'
2144 assert_template 'edit'
2130
2145
2131 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'}
2146 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'}
2132 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2147 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2133 :child => {:tag => 'option', :attributes => {:value => 'MySQL', :selected => 'selected'}}
2148 :child => {:tag => 'option', :attributes => {:value => 'MySQL', :selected => 'selected'}}
2134 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2149 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2135 :child => {:tag => 'option', :attributes => {:value => 'PostgreSQL', :selected => nil}}
2150 :child => {:tag => 'option', :attributes => {:value => 'PostgreSQL', :selected => nil}}
2136 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2151 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2137 :child => {:tag => 'option', :attributes => {:value => 'Oracle', :selected => 'selected'}}
2152 :child => {:tag => 'option', :attributes => {:value => 'Oracle', :selected => 'selected'}}
2138 end
2153 end
2139
2154
2140 def test_update_edit_form
2155 def test_update_edit_form
2141 @request.session[:user_id] = 2
2156 @request.session[:user_id] = 2
2142 xhr :put, :new, :project_id => 1,
2157 xhr :put, :new, :project_id => 1,
2143 :id => 1,
2158 :id => 1,
2144 :issue => {:tracker_id => 2,
2159 :issue => {:tracker_id => 2,
2145 :subject => 'This is the test_new issue',
2160 :subject => 'This is the test_new issue',
2146 :description => 'This is the description',
2161 :description => 'This is the description',
2147 :priority_id => 5}
2162 :priority_id => 5}
2148 assert_response :success
2163 assert_response :success
2149 assert_template 'attributes'
2164 assert_template 'attributes'
2150
2165
2151 issue = assigns(:issue)
2166 issue = assigns(:issue)
2152 assert_kind_of Issue, issue
2167 assert_kind_of Issue, issue
2153 assert_equal 1, issue.id
2168 assert_equal 1, issue.id
2154 assert_equal 1, issue.project_id
2169 assert_equal 1, issue.project_id
2155 assert_equal 2, issue.tracker_id
2170 assert_equal 2, issue.tracker_id
2156 assert_equal 'This is the test_new issue', issue.subject
2171 assert_equal 'This is the test_new issue', issue.subject
2157 end
2172 end
2158
2173
2159 def test_update_edit_form_with_project_change
2174 def test_update_edit_form_with_project_change
2160 @request.session[:user_id] = 2
2175 @request.session[:user_id] = 2
2161 xhr :put, :new, :project_id => 1,
2176 xhr :put, :new, :project_id => 1,
2162 :id => 1,
2177 :id => 1,
2163 :project_change => '1',
2178 :project_change => '1',
2164 :issue => {:project_id => 2,
2179 :issue => {:project_id => 2,
2165 :tracker_id => 2,
2180 :tracker_id => 2,
2166 :subject => 'This is the test_new issue',
2181 :subject => 'This is the test_new issue',
2167 :description => 'This is the description',
2182 :description => 'This is the description',
2168 :priority_id => 5}
2183 :priority_id => 5}
2169 assert_response :success
2184 assert_response :success
2170 assert_template 'form'
2185 assert_template 'form'
2171
2186
2172 issue = assigns(:issue)
2187 issue = assigns(:issue)
2173 assert_kind_of Issue, issue
2188 assert_kind_of Issue, issue
2174 assert_equal 1, issue.id
2189 assert_equal 1, issue.id
2175 assert_equal 2, issue.project_id
2190 assert_equal 2, issue.project_id
2176 assert_equal 2, issue.tracker_id
2191 assert_equal 2, issue.tracker_id
2177 assert_equal 'This is the test_new issue', issue.subject
2192 assert_equal 'This is the test_new issue', issue.subject
2178 end
2193 end
2179
2194
2180 def test_put_update_without_custom_fields_param
2195 def test_put_update_without_custom_fields_param
2181 @request.session[:user_id] = 2
2196 @request.session[:user_id] = 2
2182 ActionMailer::Base.deliveries.clear
2197 ActionMailer::Base.deliveries.clear
2183
2198
2184 issue = Issue.find(1)
2199 issue = Issue.find(1)
2185 assert_equal '125', issue.custom_value_for(2).value
2200 assert_equal '125', issue.custom_value_for(2).value
2186 old_subject = issue.subject
2201 old_subject = issue.subject
2187 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2202 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2188
2203
2189 assert_difference('Journal.count') do
2204 assert_difference('Journal.count') do
2190 assert_difference('JournalDetail.count', 2) do
2205 assert_difference('JournalDetail.count', 2) do
2191 put :update, :id => 1, :issue => {:subject => new_subject,
2206 put :update, :id => 1, :issue => {:subject => new_subject,
2192 :priority_id => '6',
2207 :priority_id => '6',
2193 :category_id => '1' # no change
2208 :category_id => '1' # no change
2194 }
2209 }
2195 end
2210 end
2196 end
2211 end
2197 assert_redirected_to :action => 'show', :id => '1'
2212 assert_redirected_to :action => 'show', :id => '1'
2198 issue.reload
2213 issue.reload
2199 assert_equal new_subject, issue.subject
2214 assert_equal new_subject, issue.subject
2200 # Make sure custom fields were not cleared
2215 # Make sure custom fields were not cleared
2201 assert_equal '125', issue.custom_value_for(2).value
2216 assert_equal '125', issue.custom_value_for(2).value
2202
2217
2203 mail = ActionMailer::Base.deliveries.last
2218 mail = ActionMailer::Base.deliveries.last
2204 assert_not_nil mail
2219 assert_not_nil mail
2205 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2220 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2206 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2221 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2207 end
2222 end
2208
2223
2209 def test_put_update_with_project_change
2224 def test_put_update_with_project_change
2210 @request.session[:user_id] = 2
2225 @request.session[:user_id] = 2
2211 ActionMailer::Base.deliveries.clear
2226 ActionMailer::Base.deliveries.clear
2212
2227
2213 assert_difference('Journal.count') do
2228 assert_difference('Journal.count') do
2214 assert_difference('JournalDetail.count', 3) do
2229 assert_difference('JournalDetail.count', 3) do
2215 put :update, :id => 1, :issue => {:project_id => '2',
2230 put :update, :id => 1, :issue => {:project_id => '2',
2216 :tracker_id => '1', # no change
2231 :tracker_id => '1', # no change
2217 :priority_id => '6',
2232 :priority_id => '6',
2218 :category_id => '3'
2233 :category_id => '3'
2219 }
2234 }
2220 end
2235 end
2221 end
2236 end
2222 assert_redirected_to :action => 'show', :id => '1'
2237 assert_redirected_to :action => 'show', :id => '1'
2223 issue = Issue.find(1)
2238 issue = Issue.find(1)
2224 assert_equal 2, issue.project_id
2239 assert_equal 2, issue.project_id
2225 assert_equal 1, issue.tracker_id
2240 assert_equal 1, issue.tracker_id
2226 assert_equal 6, issue.priority_id
2241 assert_equal 6, issue.priority_id
2227 assert_equal 3, issue.category_id
2242 assert_equal 3, issue.category_id
2228
2243
2229 mail = ActionMailer::Base.deliveries.last
2244 mail = ActionMailer::Base.deliveries.last
2230 assert_not_nil mail
2245 assert_not_nil mail
2231 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2246 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2232 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2247 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2233 end
2248 end
2234
2249
2235 def test_put_update_with_tracker_change
2250 def test_put_update_with_tracker_change
2236 @request.session[:user_id] = 2
2251 @request.session[:user_id] = 2
2237 ActionMailer::Base.deliveries.clear
2252 ActionMailer::Base.deliveries.clear
2238
2253
2239 assert_difference('Journal.count') do
2254 assert_difference('Journal.count') do
2240 assert_difference('JournalDetail.count', 2) do
2255 assert_difference('JournalDetail.count', 2) do
2241 put :update, :id => 1, :issue => {:project_id => '1',
2256 put :update, :id => 1, :issue => {:project_id => '1',
2242 :tracker_id => '2',
2257 :tracker_id => '2',
2243 :priority_id => '6'
2258 :priority_id => '6'
2244 }
2259 }
2245 end
2260 end
2246 end
2261 end
2247 assert_redirected_to :action => 'show', :id => '1'
2262 assert_redirected_to :action => 'show', :id => '1'
2248 issue = Issue.find(1)
2263 issue = Issue.find(1)
2249 assert_equal 1, issue.project_id
2264 assert_equal 1, issue.project_id
2250 assert_equal 2, issue.tracker_id
2265 assert_equal 2, issue.tracker_id
2251 assert_equal 6, issue.priority_id
2266 assert_equal 6, issue.priority_id
2252 assert_equal 1, issue.category_id
2267 assert_equal 1, issue.category_id
2253
2268
2254 mail = ActionMailer::Base.deliveries.last
2269 mail = ActionMailer::Base.deliveries.last
2255 assert_not_nil mail
2270 assert_not_nil mail
2256 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2271 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2257 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2272 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2258 end
2273 end
2259
2274
2260 def test_put_update_with_custom_field_change
2275 def test_put_update_with_custom_field_change
2261 @request.session[:user_id] = 2
2276 @request.session[:user_id] = 2
2262 issue = Issue.find(1)
2277 issue = Issue.find(1)
2263 assert_equal '125', issue.custom_value_for(2).value
2278 assert_equal '125', issue.custom_value_for(2).value
2264
2279
2265 assert_difference('Journal.count') do
2280 assert_difference('Journal.count') do
2266 assert_difference('JournalDetail.count', 3) do
2281 assert_difference('JournalDetail.count', 3) do
2267 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2282 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2268 :priority_id => '6',
2283 :priority_id => '6',
2269 :category_id => '1', # no change
2284 :category_id => '1', # no change
2270 :custom_field_values => { '2' => 'New custom value' }
2285 :custom_field_values => { '2' => 'New custom value' }
2271 }
2286 }
2272 end
2287 end
2273 end
2288 end
2274 assert_redirected_to :action => 'show', :id => '1'
2289 assert_redirected_to :action => 'show', :id => '1'
2275 issue.reload
2290 issue.reload
2276 assert_equal 'New custom value', issue.custom_value_for(2).value
2291 assert_equal 'New custom value', issue.custom_value_for(2).value
2277
2292
2278 mail = ActionMailer::Base.deliveries.last
2293 mail = ActionMailer::Base.deliveries.last
2279 assert_not_nil mail
2294 assert_not_nil mail
2280 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2295 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2281 end
2296 end
2282
2297
2283 def test_put_update_with_multi_custom_field_change
2298 def test_put_update_with_multi_custom_field_change
2284 field = CustomField.find(1)
2299 field = CustomField.find(1)
2285 field.update_attribute :multiple, true
2300 field.update_attribute :multiple, true
2286 issue = Issue.find(1)
2301 issue = Issue.find(1)
2287 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2302 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2288 issue.save!
2303 issue.save!
2289
2304
2290 @request.session[:user_id] = 2
2305 @request.session[:user_id] = 2
2291 assert_difference('Journal.count') do
2306 assert_difference('Journal.count') do
2292 assert_difference('JournalDetail.count', 3) do
2307 assert_difference('JournalDetail.count', 3) do
2293 put :update, :id => 1,
2308 put :update, :id => 1,
2294 :issue => {
2309 :issue => {
2295 :subject => 'Custom field change',
2310 :subject => 'Custom field change',
2296 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2311 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2297 }
2312 }
2298 end
2313 end
2299 end
2314 end
2300 assert_redirected_to :action => 'show', :id => '1'
2315 assert_redirected_to :action => 'show', :id => '1'
2301 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2316 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2302 end
2317 end
2303
2318
2304 def test_put_update_with_status_and_assignee_change
2319 def test_put_update_with_status_and_assignee_change
2305 issue = Issue.find(1)
2320 issue = Issue.find(1)
2306 assert_equal 1, issue.status_id
2321 assert_equal 1, issue.status_id
2307 @request.session[:user_id] = 2
2322 @request.session[:user_id] = 2
2308 assert_difference('TimeEntry.count', 0) do
2323 assert_difference('TimeEntry.count', 0) do
2309 put :update,
2324 put :update,
2310 :id => 1,
2325 :id => 1,
2311 :issue => { :status_id => 2, :assigned_to_id => 3 },
2326 :issue => { :status_id => 2, :assigned_to_id => 3 },
2312 :notes => 'Assigned to dlopper',
2327 :notes => 'Assigned to dlopper',
2313 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2328 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2314 end
2329 end
2315 assert_redirected_to :action => 'show', :id => '1'
2330 assert_redirected_to :action => 'show', :id => '1'
2316 issue.reload
2331 issue.reload
2317 assert_equal 2, issue.status_id
2332 assert_equal 2, issue.status_id
2318 j = Journal.find(:first, :order => 'id DESC')
2333 j = Journal.find(:first, :order => 'id DESC')
2319 assert_equal 'Assigned to dlopper', j.notes
2334 assert_equal 'Assigned to dlopper', j.notes
2320 assert_equal 2, j.details.size
2335 assert_equal 2, j.details.size
2321
2336
2322 mail = ActionMailer::Base.deliveries.last
2337 mail = ActionMailer::Base.deliveries.last
2323 assert_mail_body_match "Status changed from New to Assigned", mail
2338 assert_mail_body_match "Status changed from New to Assigned", mail
2324 # subject should contain the new status
2339 # subject should contain the new status
2325 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2340 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2326 end
2341 end
2327
2342
2328 def test_put_update_with_note_only
2343 def test_put_update_with_note_only
2329 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2344 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2330 # anonymous user
2345 # anonymous user
2331 put :update,
2346 put :update,
2332 :id => 1,
2347 :id => 1,
2333 :notes => notes
2348 :notes => notes
2334 assert_redirected_to :action => 'show', :id => '1'
2349 assert_redirected_to :action => 'show', :id => '1'
2335 j = Journal.find(:first, :order => 'id DESC')
2350 j = Journal.find(:first, :order => 'id DESC')
2336 assert_equal notes, j.notes
2351 assert_equal notes, j.notes
2337 assert_equal 0, j.details.size
2352 assert_equal 0, j.details.size
2338 assert_equal User.anonymous, j.user
2353 assert_equal User.anonymous, j.user
2339
2354
2340 mail = ActionMailer::Base.deliveries.last
2355 mail = ActionMailer::Base.deliveries.last
2341 assert_mail_body_match notes, mail
2356 assert_mail_body_match notes, mail
2342 end
2357 end
2343
2358
2344 def test_put_update_with_note_and_spent_time
2359 def test_put_update_with_note_and_spent_time
2345 @request.session[:user_id] = 2
2360 @request.session[:user_id] = 2
2346 spent_hours_before = Issue.find(1).spent_hours
2361 spent_hours_before = Issue.find(1).spent_hours
2347 assert_difference('TimeEntry.count') do
2362 assert_difference('TimeEntry.count') do
2348 put :update,
2363 put :update,
2349 :id => 1,
2364 :id => 1,
2350 :notes => '2.5 hours added',
2365 :notes => '2.5 hours added',
2351 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2366 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2352 end
2367 end
2353 assert_redirected_to :action => 'show', :id => '1'
2368 assert_redirected_to :action => 'show', :id => '1'
2354
2369
2355 issue = Issue.find(1)
2370 issue = Issue.find(1)
2356
2371
2357 j = Journal.find(:first, :order => 'id DESC')
2372 j = Journal.find(:first, :order => 'id DESC')
2358 assert_equal '2.5 hours added', j.notes
2373 assert_equal '2.5 hours added', j.notes
2359 assert_equal 0, j.details.size
2374 assert_equal 0, j.details.size
2360
2375
2361 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2376 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2362 assert_not_nil t
2377 assert_not_nil t
2363 assert_equal 2.5, t.hours
2378 assert_equal 2.5, t.hours
2364 assert_equal spent_hours_before + 2.5, issue.spent_hours
2379 assert_equal spent_hours_before + 2.5, issue.spent_hours
2365 end
2380 end
2366
2381
2367 def test_put_update_with_attachment_only
2382 def test_put_update_with_attachment_only
2368 set_tmp_attachments_directory
2383 set_tmp_attachments_directory
2369
2384
2370 # Delete all fixtured journals, a race condition can occur causing the wrong
2385 # Delete all fixtured journals, a race condition can occur causing the wrong
2371 # journal to get fetched in the next find.
2386 # journal to get fetched in the next find.
2372 Journal.delete_all
2387 Journal.delete_all
2373
2388
2374 # anonymous user
2389 # anonymous user
2375 assert_difference 'Attachment.count' do
2390 assert_difference 'Attachment.count' do
2376 put :update, :id => 1,
2391 put :update, :id => 1,
2377 :notes => '',
2392 :notes => '',
2378 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2393 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2379 end
2394 end
2380
2395
2381 assert_redirected_to :action => 'show', :id => '1'
2396 assert_redirected_to :action => 'show', :id => '1'
2382 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
2397 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
2383 assert j.notes.blank?
2398 assert j.notes.blank?
2384 assert_equal 1, j.details.size
2399 assert_equal 1, j.details.size
2385 assert_equal 'testfile.txt', j.details.first.value
2400 assert_equal 'testfile.txt', j.details.first.value
2386 assert_equal User.anonymous, j.user
2401 assert_equal User.anonymous, j.user
2387
2402
2388 attachment = Attachment.first(:order => 'id DESC')
2403 attachment = Attachment.first(:order => 'id DESC')
2389 assert_equal Issue.find(1), attachment.container
2404 assert_equal Issue.find(1), attachment.container
2390 assert_equal User.anonymous, attachment.author
2405 assert_equal User.anonymous, attachment.author
2391 assert_equal 'testfile.txt', attachment.filename
2406 assert_equal 'testfile.txt', attachment.filename
2392 assert_equal 'text/plain', attachment.content_type
2407 assert_equal 'text/plain', attachment.content_type
2393 assert_equal 'test file', attachment.description
2408 assert_equal 'test file', attachment.description
2394 assert_equal 59, attachment.filesize
2409 assert_equal 59, attachment.filesize
2395 assert File.exists?(attachment.diskfile)
2410 assert File.exists?(attachment.diskfile)
2396 assert_equal 59, File.size(attachment.diskfile)
2411 assert_equal 59, File.size(attachment.diskfile)
2397
2412
2398 mail = ActionMailer::Base.deliveries.last
2413 mail = ActionMailer::Base.deliveries.last
2399 assert_mail_body_match 'testfile.txt', mail
2414 assert_mail_body_match 'testfile.txt', mail
2400 end
2415 end
2401
2416
2402 def test_put_update_with_failure_should_save_attachments
2417 def test_put_update_with_failure_should_save_attachments
2403 set_tmp_attachments_directory
2418 set_tmp_attachments_directory
2404 @request.session[:user_id] = 2
2419 @request.session[:user_id] = 2
2405
2420
2406 assert_no_difference 'Journal.count' do
2421 assert_no_difference 'Journal.count' do
2407 assert_difference 'Attachment.count' do
2422 assert_difference 'Attachment.count' do
2408 put :update, :id => 1,
2423 put :update, :id => 1,
2409 :issue => { :subject => '' },
2424 :issue => { :subject => '' },
2410 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2425 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2411 assert_response :success
2426 assert_response :success
2412 assert_template 'edit'
2427 assert_template 'edit'
2413 end
2428 end
2414 end
2429 end
2415
2430
2416 attachment = Attachment.first(:order => 'id DESC')
2431 attachment = Attachment.first(:order => 'id DESC')
2417 assert_equal 'testfile.txt', attachment.filename
2432 assert_equal 'testfile.txt', attachment.filename
2418 assert File.exists?(attachment.diskfile)
2433 assert File.exists?(attachment.diskfile)
2419 assert_nil attachment.container
2434 assert_nil attachment.container
2420
2435
2421 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2436 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2422 assert_tag 'span', :content => /testfile.txt/
2437 assert_tag 'span', :content => /testfile.txt/
2423 end
2438 end
2424
2439
2425 def test_put_update_with_failure_should_keep_saved_attachments
2440 def test_put_update_with_failure_should_keep_saved_attachments
2426 set_tmp_attachments_directory
2441 set_tmp_attachments_directory
2427 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2442 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2428 @request.session[:user_id] = 2
2443 @request.session[:user_id] = 2
2429
2444
2430 assert_no_difference 'Journal.count' do
2445 assert_no_difference 'Journal.count' do
2431 assert_no_difference 'Attachment.count' do
2446 assert_no_difference 'Attachment.count' do
2432 put :update, :id => 1,
2447 put :update, :id => 1,
2433 :issue => { :subject => '' },
2448 :issue => { :subject => '' },
2434 :attachments => {'p0' => {'token' => attachment.token}}
2449 :attachments => {'p0' => {'token' => attachment.token}}
2435 assert_response :success
2450 assert_response :success
2436 assert_template 'edit'
2451 assert_template 'edit'
2437 end
2452 end
2438 end
2453 end
2439
2454
2440 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2455 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2441 assert_tag 'span', :content => /testfile.txt/
2456 assert_tag 'span', :content => /testfile.txt/
2442 end
2457 end
2443
2458
2444 def test_put_update_should_attach_saved_attachments
2459 def test_put_update_should_attach_saved_attachments
2445 set_tmp_attachments_directory
2460 set_tmp_attachments_directory
2446 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2461 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2447 @request.session[:user_id] = 2
2462 @request.session[:user_id] = 2
2448
2463
2449 assert_difference 'Journal.count' do
2464 assert_difference 'Journal.count' do
2450 assert_difference 'JournalDetail.count' do
2465 assert_difference 'JournalDetail.count' do
2451 assert_no_difference 'Attachment.count' do
2466 assert_no_difference 'Attachment.count' do
2452 put :update, :id => 1,
2467 put :update, :id => 1,
2453 :notes => 'Attachment added',
2468 :notes => 'Attachment added',
2454 :attachments => {'p0' => {'token' => attachment.token}}
2469 :attachments => {'p0' => {'token' => attachment.token}}
2455 assert_redirected_to '/issues/1'
2470 assert_redirected_to '/issues/1'
2456 end
2471 end
2457 end
2472 end
2458 end
2473 end
2459
2474
2460 attachment.reload
2475 attachment.reload
2461 assert_equal Issue.find(1), attachment.container
2476 assert_equal Issue.find(1), attachment.container
2462
2477
2463 journal = Journal.first(:order => 'id DESC')
2478 journal = Journal.first(:order => 'id DESC')
2464 assert_equal 1, journal.details.size
2479 assert_equal 1, journal.details.size
2465 assert_equal 'testfile.txt', journal.details.first.value
2480 assert_equal 'testfile.txt', journal.details.first.value
2466 end
2481 end
2467
2482
2468 def test_put_update_with_attachment_that_fails_to_save
2483 def test_put_update_with_attachment_that_fails_to_save
2469 set_tmp_attachments_directory
2484 set_tmp_attachments_directory
2470
2485
2471 # Delete all fixtured journals, a race condition can occur causing the wrong
2486 # Delete all fixtured journals, a race condition can occur causing the wrong
2472 # journal to get fetched in the next find.
2487 # journal to get fetched in the next find.
2473 Journal.delete_all
2488 Journal.delete_all
2474
2489
2475 # Mock out the unsaved attachment
2490 # Mock out the unsaved attachment
2476 Attachment.any_instance.stubs(:create).returns(Attachment.new)
2491 Attachment.any_instance.stubs(:create).returns(Attachment.new)
2477
2492
2478 # anonymous user
2493 # anonymous user
2479 put :update,
2494 put :update,
2480 :id => 1,
2495 :id => 1,
2481 :notes => '',
2496 :notes => '',
2482 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
2497 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
2483 assert_redirected_to :action => 'show', :id => '1'
2498 assert_redirected_to :action => 'show', :id => '1'
2484 assert_equal '1 file(s) could not be saved.', flash[:warning]
2499 assert_equal '1 file(s) could not be saved.', flash[:warning]
2485 end
2500 end
2486
2501
2487 def test_put_update_with_no_change
2502 def test_put_update_with_no_change
2488 issue = Issue.find(1)
2503 issue = Issue.find(1)
2489 issue.journals.clear
2504 issue.journals.clear
2490 ActionMailer::Base.deliveries.clear
2505 ActionMailer::Base.deliveries.clear
2491
2506
2492 put :update,
2507 put :update,
2493 :id => 1,
2508 :id => 1,
2494 :notes => ''
2509 :notes => ''
2495 assert_redirected_to :action => 'show', :id => '1'
2510 assert_redirected_to :action => 'show', :id => '1'
2496
2511
2497 issue.reload
2512 issue.reload
2498 assert issue.journals.empty?
2513 assert issue.journals.empty?
2499 # No email should be sent
2514 # No email should be sent
2500 assert ActionMailer::Base.deliveries.empty?
2515 assert ActionMailer::Base.deliveries.empty?
2501 end
2516 end
2502
2517
2503 def test_put_update_should_send_a_notification
2518 def test_put_update_should_send_a_notification
2504 @request.session[:user_id] = 2
2519 @request.session[:user_id] = 2
2505 ActionMailer::Base.deliveries.clear
2520 ActionMailer::Base.deliveries.clear
2506 issue = Issue.find(1)
2521 issue = Issue.find(1)
2507 old_subject = issue.subject
2522 old_subject = issue.subject
2508 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2523 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2509
2524
2510 put :update, :id => 1, :issue => {:subject => new_subject,
2525 put :update, :id => 1, :issue => {:subject => new_subject,
2511 :priority_id => '6',
2526 :priority_id => '6',
2512 :category_id => '1' # no change
2527 :category_id => '1' # no change
2513 }
2528 }
2514 assert_equal 1, ActionMailer::Base.deliveries.size
2529 assert_equal 1, ActionMailer::Base.deliveries.size
2515 end
2530 end
2516
2531
2517 def test_put_update_with_invalid_spent_time_hours_only
2532 def test_put_update_with_invalid_spent_time_hours_only
2518 @request.session[:user_id] = 2
2533 @request.session[:user_id] = 2
2519 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2534 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2520
2535
2521 assert_no_difference('Journal.count') do
2536 assert_no_difference('Journal.count') do
2522 put :update,
2537 put :update,
2523 :id => 1,
2538 :id => 1,
2524 :notes => notes,
2539 :notes => notes,
2525 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
2540 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
2526 end
2541 end
2527 assert_response :success
2542 assert_response :success
2528 assert_template 'edit'
2543 assert_template 'edit'
2529
2544
2530 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2545 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2531 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2546 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2532 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
2547 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
2533 end
2548 end
2534
2549
2535 def test_put_update_with_invalid_spent_time_comments_only
2550 def test_put_update_with_invalid_spent_time_comments_only
2536 @request.session[:user_id] = 2
2551 @request.session[:user_id] = 2
2537 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2552 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
2538
2553
2539 assert_no_difference('Journal.count') do
2554 assert_no_difference('Journal.count') do
2540 put :update,
2555 put :update,
2541 :id => 1,
2556 :id => 1,
2542 :notes => notes,
2557 :notes => notes,
2543 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
2558 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
2544 end
2559 end
2545 assert_response :success
2560 assert_response :success
2546 assert_template 'edit'
2561 assert_template 'edit'
2547
2562
2548 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2563 assert_error_tag :descendant => {:content => /Activity can't be blank/}
2549 assert_error_tag :descendant => {:content => /Hours can't be blank/}
2564 assert_error_tag :descendant => {:content => /Hours can't be blank/}
2550 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2565 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
2551 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
2566 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
2552 end
2567 end
2553
2568
2554 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
2569 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
2555 issue = Issue.find(2)
2570 issue = Issue.find(2)
2556 @request.session[:user_id] = 2
2571 @request.session[:user_id] = 2
2557
2572
2558 put :update,
2573 put :update,
2559 :id => issue.id,
2574 :id => issue.id,
2560 :issue => {
2575 :issue => {
2561 :fixed_version_id => 4
2576 :fixed_version_id => 4
2562 }
2577 }
2563
2578
2564 assert_response :redirect
2579 assert_response :redirect
2565 issue.reload
2580 issue.reload
2566 assert_equal 4, issue.fixed_version_id
2581 assert_equal 4, issue.fixed_version_id
2567 assert_not_equal issue.project_id, issue.fixed_version.project_id
2582 assert_not_equal issue.project_id, issue.fixed_version.project_id
2568 end
2583 end
2569
2584
2570 def test_put_update_should_redirect_back_using_the_back_url_parameter
2585 def test_put_update_should_redirect_back_using_the_back_url_parameter
2571 issue = Issue.find(2)
2586 issue = Issue.find(2)
2572 @request.session[:user_id] = 2
2587 @request.session[:user_id] = 2
2573
2588
2574 put :update,
2589 put :update,
2575 :id => issue.id,
2590 :id => issue.id,
2576 :issue => {
2591 :issue => {
2577 :fixed_version_id => 4
2592 :fixed_version_id => 4
2578 },
2593 },
2579 :back_url => '/issues'
2594 :back_url => '/issues'
2580
2595
2581 assert_response :redirect
2596 assert_response :redirect
2582 assert_redirected_to '/issues'
2597 assert_redirected_to '/issues'
2583 end
2598 end
2584
2599
2585 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2600 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2586 issue = Issue.find(2)
2601 issue = Issue.find(2)
2587 @request.session[:user_id] = 2
2602 @request.session[:user_id] = 2
2588
2603
2589 put :update,
2604 put :update,
2590 :id => issue.id,
2605 :id => issue.id,
2591 :issue => {
2606 :issue => {
2592 :fixed_version_id => 4
2607 :fixed_version_id => 4
2593 },
2608 },
2594 :back_url => 'http://google.com'
2609 :back_url => 'http://google.com'
2595
2610
2596 assert_response :redirect
2611 assert_response :redirect
2597 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
2612 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
2598 end
2613 end
2599
2614
2600 def test_get_bulk_edit
2615 def test_get_bulk_edit
2601 @request.session[:user_id] = 2
2616 @request.session[:user_id] = 2
2602 get :bulk_edit, :ids => [1, 2]
2617 get :bulk_edit, :ids => [1, 2]
2603 assert_response :success
2618 assert_response :success
2604 assert_template 'bulk_edit'
2619 assert_template 'bulk_edit'
2605
2620
2606 assert_tag :select, :attributes => {:name => 'issue[project_id]'}
2621 assert_tag :select, :attributes => {:name => 'issue[project_id]'}
2607 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2622 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2608
2623
2609 # Project specific custom field, date type
2624 # Project specific custom field, date type
2610 field = CustomField.find(9)
2625 field = CustomField.find(9)
2611 assert !field.is_for_all?
2626 assert !field.is_for_all?
2612 assert_equal 'date', field.field_format
2627 assert_equal 'date', field.field_format
2613 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2628 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2614
2629
2615 # System wide custom field
2630 # System wide custom field
2616 assert CustomField.find(1).is_for_all?
2631 assert CustomField.find(1).is_for_all?
2617 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
2632 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
2618
2633
2619 # Be sure we don't display inactive IssuePriorities
2634 # Be sure we don't display inactive IssuePriorities
2620 assert ! IssuePriority.find(15).active?
2635 assert ! IssuePriority.find(15).active?
2621 assert_no_tag :option, :attributes => {:value => '15'},
2636 assert_no_tag :option, :attributes => {:value => '15'},
2622 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2637 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2623 end
2638 end
2624
2639
2625 def test_get_bulk_edit_on_different_projects
2640 def test_get_bulk_edit_on_different_projects
2626 @request.session[:user_id] = 2
2641 @request.session[:user_id] = 2
2627 get :bulk_edit, :ids => [1, 2, 6]
2642 get :bulk_edit, :ids => [1, 2, 6]
2628 assert_response :success
2643 assert_response :success
2629 assert_template 'bulk_edit'
2644 assert_template 'bulk_edit'
2630
2645
2631 # Can not set issues from different projects as children of an issue
2646 # Can not set issues from different projects as children of an issue
2632 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2647 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
2633
2648
2634 # Project specific custom field, date type
2649 # Project specific custom field, date type
2635 field = CustomField.find(9)
2650 field = CustomField.find(9)
2636 assert !field.is_for_all?
2651 assert !field.is_for_all?
2637 assert !field.project_ids.include?(Issue.find(6).project_id)
2652 assert !field.project_ids.include?(Issue.find(6).project_id)
2638 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2653 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
2639 end
2654 end
2640
2655
2641 def test_get_bulk_edit_with_user_custom_field
2656 def test_get_bulk_edit_with_user_custom_field
2642 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
2657 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
2643
2658
2644 @request.session[:user_id] = 2
2659 @request.session[:user_id] = 2
2645 get :bulk_edit, :ids => [1, 2]
2660 get :bulk_edit, :ids => [1, 2]
2646 assert_response :success
2661 assert_response :success
2647 assert_template 'bulk_edit'
2662 assert_template 'bulk_edit'
2648
2663
2649 assert_tag :select,
2664 assert_tag :select,
2650 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2665 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2651 :children => {
2666 :children => {
2652 :only => {:tag => 'option'},
2667 :only => {:tag => 'option'},
2653 :count => Project.find(1).users.count + 1
2668 :count => Project.find(1).users.count + 1
2654 }
2669 }
2655 end
2670 end
2656
2671
2657 def test_get_bulk_edit_with_version_custom_field
2672 def test_get_bulk_edit_with_version_custom_field
2658 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
2673 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
2659
2674
2660 @request.session[:user_id] = 2
2675 @request.session[:user_id] = 2
2661 get :bulk_edit, :ids => [1, 2]
2676 get :bulk_edit, :ids => [1, 2]
2662 assert_response :success
2677 assert_response :success
2663 assert_template 'bulk_edit'
2678 assert_template 'bulk_edit'
2664
2679
2665 assert_tag :select,
2680 assert_tag :select,
2666 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2681 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
2667 :children => {
2682 :children => {
2668 :only => {:tag => 'option'},
2683 :only => {:tag => 'option'},
2669 :count => Project.find(1).shared_versions.count + 1
2684 :count => Project.find(1).shared_versions.count + 1
2670 }
2685 }
2671 end
2686 end
2672
2687
2673 def test_get_bulk_edit_with_multi_custom_field
2688 def test_get_bulk_edit_with_multi_custom_field
2674 field = CustomField.find(1)
2689 field = CustomField.find(1)
2675 field.update_attribute :multiple, true
2690 field.update_attribute :multiple, true
2676
2691
2677 @request.session[:user_id] = 2
2692 @request.session[:user_id] = 2
2678 get :bulk_edit, :ids => [1, 2]
2693 get :bulk_edit, :ids => [1, 2]
2679 assert_response :success
2694 assert_response :success
2680 assert_template 'bulk_edit'
2695 assert_template 'bulk_edit'
2681
2696
2682 assert_tag :select,
2697 assert_tag :select,
2683 :attributes => {:name => "issue[custom_field_values][1][]"},
2698 :attributes => {:name => "issue[custom_field_values][1][]"},
2684 :children => {
2699 :children => {
2685 :only => {:tag => 'option'},
2700 :only => {:tag => 'option'},
2686 :count => 3
2701 :count => 3
2687 }
2702 }
2688 end
2703 end
2689
2704
2690 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
2705 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
2691 Workflow.delete_all
2706 Workflow.delete_all
2692 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
2707 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
2693 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
2708 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
2694 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
2709 Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
2695 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2710 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2696 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2711 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2697 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2712 Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2698 @request.session[:user_id] = 2
2713 @request.session[:user_id] = 2
2699 get :bulk_edit, :ids => [1, 2]
2714 get :bulk_edit, :ids => [1, 2]
2700
2715
2701 assert_response :success
2716 assert_response :success
2702 statuses = assigns(:available_statuses)
2717 statuses = assigns(:available_statuses)
2703 assert_not_nil statuses
2718 assert_not_nil statuses
2704 assert_equal [1, 3], statuses.map(&:id).sort
2719 assert_equal [1, 3], statuses.map(&:id).sort
2705
2720
2706 assert_tag 'select', :attributes => {:name => 'issue[status_id]'},
2721 assert_tag 'select', :attributes => {:name => 'issue[status_id]'},
2707 :children => {:count => 3} # 2 statuses + "no change" option
2722 :children => {:count => 3} # 2 statuses + "no change" option
2708 end
2723 end
2709
2724
2710 def test_bulk_edit_should_propose_target_project_open_shared_versions
2725 def test_bulk_edit_should_propose_target_project_open_shared_versions
2711 @request.session[:user_id] = 2
2726 @request.session[:user_id] = 2
2712 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2727 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2713 assert_response :success
2728 assert_response :success
2714 assert_template 'bulk_edit'
2729 assert_template 'bulk_edit'
2715 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
2730 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
2716 assert_tag 'select',
2731 assert_tag 'select',
2717 :attributes => {:name => 'issue[fixed_version_id]'},
2732 :attributes => {:name => 'issue[fixed_version_id]'},
2718 :descendant => {:tag => 'option', :content => '2.0'}
2733 :descendant => {:tag => 'option', :content => '2.0'}
2719 end
2734 end
2720
2735
2721 def test_bulk_edit_should_propose_target_project_categories
2736 def test_bulk_edit_should_propose_target_project_categories
2722 @request.session[:user_id] = 2
2737 @request.session[:user_id] = 2
2723 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2738 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
2724 assert_response :success
2739 assert_response :success
2725 assert_template 'bulk_edit'
2740 assert_template 'bulk_edit'
2726 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
2741 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
2727 assert_tag 'select',
2742 assert_tag 'select',
2728 :attributes => {:name => 'issue[category_id]'},
2743 :attributes => {:name => 'issue[category_id]'},
2729 :descendant => {:tag => 'option', :content => 'Recipes'}
2744 :descendant => {:tag => 'option', :content => 'Recipes'}
2730 end
2745 end
2731
2746
2732 def test_bulk_update
2747 def test_bulk_update
2733 @request.session[:user_id] = 2
2748 @request.session[:user_id] = 2
2734 # update issues priority
2749 # update issues priority
2735 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2750 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2736 :issue => {:priority_id => 7,
2751 :issue => {:priority_id => 7,
2737 :assigned_to_id => '',
2752 :assigned_to_id => '',
2738 :custom_field_values => {'2' => ''}}
2753 :custom_field_values => {'2' => ''}}
2739
2754
2740 assert_response 302
2755 assert_response 302
2741 # check that the issues were updated
2756 # check that the issues were updated
2742 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
2757 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
2743
2758
2744 issue = Issue.find(1)
2759 issue = Issue.find(1)
2745 journal = issue.journals.find(:first, :order => 'created_on DESC')
2760 journal = issue.journals.find(:first, :order => 'created_on DESC')
2746 assert_equal '125', issue.custom_value_for(2).value
2761 assert_equal '125', issue.custom_value_for(2).value
2747 assert_equal 'Bulk editing', journal.notes
2762 assert_equal 'Bulk editing', journal.notes
2748 assert_equal 1, journal.details.size
2763 assert_equal 1, journal.details.size
2749 end
2764 end
2750
2765
2751 def test_bulk_update_with_group_assignee
2766 def test_bulk_update_with_group_assignee
2752 group = Group.find(11)
2767 group = Group.find(11)
2753 project = Project.find(1)
2768 project = Project.find(1)
2754 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
2769 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
2755
2770
2756 @request.session[:user_id] = 2
2771 @request.session[:user_id] = 2
2757 # update issues assignee
2772 # update issues assignee
2758 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2773 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
2759 :issue => {:priority_id => '',
2774 :issue => {:priority_id => '',
2760 :assigned_to_id => group.id,
2775 :assigned_to_id => group.id,
2761 :custom_field_values => {'2' => ''}}
2776 :custom_field_values => {'2' => ''}}
2762
2777
2763 assert_response 302
2778 assert_response 302
2764 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
2779 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
2765 end
2780 end
2766
2781
2767 def test_bulk_update_on_different_projects
2782 def test_bulk_update_on_different_projects
2768 @request.session[:user_id] = 2
2783 @request.session[:user_id] = 2
2769 # update issues priority
2784 # update issues priority
2770 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
2785 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
2771 :issue => {:priority_id => 7,
2786 :issue => {:priority_id => 7,
2772 :assigned_to_id => '',
2787 :assigned_to_id => '',
2773 :custom_field_values => {'2' => ''}}
2788 :custom_field_values => {'2' => ''}}
2774
2789
2775 assert_response 302
2790 assert_response 302
2776 # check that the issues were updated
2791 # check that the issues were updated
2777 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
2792 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
2778
2793
2779 issue = Issue.find(1)
2794 issue = Issue.find(1)
2780 journal = issue.journals.find(:first, :order => 'created_on DESC')
2795 journal = issue.journals.find(:first, :order => 'created_on DESC')
2781 assert_equal '125', issue.custom_value_for(2).value
2796 assert_equal '125', issue.custom_value_for(2).value
2782 assert_equal 'Bulk editing', journal.notes
2797 assert_equal 'Bulk editing', journal.notes
2783 assert_equal 1, journal.details.size
2798 assert_equal 1, journal.details.size
2784 end
2799 end
2785
2800
2786 def test_bulk_update_on_different_projects_without_rights
2801 def test_bulk_update_on_different_projects_without_rights
2787 @request.session[:user_id] = 3
2802 @request.session[:user_id] = 3
2788 user = User.find(3)
2803 user = User.find(3)
2789 action = { :controller => "issues", :action => "bulk_update" }
2804 action = { :controller => "issues", :action => "bulk_update" }
2790 assert user.allowed_to?(action, Issue.find(1).project)
2805 assert user.allowed_to?(action, Issue.find(1).project)
2791 assert ! user.allowed_to?(action, Issue.find(6).project)
2806 assert ! user.allowed_to?(action, Issue.find(6).project)
2792 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2807 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
2793 :issue => {:priority_id => 7,
2808 :issue => {:priority_id => 7,
2794 :assigned_to_id => '',
2809 :assigned_to_id => '',
2795 :custom_field_values => {'2' => ''}}
2810 :custom_field_values => {'2' => ''}}
2796 assert_response 403
2811 assert_response 403
2797 assert_not_equal "Bulk should fail", Journal.last.notes
2812 assert_not_equal "Bulk should fail", Journal.last.notes
2798 end
2813 end
2799
2814
2800 def test_bullk_update_should_send_a_notification
2815 def test_bullk_update_should_send_a_notification
2801 @request.session[:user_id] = 2
2816 @request.session[:user_id] = 2
2802 ActionMailer::Base.deliveries.clear
2817 ActionMailer::Base.deliveries.clear
2803 post(:bulk_update,
2818 post(:bulk_update,
2804 {
2819 {
2805 :ids => [1, 2],
2820 :ids => [1, 2],
2806 :notes => 'Bulk editing',
2821 :notes => 'Bulk editing',
2807 :issue => {
2822 :issue => {
2808 :priority_id => 7,
2823 :priority_id => 7,
2809 :assigned_to_id => '',
2824 :assigned_to_id => '',
2810 :custom_field_values => {'2' => ''}
2825 :custom_field_values => {'2' => ''}
2811 }
2826 }
2812 })
2827 })
2813
2828
2814 assert_response 302
2829 assert_response 302
2815 assert_equal 2, ActionMailer::Base.deliveries.size
2830 assert_equal 2, ActionMailer::Base.deliveries.size
2816 end
2831 end
2817
2832
2818 def test_bulk_update_project
2833 def test_bulk_update_project
2819 @request.session[:user_id] = 2
2834 @request.session[:user_id] = 2
2820 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
2835 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
2821 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2836 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2822 # Issues moved to project 2
2837 # Issues moved to project 2
2823 assert_equal 2, Issue.find(1).project_id
2838 assert_equal 2, Issue.find(1).project_id
2824 assert_equal 2, Issue.find(2).project_id
2839 assert_equal 2, Issue.find(2).project_id
2825 # No tracker change
2840 # No tracker change
2826 assert_equal 1, Issue.find(1).tracker_id
2841 assert_equal 1, Issue.find(1).tracker_id
2827 assert_equal 2, Issue.find(2).tracker_id
2842 assert_equal 2, Issue.find(2).tracker_id
2828 end
2843 end
2829
2844
2830 def test_bulk_update_project_on_single_issue_should_follow_when_needed
2845 def test_bulk_update_project_on_single_issue_should_follow_when_needed
2831 @request.session[:user_id] = 2
2846 @request.session[:user_id] = 2
2832 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
2847 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
2833 assert_redirected_to '/issues/1'
2848 assert_redirected_to '/issues/1'
2834 end
2849 end
2835
2850
2836 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
2851 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
2837 @request.session[:user_id] = 2
2852 @request.session[:user_id] = 2
2838 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
2853 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
2839 assert_redirected_to '/projects/onlinestore/issues'
2854 assert_redirected_to '/projects/onlinestore/issues'
2840 end
2855 end
2841
2856
2842 def test_bulk_update_tracker
2857 def test_bulk_update_tracker
2843 @request.session[:user_id] = 2
2858 @request.session[:user_id] = 2
2844 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
2859 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
2845 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2860 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2846 assert_equal 2, Issue.find(1).tracker_id
2861 assert_equal 2, Issue.find(1).tracker_id
2847 assert_equal 2, Issue.find(2).tracker_id
2862 assert_equal 2, Issue.find(2).tracker_id
2848 end
2863 end
2849
2864
2850 def test_bulk_update_status
2865 def test_bulk_update_status
2851 @request.session[:user_id] = 2
2866 @request.session[:user_id] = 2
2852 # update issues priority
2867 # update issues priority
2853 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2868 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
2854 :issue => {:priority_id => '',
2869 :issue => {:priority_id => '',
2855 :assigned_to_id => '',
2870 :assigned_to_id => '',
2856 :status_id => '5'}
2871 :status_id => '5'}
2857
2872
2858 assert_response 302
2873 assert_response 302
2859 issue = Issue.find(1)
2874 issue = Issue.find(1)
2860 assert issue.closed?
2875 assert issue.closed?
2861 end
2876 end
2862
2877
2863 def test_bulk_update_priority
2878 def test_bulk_update_priority
2864 @request.session[:user_id] = 2
2879 @request.session[:user_id] = 2
2865 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
2880 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
2866
2881
2867 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2882 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2868 assert_equal 6, Issue.find(1).priority_id
2883 assert_equal 6, Issue.find(1).priority_id
2869 assert_equal 6, Issue.find(2).priority_id
2884 assert_equal 6, Issue.find(2).priority_id
2870 end
2885 end
2871
2886
2872 def test_bulk_update_with_notes
2887 def test_bulk_update_with_notes
2873 @request.session[:user_id] = 2
2888 @request.session[:user_id] = 2
2874 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
2889 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
2875
2890
2876 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2891 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2877 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
2892 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
2878 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
2893 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
2879 end
2894 end
2880
2895
2881 def test_bulk_update_parent_id
2896 def test_bulk_update_parent_id
2882 @request.session[:user_id] = 2
2897 @request.session[:user_id] = 2
2883 post :bulk_update, :ids => [1, 3],
2898 post :bulk_update, :ids => [1, 3],
2884 :notes => 'Bulk editing parent',
2899 :notes => 'Bulk editing parent',
2885 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2900 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
2886
2901
2887 assert_response 302
2902 assert_response 302
2888 parent = Issue.find(2)
2903 parent = Issue.find(2)
2889 assert_equal parent.id, Issue.find(1).parent_id
2904 assert_equal parent.id, Issue.find(1).parent_id
2890 assert_equal parent.id, Issue.find(3).parent_id
2905 assert_equal parent.id, Issue.find(3).parent_id
2891 assert_equal [1, 3], parent.children.collect(&:id).sort
2906 assert_equal [1, 3], parent.children.collect(&:id).sort
2892 end
2907 end
2893
2908
2894 def test_bulk_update_custom_field
2909 def test_bulk_update_custom_field
2895 @request.session[:user_id] = 2
2910 @request.session[:user_id] = 2
2896 # update issues priority
2911 # update issues priority
2897 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2912 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
2898 :issue => {:priority_id => '',
2913 :issue => {:priority_id => '',
2899 :assigned_to_id => '',
2914 :assigned_to_id => '',
2900 :custom_field_values => {'2' => '777'}}
2915 :custom_field_values => {'2' => '777'}}
2901
2916
2902 assert_response 302
2917 assert_response 302
2903
2918
2904 issue = Issue.find(1)
2919 issue = Issue.find(1)
2905 journal = issue.journals.find(:first, :order => 'created_on DESC')
2920 journal = issue.journals.find(:first, :order => 'created_on DESC')
2906 assert_equal '777', issue.custom_value_for(2).value
2921 assert_equal '777', issue.custom_value_for(2).value
2907 assert_equal 1, journal.details.size
2922 assert_equal 1, journal.details.size
2908 assert_equal '125', journal.details.first.old_value
2923 assert_equal '125', journal.details.first.old_value
2909 assert_equal '777', journal.details.first.value
2924 assert_equal '777', journal.details.first.value
2910 end
2925 end
2911
2926
2912 def test_bulk_update_multi_custom_field
2927 def test_bulk_update_multi_custom_field
2913 field = CustomField.find(1)
2928 field = CustomField.find(1)
2914 field.update_attribute :multiple, true
2929 field.update_attribute :multiple, true
2915
2930
2916 @request.session[:user_id] = 2
2931 @request.session[:user_id] = 2
2917 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
2932 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
2918 :issue => {:priority_id => '',
2933 :issue => {:priority_id => '',
2919 :assigned_to_id => '',
2934 :assigned_to_id => '',
2920 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
2935 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
2921
2936
2922 assert_response 302
2937 assert_response 302
2923
2938
2924 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
2939 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
2925 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
2940 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
2926 # the custom field is not associated with the issue tracker
2941 # the custom field is not associated with the issue tracker
2927 assert_nil Issue.find(2).custom_field_value(1)
2942 assert_nil Issue.find(2).custom_field_value(1)
2928 end
2943 end
2929
2944
2930 def test_bulk_update_unassign
2945 def test_bulk_update_unassign
2931 assert_not_nil Issue.find(2).assigned_to
2946 assert_not_nil Issue.find(2).assigned_to
2932 @request.session[:user_id] = 2
2947 @request.session[:user_id] = 2
2933 # unassign issues
2948 # unassign issues
2934 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
2949 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
2935 assert_response 302
2950 assert_response 302
2936 # check that the issues were updated
2951 # check that the issues were updated
2937 assert_nil Issue.find(2).assigned_to
2952 assert_nil Issue.find(2).assigned_to
2938 end
2953 end
2939
2954
2940 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
2955 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
2941 @request.session[:user_id] = 2
2956 @request.session[:user_id] = 2
2942
2957
2943 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
2958 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
2944
2959
2945 assert_response :redirect
2960 assert_response :redirect
2946 issues = Issue.find([1,2])
2961 issues = Issue.find([1,2])
2947 issues.each do |issue|
2962 issues.each do |issue|
2948 assert_equal 4, issue.fixed_version_id
2963 assert_equal 4, issue.fixed_version_id
2949 assert_not_equal issue.project_id, issue.fixed_version.project_id
2964 assert_not_equal issue.project_id, issue.fixed_version.project_id
2950 end
2965 end
2951 end
2966 end
2952
2967
2953 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
2968 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
2954 @request.session[:user_id] = 2
2969 @request.session[:user_id] = 2
2955 post :bulk_update, :ids => [1,2], :back_url => '/issues'
2970 post :bulk_update, :ids => [1,2], :back_url => '/issues'
2956
2971
2957 assert_response :redirect
2972 assert_response :redirect
2958 assert_redirected_to '/issues'
2973 assert_redirected_to '/issues'
2959 end
2974 end
2960
2975
2961 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2976 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
2962 @request.session[:user_id] = 2
2977 @request.session[:user_id] = 2
2963 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
2978 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
2964
2979
2965 assert_response :redirect
2980 assert_response :redirect
2966 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
2981 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
2967 end
2982 end
2968
2983
2969 def test_bulk_update_with_failure_should_set_flash
2984 def test_bulk_update_with_failure_should_set_flash
2970 @request.session[:user_id] = 2
2985 @request.session[:user_id] = 2
2971 Issue.update_all("subject = ''", "id = 2") # Make it invalid
2986 Issue.update_all("subject = ''", "id = 2") # Make it invalid
2972 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
2987 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
2973
2988
2974 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2989 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
2975 assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
2990 assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
2976 end
2991 end
2977
2992
2978 def test_bulk_copy_to_another_project
2993 def test_bulk_copy_to_another_project
2979 @request.session[:user_id] = 2
2994 @request.session[:user_id] = 2
2980 assert_difference 'Issue.count', 2 do
2995 assert_difference 'Issue.count', 2 do
2981 assert_no_difference 'Project.find(1).issues.count' do
2996 assert_no_difference 'Project.find(1).issues.count' do
2982 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
2997 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
2983 end
2998 end
2984 end
2999 end
2985 assert_redirected_to '/projects/ecookbook/issues'
3000 assert_redirected_to '/projects/ecookbook/issues'
2986 end
3001 end
2987
3002
2988 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3003 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
2989 @request.session[:user_id] = 2
3004 @request.session[:user_id] = 2
2990 issue_before_move = Issue.find(1)
3005 issue_before_move = Issue.find(1)
2991 assert_difference 'Issue.count', 1 do
3006 assert_difference 'Issue.count', 1 do
2992 assert_no_difference 'Project.find(1).issues.count' do
3007 assert_no_difference 'Project.find(1).issues.count' do
2993 post :bulk_update, :ids => [1], :copy => '1',
3008 post :bulk_update, :ids => [1], :copy => '1',
2994 :issue => {
3009 :issue => {
2995 :project_id => '2', :tracker_id => '', :assigned_to_id => '',
3010 :project_id => '2', :tracker_id => '', :assigned_to_id => '',
2996 :status_id => '', :start_date => '', :due_date => ''
3011 :status_id => '', :start_date => '', :due_date => ''
2997 }
3012 }
2998 end
3013 end
2999 end
3014 end
3000 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
3015 issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
3001 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
3016 assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
3002 assert_equal issue_before_move.status_id, issue_after_move.status_id
3017 assert_equal issue_before_move.status_id, issue_after_move.status_id
3003 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
3018 assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
3004 end
3019 end
3005
3020
3006 def test_bulk_copy_should_allow_changing_the_issue_attributes
3021 def test_bulk_copy_should_allow_changing_the_issue_attributes
3007 # Fixes random test failure with Mysql
3022 # Fixes random test failure with Mysql
3008 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3023 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3009 # doesn't return the expected results
3024 # doesn't return the expected results
3010 Issue.delete_all("project_id=2")
3025 Issue.delete_all("project_id=2")
3011
3026
3012 @request.session[:user_id] = 2
3027 @request.session[:user_id] = 2
3013 assert_difference 'Issue.count', 2 do
3028 assert_difference 'Issue.count', 2 do
3014 assert_no_difference 'Project.find(1).issues.count' do
3029 assert_no_difference 'Project.find(1).issues.count' do
3015 post :bulk_update, :ids => [1, 2], :copy => '1',
3030 post :bulk_update, :ids => [1, 2], :copy => '1',
3016 :issue => {
3031 :issue => {
3017 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3032 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3018 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3033 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3019 }
3034 }
3020 end
3035 end
3021 end
3036 end
3022
3037
3023 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3038 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3024 assert_equal 2, copied_issues.size
3039 assert_equal 2, copied_issues.size
3025 copied_issues.each do |issue|
3040 copied_issues.each do |issue|
3026 assert_equal 2, issue.project_id, "Project is incorrect"
3041 assert_equal 2, issue.project_id, "Project is incorrect"
3027 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3042 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3028 assert_equal 3, issue.status_id, "Status is incorrect"
3043 assert_equal 3, issue.status_id, "Status is incorrect"
3029 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3044 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3030 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3045 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3031 end
3046 end
3032 end
3047 end
3033
3048
3034 def test_bulk_copy_should_allow_adding_a_note
3049 def test_bulk_copy_should_allow_adding_a_note
3035 @request.session[:user_id] = 2
3050 @request.session[:user_id] = 2
3036 assert_difference 'Issue.count', 1 do
3051 assert_difference 'Issue.count', 1 do
3037 post :bulk_update, :ids => [1], :copy => '1',
3052 post :bulk_update, :ids => [1], :copy => '1',
3038 :notes => 'Copying one issue',
3053 :notes => 'Copying one issue',
3039 :issue => {
3054 :issue => {
3040 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3055 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3041 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3056 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3042 }
3057 }
3043 end
3058 end
3044
3059
3045 issue = Issue.first(:order => 'id DESC')
3060 issue = Issue.first(:order => 'id DESC')
3046 assert_equal 1, issue.journals.size
3061 assert_equal 1, issue.journals.size
3047 journal = issue.journals.first
3062 journal = issue.journals.first
3048 assert_equal 0, journal.details.size
3063 assert_equal 0, journal.details.size
3049 assert_equal 'Copying one issue', journal.notes
3064 assert_equal 'Copying one issue', journal.notes
3050 end
3065 end
3051
3066
3052 def test_bulk_copy_to_another_project_should_follow_when_needed
3067 def test_bulk_copy_to_another_project_should_follow_when_needed
3053 @request.session[:user_id] = 2
3068 @request.session[:user_id] = 2
3054 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3069 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3055 issue = Issue.first(:order => 'id DESC')
3070 issue = Issue.first(:order => 'id DESC')
3056 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3071 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3057 end
3072 end
3058
3073
3059 def test_destroy_issue_with_no_time_entries
3074 def test_destroy_issue_with_no_time_entries
3060 assert_nil TimeEntry.find_by_issue_id(2)
3075 assert_nil TimeEntry.find_by_issue_id(2)
3061 @request.session[:user_id] = 2
3076 @request.session[:user_id] = 2
3062
3077
3063 assert_difference 'Issue.count', -1 do
3078 assert_difference 'Issue.count', -1 do
3064 delete :destroy, :id => 2
3079 delete :destroy, :id => 2
3065 end
3080 end
3066 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3081 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3067 assert_nil Issue.find_by_id(2)
3082 assert_nil Issue.find_by_id(2)
3068 end
3083 end
3069
3084
3070 def test_destroy_issues_with_time_entries
3085 def test_destroy_issues_with_time_entries
3071 @request.session[:user_id] = 2
3086 @request.session[:user_id] = 2
3072
3087
3073 assert_no_difference 'Issue.count' do
3088 assert_no_difference 'Issue.count' do
3074 delete :destroy, :ids => [1, 3]
3089 delete :destroy, :ids => [1, 3]
3075 end
3090 end
3076 assert_response :success
3091 assert_response :success
3077 assert_template 'destroy'
3092 assert_template 'destroy'
3078 assert_not_nil assigns(:hours)
3093 assert_not_nil assigns(:hours)
3079 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3094 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3080 assert_tag 'form',
3095 assert_tag 'form',
3081 :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}}
3096 :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}}
3082 end
3097 end
3083
3098
3084 def test_destroy_issues_and_destroy_time_entries
3099 def test_destroy_issues_and_destroy_time_entries
3085 @request.session[:user_id] = 2
3100 @request.session[:user_id] = 2
3086
3101
3087 assert_difference 'Issue.count', -2 do
3102 assert_difference 'Issue.count', -2 do
3088 assert_difference 'TimeEntry.count', -3 do
3103 assert_difference 'TimeEntry.count', -3 do
3089 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3104 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3090 end
3105 end
3091 end
3106 end
3092 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3107 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3093 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3108 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3094 assert_nil TimeEntry.find_by_id([1, 2])
3109 assert_nil TimeEntry.find_by_id([1, 2])
3095 end
3110 end
3096
3111
3097 def test_destroy_issues_and_assign_time_entries_to_project
3112 def test_destroy_issues_and_assign_time_entries_to_project
3098 @request.session[:user_id] = 2
3113 @request.session[:user_id] = 2
3099
3114
3100 assert_difference 'Issue.count', -2 do
3115 assert_difference 'Issue.count', -2 do
3101 assert_no_difference 'TimeEntry.count' do
3116 assert_no_difference 'TimeEntry.count' do
3102 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3117 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3103 end
3118 end
3104 end
3119 end
3105 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3120 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3106 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3121 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3107 assert_nil TimeEntry.find(1).issue_id
3122 assert_nil TimeEntry.find(1).issue_id
3108 assert_nil TimeEntry.find(2).issue_id
3123 assert_nil TimeEntry.find(2).issue_id
3109 end
3124 end
3110
3125
3111 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3126 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3112 @request.session[:user_id] = 2
3127 @request.session[:user_id] = 2
3113
3128
3114 assert_difference 'Issue.count', -2 do
3129 assert_difference 'Issue.count', -2 do
3115 assert_no_difference 'TimeEntry.count' do
3130 assert_no_difference 'TimeEntry.count' do
3116 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3131 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3117 end
3132 end
3118 end
3133 end
3119 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3134 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3120 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3135 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3121 assert_equal 2, TimeEntry.find(1).issue_id
3136 assert_equal 2, TimeEntry.find(1).issue_id
3122 assert_equal 2, TimeEntry.find(2).issue_id
3137 assert_equal 2, TimeEntry.find(2).issue_id
3123 end
3138 end
3124
3139
3125 def test_destroy_issues_from_different_projects
3140 def test_destroy_issues_from_different_projects
3126 @request.session[:user_id] = 2
3141 @request.session[:user_id] = 2
3127
3142
3128 assert_difference 'Issue.count', -3 do
3143 assert_difference 'Issue.count', -3 do
3129 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3144 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3130 end
3145 end
3131 assert_redirected_to :controller => 'issues', :action => 'index'
3146 assert_redirected_to :controller => 'issues', :action => 'index'
3132 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3147 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3133 end
3148 end
3134
3149
3135 def test_destroy_parent_and_child_issues
3150 def test_destroy_parent_and_child_issues
3136 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3151 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3137 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3152 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3138 assert child.is_descendant_of?(parent.reload)
3153 assert child.is_descendant_of?(parent.reload)
3139
3154
3140 @request.session[:user_id] = 2
3155 @request.session[:user_id] = 2
3141 assert_difference 'Issue.count', -2 do
3156 assert_difference 'Issue.count', -2 do
3142 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3157 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3143 end
3158 end
3144 assert_response 302
3159 assert_response 302
3145 end
3160 end
3146
3161
3147 def test_default_search_scope
3162 def test_default_search_scope
3148 get :index
3163 get :index
3149 assert_tag :div, :attributes => {:id => 'quick-search'},
3164 assert_tag :div, :attributes => {:id => 'quick-search'},
3150 :child => {:tag => 'form',
3165 :child => {:tag => 'form',
3151 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
3166 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
3152 end
3167 end
3153 end
3168 end
@@ -1,103 +1,122
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 require 'watchers_controller'
19 require 'watchers_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class WatchersController; def rescue_action(e) raise e end; end
22 class WatchersController; def rescue_action(e) raise e end; end
23
23
24 class WatchersControllerTest < ActionController::TestCase
24 class WatchersControllerTest < ActionController::TestCase
25 fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules,
25 fixtures :projects, :users, :roles, :members, :member_roles, :enabled_modules,
26 :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers
26 :issues, :trackers, :projects_trackers, :issue_statuses, :enumerations, :watchers
27
27
28 def setup
28 def setup
29 @controller = WatchersController.new
29 @controller = WatchersController.new
30 @request = ActionController::TestRequest.new
30 @request = ActionController::TestRequest.new
31 @response = ActionController::TestResponse.new
31 @response = ActionController::TestResponse.new
32 User.current = nil
32 User.current = nil
33 end
33 end
34
34
35 def test_watch
35 def test_watch
36 @request.session[:user_id] = 3
36 @request.session[:user_id] = 3
37 assert_difference('Watcher.count') do
37 assert_difference('Watcher.count') do
38 xhr :post, :watch, :object_type => 'issue', :object_id => '1'
38 xhr :post, :watch, :object_type => 'issue', :object_id => '1'
39 assert_response :success
39 assert_response :success
40 assert @response.body.include?('$$(".issue-1-watcher")')
40 assert @response.body.include?('$$(".issue-1-watcher")')
41 end
41 end
42 assert Issue.find(1).watched_by?(User.find(3))
42 assert Issue.find(1).watched_by?(User.find(3))
43 end
43 end
44
44
45 def test_watch_should_be_denied_without_permission
45 def test_watch_should_be_denied_without_permission
46 Role.find(2).remove_permission! :view_issues
46 Role.find(2).remove_permission! :view_issues
47 @request.session[:user_id] = 3
47 @request.session[:user_id] = 3
48 assert_no_difference('Watcher.count') do
48 assert_no_difference('Watcher.count') do
49 xhr :post, :watch, :object_type => 'issue', :object_id => '1'
49 xhr :post, :watch, :object_type => 'issue', :object_id => '1'
50 assert_response 403
50 assert_response 403
51 end
51 end
52 end
52 end
53
53
54 def test_unwatch
54 def test_unwatch
55 @request.session[:user_id] = 3
55 @request.session[:user_id] = 3
56 assert_difference('Watcher.count', -1) do
56 assert_difference('Watcher.count', -1) do
57 xhr :post, :unwatch, :object_type => 'issue', :object_id => '2'
57 xhr :post, :unwatch, :object_type => 'issue', :object_id => '2'
58 assert_response :success
58 assert_response :success
59 assert @response.body.include?('$$(".issue-2-watcher")')
59 assert @response.body.include?('$$(".issue-2-watcher")')
60 end
60 end
61 assert !Issue.find(1).watched_by?(User.find(3))
61 assert !Issue.find(1).watched_by?(User.find(3))
62 end
62 end
63
63
64 def test_new
64 def test_new
65 @request.session[:user_id] = 2
65 @request.session[:user_id] = 2
66 xhr :get, :new, :object_type => 'issue', :object_id => '2'
66 xhr :get, :new, :object_type => 'issue', :object_id => '2'
67 assert_response :success
67 assert_response :success
68 assert_select_rjs :replace_html, 'ajax-modal'
68 assert_select_rjs :replace_html, 'ajax-modal'
69 end
69 end
70
70
71 def test_new_for_new_record
72 @request.session[:user_id] = 2
73 xhr :get, :new, :project_id => 1
74 assert_response :success
75 assert_select_rjs :replace_html, 'ajax-modal'
76 end
77
71 def test_create
78 def test_create
72 @request.session[:user_id] = 2
79 @request.session[:user_id] = 2
73 assert_difference('Watcher.count') do
80 assert_difference('Watcher.count') do
74 xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_id => '4'}
81 xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_id => '4'}
75 assert_response :success
82 assert_response :success
76 assert_select_rjs :replace_html, 'watchers'
83 assert_select_rjs :replace_html, 'watchers'
77 assert_select_rjs :replace_html, 'ajax-modal'
84 assert_select_rjs :replace_html, 'ajax-modal'
78 end
85 end
79 assert Issue.find(2).watched_by?(User.find(4))
86 assert Issue.find(2).watched_by?(User.find(4))
80 end
87 end
81
88
82 def test_create_multiple
89 def test_create_multiple
83 @request.session[:user_id] = 2
90 @request.session[:user_id] = 2
84 assert_difference('Watcher.count', 2) do
91 assert_difference('Watcher.count', 2) do
85 xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_ids => ['4', '7']}
92 xhr :post, :create, :object_type => 'issue', :object_id => '2', :watcher => {:user_ids => ['4', '7']}
86 assert_response :success
93 assert_response :success
87 assert_select_rjs :replace_html, 'watchers'
94 assert_select_rjs :replace_html, 'watchers'
88 assert_select_rjs :replace_html, 'ajax-modal'
95 assert_select_rjs :replace_html, 'ajax-modal'
89 end
96 end
90 assert Issue.find(2).watched_by?(User.find(4))
97 assert Issue.find(2).watched_by?(User.find(4))
91 assert Issue.find(2).watched_by?(User.find(7))
98 assert Issue.find(2).watched_by?(User.find(7))
92 end
99 end
93
100
101 def test_append
102 @request.session[:user_id] = 2
103 assert_no_difference 'Watcher.count' do
104 xhr :post, :append, :watcher => {:user_ids => ['4', '7']}
105 assert_response :success
106 assert_select_rjs :insert_html, 'watchers_inputs' do
107 assert_select 'input[name=?][value=4]', 'issue[watcher_user_ids][]'
108 assert_select 'input[name=?][value=7]', 'issue[watcher_user_ids][]'
109 end
110 end
111 end
112
94 def test_remove_watcher
113 def test_remove_watcher
95 @request.session[:user_id] = 2
114 @request.session[:user_id] = 2
96 assert_difference('Watcher.count', -1) do
115 assert_difference('Watcher.count', -1) do
97 xhr :post, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3'
116 xhr :post, :destroy, :object_type => 'issue', :object_id => '2', :user_id => '3'
98 assert_response :success
117 assert_response :success
99 assert_select_rjs :replace_html, 'watchers'
118 assert_select_rjs :replace_html, 'watchers'
100 end
119 end
101 assert !Issue.find(2).watched_by?(User.find(3))
120 assert !Issue.find(2).watched_by?(User.find(3))
102 end
121 end
103 end
122 end
@@ -1,47 +1,51
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 RoutingWatchersTest < ActionController::IntegrationTest
20 class RoutingWatchersTest < ActionController::IntegrationTest
21 def test_watchers
21 def test_watchers
22 assert_routing(
22 assert_routing(
23 { :method => 'get', :path => "/watchers/new" },
23 { :method => 'get', :path => "/watchers/new" },
24 { :controller => 'watchers', :action => 'new' }
24 { :controller => 'watchers', :action => 'new' }
25 )
25 )
26 assert_routing(
26 assert_routing(
27 { :method => 'post', :path => "/watchers/append" },
28 { :controller => 'watchers', :action => 'append' }
29 )
30 assert_routing(
27 { :method => 'post', :path => "/watchers" },
31 { :method => 'post', :path => "/watchers" },
28 { :controller => 'watchers', :action => 'create' }
32 { :controller => 'watchers', :action => 'create' }
29 )
33 )
30 assert_routing(
34 assert_routing(
31 { :method => 'post', :path => "/watchers/destroy" },
35 { :method => 'post', :path => "/watchers/destroy" },
32 { :controller => 'watchers', :action => 'destroy' }
36 { :controller => 'watchers', :action => 'destroy' }
33 )
37 )
34 assert_routing(
38 assert_routing(
35 { :method => 'get', :path => "/watchers/autocomplete_for_user" },
39 { :method => 'get', :path => "/watchers/autocomplete_for_user" },
36 { :controller => 'watchers', :action => 'autocomplete_for_user' }
40 { :controller => 'watchers', :action => 'autocomplete_for_user' }
37 )
41 )
38 assert_routing(
42 assert_routing(
39 { :method => 'post', :path => "/watchers/watch" },
43 { :method => 'post', :path => "/watchers/watch" },
40 { :controller => 'watchers', :action => 'watch' }
44 { :controller => 'watchers', :action => 'watch' }
41 )
45 )
42 assert_routing(
46 assert_routing(
43 { :method => 'post', :path => "/watchers/unwatch" },
47 { :method => 'post', :path => "/watchers/unwatch" },
44 { :controller => 'watchers', :action => 'unwatch' }
48 { :controller => 'watchers', :action => 'unwatch' }
45 )
49 )
46 end
50 end
47 end
51 end
General Comments 0
You need to be logged in to leave comments. Login now