##// END OF EJS Templates
Configurable behavour for linking issues on copy (#18500)....
Jean-Philippe Lang -
r13286:30d65829b859
parent child
Show More
@@ -1,489 +1,502
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssuesController < ApplicationController
19 19 menu_item :new_issue, :only => [:new, :create]
20 20 default_search_scope :issues
21 21
22 22 before_filter :find_issue, :only => [:show, :edit, :update]
23 23 before_filter :find_issues, :only => [:bulk_edit, :bulk_update, :destroy]
24 24 before_filter :find_project, :only => [:new, :create, :update_form]
25 25 before_filter :authorize, :except => [:index]
26 26 before_filter :find_optional_project, :only => [:index]
27 27 before_filter :build_new_issue_from_params, :only => [:new, :create, :update_form]
28 28 accept_rss_auth :index, :show
29 29 accept_api_auth :index, :show, :create, :update, :destroy
30 30
31 31 rescue_from Query::StatementInvalid, :with => :query_statement_invalid
32 32
33 33 helper :journals
34 34 helper :projects
35 35 include ProjectsHelper
36 36 helper :custom_fields
37 37 include CustomFieldsHelper
38 38 helper :issue_relations
39 39 include IssueRelationsHelper
40 40 helper :watchers
41 41 include WatchersHelper
42 42 helper :attachments
43 43 include AttachmentsHelper
44 44 helper :queries
45 45 include QueriesHelper
46 46 helper :repositories
47 47 include RepositoriesHelper
48 48 helper :sort
49 49 include SortHelper
50 50 include IssuesHelper
51 51 helper :timelog
52 52 include Redmine::Export::PDF
53 53
54 54 def index
55 55 retrieve_query
56 56 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
57 57 sort_update(@query.sortable_columns)
58 58 @query.sort_criteria = sort_criteria.to_a
59 59
60 60 if @query.valid?
61 61 case params[:format]
62 62 when 'csv', 'pdf'
63 63 @limit = Setting.issues_export_limit.to_i
64 64 if params[:columns] == 'all'
65 65 @query.column_names = @query.available_inline_columns.map(&:name)
66 66 end
67 67 when 'atom'
68 68 @limit = Setting.feeds_limit.to_i
69 69 when 'xml', 'json'
70 70 @offset, @limit = api_offset_and_limit
71 71 @query.column_names = %w(author)
72 72 else
73 73 @limit = per_page_option
74 74 end
75 75
76 76 @issue_count = @query.issue_count
77 77 @issue_pages = Paginator.new @issue_count, @limit, params['page']
78 78 @offset ||= @issue_pages.offset
79 79 @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
80 80 :order => sort_clause,
81 81 :offset => @offset,
82 82 :limit => @limit)
83 83 @issue_count_by_group = @query.issue_count_by_group
84 84
85 85 respond_to do |format|
86 86 format.html { render :template => 'issues/index', :layout => !request.xhr? }
87 87 format.api {
88 88 Issue.load_visible_relations(@issues) if include_in_api_response?('relations')
89 89 }
90 90 format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
91 91 format.csv { send_data(query_to_csv(@issues, @query, params), :type => 'text/csv; header=present', :filename => 'issues.csv') }
92 92 format.pdf { send_data(issues_to_pdf(@issues, @project, @query), :type => 'application/pdf', :filename => 'issues.pdf') }
93 93 end
94 94 else
95 95 respond_to do |format|
96 96 format.html { render(:template => 'issues/index', :layout => !request.xhr?) }
97 97 format.any(:atom, :csv, :pdf) { render(:nothing => true) }
98 98 format.api { render_validation_errors(@query) }
99 99 end
100 100 end
101 101 rescue ActiveRecord::RecordNotFound
102 102 render_404
103 103 end
104 104
105 105 def show
106 106 @journals = @issue.journals.includes(:user, :details).
107 107 references(:user, :details).
108 108 reorder("#{Journal.table_name}.id ASC").to_a
109 109 @journals.each_with_index {|j,i| j.indice = i+1}
110 110 @journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
111 111 Journal.preload_journals_details_custom_fields(@journals)
112 112 @journals.select! {|journal| journal.notes? || journal.visible_details.any?}
113 113 @journals.reverse! if User.current.wants_comments_in_reverse_order?
114 114
115 115 @changesets = @issue.changesets.visible.to_a
116 116 @changesets.reverse! if User.current.wants_comments_in_reverse_order?
117 117
118 118 @relations = @issue.relations.select {|r| r.other_issue(@issue) && r.other_issue(@issue).visible? }
119 119 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
120 120 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
121 121 @priorities = IssuePriority.active
122 122 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
123 123 @relation = IssueRelation.new
124 124
125 125 respond_to do |format|
126 126 format.html {
127 127 retrieve_previous_and_next_issue_ids
128 128 render :template => 'issues/show'
129 129 }
130 130 format.api
131 131 format.atom { render :template => 'journals/index', :layout => false, :content_type => 'application/atom+xml' }
132 132 format.pdf {
133 133 pdf = issue_to_pdf(@issue, :journals => @journals)
134 134 send_data(pdf, :type => 'application/pdf', :filename => "#{@project.identifier}-#{@issue.id}.pdf")
135 135 }
136 136 end
137 137 end
138 138
139 139 # Add a new issue
140 140 # The new issue will be created from an existing one if copy_from parameter is given
141 141 def new
142 142 respond_to do |format|
143 143 format.html { render :action => 'new', :layout => !request.xhr? }
144 144 end
145 145 end
146 146
147 147 def create
148 148 call_hook(:controller_issues_new_before_save, { :params => params, :issue => @issue })
149 149 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
150 150 if @issue.save
151 151 call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
152 152 respond_to do |format|
153 153 format.html {
154 154 render_attachment_warning_if_needed(@issue)
155 155 flash[:notice] = l(:notice_issue_successful_create, :id => view_context.link_to("##{@issue.id}", issue_path(@issue), :title => @issue.subject))
156 156 if params[:continue]
157 157 attrs = {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?}
158 158 redirect_to new_project_issue_path(@issue.project, :issue => attrs)
159 159 else
160 160 redirect_to issue_path(@issue)
161 161 end
162 162 }
163 163 format.api { render :action => 'show', :status => :created, :location => issue_url(@issue) }
164 164 end
165 165 return
166 166 else
167 167 respond_to do |format|
168 168 format.html { render :action => 'new' }
169 169 format.api { render_validation_errors(@issue) }
170 170 end
171 171 end
172 172 end
173 173
174 174 def edit
175 175 return unless update_issue_from_params
176 176
177 177 respond_to do |format|
178 178 format.html { }
179 179 format.xml { }
180 180 end
181 181 end
182 182
183 183 def update
184 184 return unless update_issue_from_params
185 185 @issue.save_attachments(params[:attachments] || (params[:issue] && params[:issue][:uploads]))
186 186 saved = false
187 187 begin
188 188 saved = save_issue_with_child_records
189 189 rescue ActiveRecord::StaleObjectError
190 190 @conflict = true
191 191 if params[:last_journal_id]
192 192 @conflict_journals = @issue.journals_after(params[:last_journal_id]).to_a
193 193 @conflict_journals.reject!(&:private_notes?) unless User.current.allowed_to?(:view_private_notes, @issue.project)
194 194 end
195 195 end
196 196
197 197 if saved
198 198 render_attachment_warning_if_needed(@issue)
199 199 flash[:notice] = l(:notice_successful_update) unless @issue.current_journal.new_record?
200 200
201 201 respond_to do |format|
202 202 format.html { redirect_back_or_default issue_path(@issue) }
203 203 format.api { render_api_ok }
204 204 end
205 205 else
206 206 respond_to do |format|
207 207 format.html { render :action => 'edit' }
208 208 format.api { render_validation_errors(@issue) }
209 209 end
210 210 end
211 211 end
212 212
213 213 # Updates the issue form when changing the project, status or tracker
214 214 # on issue creation/update
215 215 def update_form
216 216 end
217 217
218 218 # Bulk edit/copy a set of issues
219 219 def bulk_edit
220 220 @issues.sort!
221 221 @copy = params[:copy].present?
222 222 @notes = params[:notes]
223 223
224 224 if User.current.allowed_to?(:move_issues, @projects)
225 225 @allowed_projects = Issue.allowed_target_projects_on_move
226 226 if params[:issue]
227 227 @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:issue][:project_id].to_s}
228 228 if @target_project
229 229 target_projects = [@target_project]
230 230 end
231 231 end
232 232 end
233 233 target_projects ||= @projects
234 234
235 235 if @copy
236 236 # Copied issues will get their default statuses
237 237 @available_statuses = []
238 238 else
239 239 @available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
240 240 end
241 241 @custom_fields = target_projects.map{|p|p.all_issue_custom_fields.visible}.reduce(:&)
242 242 @assignables = target_projects.map(&:assignable_users).reduce(:&)
243 243 @trackers = target_projects.map(&:trackers).reduce(:&)
244 244 @versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
245 245 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
246 246 if @copy
247 247 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
248 248 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
249 249 end
250 250
251 251 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
252 252
253 253 @issue_params = params[:issue] || {}
254 254 @issue_params[:custom_field_values] ||= {}
255 255 end
256 256
257 257 def bulk_update
258 258 @issues.sort!
259 259 @copy = params[:copy].present?
260 260 attributes = parse_params_for_bulk_issue_attributes(params)
261 261
262 262 unsaved_issues = []
263 263 saved_issues = []
264 264
265 265 if @copy && params[:copy_subtasks].present?
266 266 # Descendant issues will be copied with the parent task
267 267 # Don't copy them twice
268 268 @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}}
269 269 end
270 270
271 271 @issues.each do |orig_issue|
272 272 orig_issue.reload
273 273 if @copy
274 274 issue = orig_issue.copy({},
275 275 :attachments => params[:copy_attachments].present?,
276 :subtasks => params[:copy_subtasks].present?
276 :subtasks => params[:copy_subtasks].present?,
277 :link => link_copy?(params[:link_copy])
277 278 )
278 279 else
279 280 issue = orig_issue
280 281 end
281 282 journal = issue.init_journal(User.current, params[:notes])
282 283 issue.safe_attributes = attributes
283 284 call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
284 285 if issue.save
285 286 saved_issues << issue
286 287 else
287 288 unsaved_issues << orig_issue
288 289 end
289 290 end
290 291
291 292 if unsaved_issues.empty?
292 293 flash[:notice] = l(:notice_successful_update) unless saved_issues.empty?
293 294 if params[:follow]
294 295 if @issues.size == 1 && saved_issues.size == 1
295 296 redirect_to issue_path(saved_issues.first)
296 297 elsif saved_issues.map(&:project).uniq.size == 1
297 298 redirect_to project_issues_path(saved_issues.map(&:project).first)
298 299 end
299 300 else
300 301 redirect_back_or_default _project_issues_path(@project)
301 302 end
302 303 else
303 304 @saved_issues = @issues
304 305 @unsaved_issues = unsaved_issues
305 306 @issues = Issue.visible.where(:id => @unsaved_issues.map(&:id)).to_a
306 307 bulk_edit
307 308 render :action => 'bulk_edit'
308 309 end
309 310 end
310 311
311 312 def destroy
312 313 @hours = TimeEntry.where(:issue_id => @issues.map(&:id)).sum(:hours).to_f
313 314 if @hours > 0
314 315 case params[:todo]
315 316 when 'destroy'
316 317 # nothing to do
317 318 when 'nullify'
318 319 TimeEntry.where(['issue_id IN (?)', @issues]).update_all('issue_id = NULL')
319 320 when 'reassign'
320 321 reassign_to = @project.issues.find_by_id(params[:reassign_to_id])
321 322 if reassign_to.nil?
322 323 flash.now[:error] = l(:error_issue_not_found_in_project)
323 324 return
324 325 else
325 326 TimeEntry.where(['issue_id IN (?)', @issues]).
326 327 update_all("issue_id = #{reassign_to.id}")
327 328 end
328 329 else
329 330 # display the destroy form if it's a user request
330 331 return unless api_request?
331 332 end
332 333 end
333 334 @issues.each do |issue|
334 335 begin
335 336 issue.reload.destroy
336 337 rescue ::ActiveRecord::RecordNotFound # raised by #reload if issue no longer exists
337 338 # nothing to do, issue was already deleted (eg. by a parent)
338 339 end
339 340 end
340 341 respond_to do |format|
341 342 format.html { redirect_back_or_default _project_issues_path(@project) }
342 343 format.api { render_api_ok }
343 344 end
344 345 end
345 346
346 347 private
347 348
348 349 def find_project
349 350 project_id = params[:project_id] || (params[:issue] && params[:issue][:project_id])
350 351 @project = Project.find(project_id)
351 352 rescue ActiveRecord::RecordNotFound
352 353 render_404
353 354 end
354 355
355 356 def retrieve_previous_and_next_issue_ids
356 357 retrieve_query_from_session
357 358 if @query
358 359 sort_init(@query.sort_criteria.empty? ? [['id', 'desc']] : @query.sort_criteria)
359 360 sort_update(@query.sortable_columns, 'issues_index_sort')
360 361 limit = 500
361 362 issue_ids = @query.issue_ids(:order => sort_clause, :limit => (limit + 1), :include => [:assigned_to, :tracker, :priority, :category, :fixed_version])
362 363 if (idx = issue_ids.index(@issue.id)) && idx < limit
363 364 if issue_ids.size < 500
364 365 @issue_position = idx + 1
365 366 @issue_count = issue_ids.size
366 367 end
367 368 @prev_issue_id = issue_ids[idx - 1] if idx > 0
368 369 @next_issue_id = issue_ids[idx + 1] if idx < (issue_ids.size - 1)
369 370 end
370 371 end
371 372 end
372 373
373 374 # Used by #edit and #update to set some common instance variables
374 375 # from the params
375 376 # TODO: Refactor, not everything in here is needed by #edit
376 377 def update_issue_from_params
377 378 @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
378 379 @time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
379 380 if params[:time_entry]
380 381 @time_entry.attributes = params[:time_entry]
381 382 end
382 383
383 384 @issue.init_journal(User.current)
384 385
385 386 issue_attributes = params[:issue]
386 387 if issue_attributes && params[:conflict_resolution]
387 388 case params[:conflict_resolution]
388 389 when 'overwrite'
389 390 issue_attributes = issue_attributes.dup
390 391 issue_attributes.delete(:lock_version)
391 392 when 'add_notes'
392 393 issue_attributes = issue_attributes.slice(:notes)
393 394 when 'cancel'
394 395 redirect_to issue_path(@issue)
395 396 return false
396 397 end
397 398 end
398 399 @issue.safe_attributes = issue_attributes
399 400 @priorities = IssuePriority.active
400 401 @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
401 402 true
402 403 end
403 404
404 405 # TODO: Refactor, lots of extra code in here
405 406 # TODO: Changing tracker on an existing issue should not trigger this
406 407 def build_new_issue_from_params
407 408 if params[:id].blank?
408 409 @issue = Issue.new
409 410 @issue.init_journal(User.current)
410 411 if params[:copy_from]
411 412 begin
412 413 @copy_from = Issue.visible.find(params[:copy_from])
414 @link_copy = link_copy?(params[:link_copy]) || request.get?
413 415 @copy_attachments = params[:copy_attachments].present? || request.get?
414 416 @copy_subtasks = params[:copy_subtasks].present? || request.get?
415 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
417 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks, :link => @link_copy)
416 418 rescue ActiveRecord::RecordNotFound
417 419 render_404
418 420 return
419 421 end
420 422 end
421 423 @issue.project = @project
422 424 @issue.author ||= User.current
423 425 @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
424 426 else
425 427 @issue = @project.issues.visible.find(params[:id])
426 428 end
427 429
428 430 if attrs = params[:issue].deep_dup
429 431 if params[:was_default_status] == attrs[:status_id]
430 432 attrs.delete(:status_id)
431 433 end
432 434 @issue.safe_attributes = attrs
433 435 end
434 436 @issue.tracker ||= @project.trackers.first
435 437 if @issue.tracker.nil?
436 438 render_error l(:error_no_tracker_in_project)
437 439 return false
438 440 end
439 441 if @issue.status.nil?
440 442 render_error l(:error_no_default_issue_status)
441 443 return false
442 444 end
443 445
444 446 @priorities = IssuePriority.active
445 447 @allowed_statuses = @issue.new_statuses_allowed_to(User.current, @issue.new_record?)
446 448 @available_watchers = @issue.watcher_users
447 449 if @issue.project.users.count <= 20
448 450 @available_watchers = (@available_watchers + @issue.project.users.sort).uniq
449 451 end
450 452 end
451 453
452 454 def parse_params_for_bulk_issue_attributes(params)
453 455 attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
454 456 attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
455 457 if custom = attributes[:custom_field_values]
456 458 custom.reject! {|k,v| v.blank?}
457 459 custom.keys.each do |k|
458 460 if custom[k].is_a?(Array)
459 461 custom[k] << '' if custom[k].delete('__none__')
460 462 else
461 463 custom[k] = '' if custom[k] == '__none__'
462 464 end
463 465 end
464 466 end
465 467 attributes
466 468 end
467 469
468 470 # Saves @issue and a time_entry from the parameters
469 471 def save_issue_with_child_records
470 472 Issue.transaction do
471 473 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, @issue.project)
472 474 time_entry = @time_entry || TimeEntry.new
473 475 time_entry.project = @issue.project
474 476 time_entry.issue = @issue
475 477 time_entry.user = User.current
476 478 time_entry.spent_on = User.current.today
477 479 time_entry.attributes = params[:time_entry]
478 480 @issue.time_entries << time_entry
479 481 end
480 482
481 483 call_hook(:controller_issues_edit_before_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
482 484 if @issue.save
483 485 call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => time_entry, :journal => @issue.current_journal})
484 486 else
485 487 raise ActiveRecord::Rollback
486 488 end
487 489 end
488 490 end
491
492 def link_copy?(param)
493 case Setting.link_copied_issue
494 when 'yes'
495 true
496 when 'no'
497 false
498 when 'ask'
499 param == '1'
500 end
501 end
489 502 end
@@ -1,118 +1,128
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2014 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module SettingsHelper
21 21 def administration_settings_tabs
22 22 tabs = [{:name => 'general', :partial => 'settings/general', :label => :label_general},
23 23 {:name => 'display', :partial => 'settings/display', :label => :label_display},
24 24 {:name => 'authentication', :partial => 'settings/authentication', :label => :label_authentication},
25 25 {:name => 'projects', :partial => 'settings/projects', :label => :label_project_plural},
26 26 {:name => 'issues', :partial => 'settings/issues', :label => :label_issue_tracking},
27 27 {:name => 'notifications', :partial => 'settings/notifications', :label => :field_mail_notification},
28 28 {:name => 'mail_handler', :partial => 'settings/mail_handler', :label => :label_incoming_emails},
29 29 {:name => 'repositories', :partial => 'settings/repositories', :label => :label_repository_plural}
30 30 ]
31 31 end
32 32
33 33 def setting_select(setting, choices, options={})
34 34 if blank_text = options.delete(:blank)
35 35 choices = [[blank_text.is_a?(Symbol) ? l(blank_text) : blank_text, '']] + choices
36 36 end
37 37 setting_label(setting, options).html_safe +
38 38 select_tag("settings[#{setting}]",
39 39 options_for_select(choices, Setting.send(setting).to_s),
40 40 options).html_safe
41 41 end
42 42
43 43 def setting_multiselect(setting, choices, options={})
44 44 setting_values = Setting.send(setting)
45 45 setting_values = [] unless setting_values.is_a?(Array)
46 46
47 47 content_tag("label", l(options[:label] || "setting_#{setting}")) +
48 48 hidden_field_tag("settings[#{setting}][]", '').html_safe +
49 49 choices.collect do |choice|
50 50 text, value = (choice.is_a?(Array) ? choice : [choice, choice])
51 51 content_tag(
52 52 'label',
53 53 check_box_tag(
54 54 "settings[#{setting}][]",
55 55 value,
56 56 setting_values.include?(value),
57 57 :id => nil
58 58 ) + text.to_s,
59 59 :class => (options[:inline] ? 'inline' : 'block')
60 60 )
61 61 end.join.html_safe
62 62 end
63 63
64 64 def setting_text_field(setting, options={})
65 65 setting_label(setting, options).html_safe +
66 66 text_field_tag("settings[#{setting}]", Setting.send(setting), options).html_safe
67 67 end
68 68
69 69 def setting_text_area(setting, options={})
70 70 setting_label(setting, options).html_safe +
71 71 text_area_tag("settings[#{setting}]", Setting.send(setting), options).html_safe
72 72 end
73 73
74 74 def setting_check_box(setting, options={})
75 75 setting_label(setting, options).html_safe +
76 76 hidden_field_tag("settings[#{setting}]", 0, :id => nil).html_safe +
77 77 check_box_tag("settings[#{setting}]", 1, Setting.send("#{setting}?"), options).html_safe
78 78 end
79 79
80 80 def setting_label(setting, options={})
81 81 label = options.delete(:label)
82 82 label != false ? label_tag("settings_#{setting}", l(label || "setting_#{setting}"), options[:label_options]).html_safe : ''
83 83 end
84 84
85 85 # Renders a notification field for a Redmine::Notifiable option
86 86 def notification_field(notifiable)
87 87 tag_data = notifiable.parent.present? ?
88 88 {:parent_notifiable => notifiable.parent} :
89 89 {:disables => "input[data-parent-notifiable=#{notifiable.name}]"}
90 90
91 91 tag = check_box_tag('settings[notified_events][]',
92 92 notifiable.name,
93 93 Setting.notified_events.include?(notifiable.name),
94 94 :id => nil,
95 95 :data => tag_data)
96 96
97 97 text = l_or_humanize(notifiable.name, :prefix => 'label_')
98 98
99 99 options = {}
100 100 if notifiable.parent.present?
101 101 options[:class] = "parent"
102 102 end
103 103
104 104 content_tag(:label, tag + text, options)
105 105 end
106 106
107 def link_copied_issue_options
108 options = [
109 [:general_text_Yes, 'yes'],
110 [:general_text_No, 'no'],
111 [:label_ask, 'ask']
112 ]
113
114 options.map {|label, value| [l(label), value.to_s]}
115 end
116
107 117 def cross_project_subtasks_options
108 118 options = [
109 119 [:label_disabled, ''],
110 120 [:label_cross_project_system, 'system'],
111 121 [:label_cross_project_tree, 'tree'],
112 122 [:label_cross_project_hierarchy, 'hierarchy'],
113 123 [:label_cross_project_descendants, 'descendants']
114 124 ]
115 125
116 126 options.map {|label, value| [l(label), value.to_s]}
117 127 end
118 128 end
@@ -1,198 +1,206
1 1 <h2><%= @copy ? l(:button_copy) : l(:label_bulk_edit_selected_issues) %></h2>
2 2
3 3 <% if @saved_issues && @unsaved_issues.present? %>
4 4 <div id="errorExplanation">
5 5 <span>
6 6 <%= l(:notice_failed_to_save_issues,
7 7 :count => @unsaved_issues.size,
8 8 :total => @saved_issues.size,
9 9 :ids => @unsaved_issues.map {|i| "##{i.id}"}.join(', ')) %>
10 10 </span>
11 11 <ul>
12 12 <% bulk_edit_error_messages(@unsaved_issues).each do |message| %>
13 13 <li><%= message %></li>
14 14 <% end %>
15 15 </ul>
16 16 </div>
17 17 <% end %>
18 18
19 19 <ul id="bulk-selection">
20 20 <% @issues.each do |issue| %>
21 21 <%= content_tag 'li', link_to_issue(issue) %>
22 22 <% end %>
23 23 </ul>
24 24
25 25 <%= form_tag(bulk_update_issues_path, :id => 'bulk_edit_form') do %>
26 26 <%= @issues.collect {|i| hidden_field_tag('ids[]', i.id, :id => nil)}.join("\n").html_safe %>
27 27 <div class="box tabular">
28 28 <fieldset class="attributes">
29 29 <legend><%= l(:label_change_properties) %></legend>
30 30
31 31 <div class="splitcontentleft">
32 32 <% if @allowed_projects.present? %>
33 33 <p>
34 34 <label for="issue_project_id"><%= l(:field_project) %></label>
35 35 <%= select_tag('issue[project_id]',
36 36 content_tag('option', l(:label_no_change_option), :value => '') +
37 37 project_tree_options_for_select(@allowed_projects, :selected => @target_project),
38 38 :onchange => "updateBulkEditFrom('#{escape_javascript url_for(:action => 'bulk_edit', :format => 'js')}')") %>
39 39 </p>
40 40 <% end %>
41 41 <p>
42 42 <label for="issue_tracker_id"><%= l(:field_tracker) %></label>
43 43 <%= select_tag('issue[tracker_id]',
44 44 content_tag('option', l(:label_no_change_option), :value => '') +
45 45 options_from_collection_for_select(@trackers, :id, :name, @issue_params[:tracker_id])) %>
46 46 </p>
47 47 <% if @available_statuses.any? %>
48 48 <p>
49 49 <label for='issue_status_id'><%= l(:field_status) %></label>
50 50 <%= select_tag('issue[status_id]',
51 51 content_tag('option', l(:label_no_change_option), :value => '') +
52 52 options_from_collection_for_select(@available_statuses, :id, :name, @issue_params[:status_id])) %>
53 53 </p>
54 54 <% end %>
55 55
56 56 <% if @safe_attributes.include?('priority_id') -%>
57 57 <p>
58 58 <label for='issue_priority_id'><%= l(:field_priority) %></label>
59 59 <%= select_tag('issue[priority_id]',
60 60 content_tag('option', l(:label_no_change_option), :value => '') +
61 61 options_from_collection_for_select(IssuePriority.active, :id, :name, @issue_params[:priority_id])) %>
62 62 </p>
63 63 <% end %>
64 64
65 65 <% if @safe_attributes.include?('assigned_to_id') -%>
66 66 <p>
67 67 <label for='issue_assigned_to_id'><%= l(:field_assigned_to) %></label>
68 68 <%= select_tag('issue[assigned_to_id]',
69 69 content_tag('option', l(:label_no_change_option), :value => '') +
70 70 content_tag('option', l(:label_nobody), :value => 'none', :selected => (@issue_params[:assigned_to_id] == 'none')) +
71 71 principals_options_for_select(@assignables, @issue_params[:assigned_to_id])) %>
72 72 </p>
73 73 <% end %>
74 74
75 75 <% if @safe_attributes.include?('category_id') -%>
76 76 <p>
77 77 <label for='issue_category_id'><%= l(:field_category) %></label>
78 78 <%= select_tag('issue[category_id]', content_tag('option', l(:label_no_change_option), :value => '') +
79 79 content_tag('option', l(:label_none), :value => 'none', :selected => (@issue_params[:category_id] == 'none')) +
80 80 options_from_collection_for_select(@categories, :id, :name, @issue_params[:category_id])) %>
81 81 </p>
82 82 <% end %>
83 83
84 84 <% if @safe_attributes.include?('fixed_version_id') -%>
85 85 <p>
86 86 <label for='issue_fixed_version_id'><%= l(:field_fixed_version) %></label>
87 87 <%= select_tag('issue[fixed_version_id]', content_tag('option', l(:label_no_change_option), :value => '') +
88 88 content_tag('option', l(:label_none), :value => 'none', :selected => (@issue_params[:fixed_version_id] == 'none')) +
89 89 version_options_for_select(@versions.sort, @issue_params[:fixed_version_id])) %>
90 90 </p>
91 91 <% end %>
92 92
93 93 <% @custom_fields.each do |custom_field| %>
94 94 <p>
95 95 <label><%= h(custom_field.name) %></label>
96 96 <%= custom_field_tag_for_bulk_edit('issue', custom_field, @issues, @issue_params[:custom_field_values][custom_field.id.to_s]) %>
97 97 </p>
98 98 <% end %>
99 99
100 <% if @copy && Setting.link_copied_issue == 'ask' %>
101 <p>
102 <label for='link_copy'><%= l(:label_link_copied_issue) %></label>
103 <%= hidden_field_tag 'link_copy', '0' %>
104 <%= check_box_tag 'link_copy', '1', params[:link_copy] != 0 %>
105 </p>
106 <% end %>
107
100 108 <% if @copy && @attachments_present %>
101 109 <%= hidden_field_tag 'copy_attachments', '0' %>
102 110 <p>
103 111 <label for='copy_attachments'><%= l(:label_copy_attachments) %></label>
104 112 <%= check_box_tag 'copy_attachments', '1', params[:copy_attachments] != '0' %>
105 113 </p>
106 114 <% end %>
107 115
108 116 <% if @copy && @subtasks_present %>
109 117 <%= hidden_field_tag 'copy_subtasks', '0' %>
110 118 <p>
111 119 <label for='copy_subtasks'><%= l(:label_copy_subtasks) %></label>
112 120 <%= check_box_tag 'copy_subtasks', '1', params[:copy_subtasks] != '0' %>
113 121 </p>
114 122 <% end %>
115 123
116 124 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
117 125 </div>
118 126
119 127 <div class="splitcontentright">
120 128 <% if @safe_attributes.include?('is_private') %>
121 129 <p>
122 130 <label for='issue_is_private'><%= l(:field_is_private) %></label>
123 131 <%= select_tag('issue[is_private]', content_tag('option', l(:label_no_change_option), :value => '') +
124 132 content_tag('option', l(:general_text_Yes), :value => '1', :selected => (@issue_params[:is_private] == '1')) +
125 133 content_tag('option', l(:general_text_No), :value => '0', :selected => (@issue_params[:is_private] == '0'))) %>
126 134 </p>
127 135 <% end %>
128 136
129 137 <% if @safe_attributes.include?('parent_issue_id') && @project %>
130 138 <p>
131 139 <label for='issue_parent_issue_id'><%= l(:field_parent_issue) %></label>
132 140 <%= text_field_tag 'issue[parent_issue_id]', '', :size => 10, :value => @issue_params[:parent_issue_id] %>
133 141 <label class="inline"><%= check_box_tag 'issue[parent_issue_id]', 'none', (@issue_params[:parent_issue_id] == 'none'), :id => nil, :data => {:disables => '#issue_parent_issue_id'} %><%= l(:button_clear) %></label>
134 142 </p>
135 143 <%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @project)}')" %>
136 144 <% end %>
137 145
138 146 <% if @safe_attributes.include?('start_date') %>
139 147 <p>
140 148 <label for='issue_start_date'><%= l(:field_start_date) %></label>
141 149 <%= text_field_tag 'issue[start_date]', '', :value => @issue_params[:start_date], :size => 10 %><%= calendar_for('issue_start_date') %>
142 150 <label class="inline"><%= check_box_tag 'issue[start_date]', 'none', (@issue_params[:start_date] == 'none'), :id => nil, :data => {:disables => '#issue_start_date'} %><%= l(:button_clear) %></label>
143 151 </p>
144 152 <% end %>
145 153
146 154 <% if @safe_attributes.include?('due_date') %>
147 155 <p>
148 156 <label for='issue_due_date'><%= l(:field_due_date) %></label>
149 157 <%= text_field_tag 'issue[due_date]', '', :value => @issue_params[:due_date], :size => 10 %><%= calendar_for('issue_due_date') %>
150 158 <label class="inline"><%= check_box_tag 'issue[due_date]', 'none', (@issue_params[:due_date] == 'none'), :id => nil, :data => {:disables => '#issue_due_date'} %><%= l(:button_clear) %></label>
151 159 </p>
152 160 <% end %>
153 161
154 162 <% if @safe_attributes.include?('done_ratio') && Issue.use_field_for_done_ratio? %>
155 163 <p>
156 164 <label for='issue_done_ratio'><%= l(:field_done_ratio) %></label>
157 165 <%= select_tag 'issue[done_ratio]', options_for_select([[l(:label_no_change_option), '']] + (0..10).to_a.collect {|r| ["#{r*10} %", r*10] }, @issue_params[:done_ratio]) %>
158 166 </p>
159 167 <% end %>
160 168 </div>
161 169 </fieldset>
162 170
163 171 <fieldset>
164 172 <legend><%= l(:field_notes) %></legend>
165 173 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
166 174 <%= wikitoolbar_for 'notes' %>
167 175 </fieldset>
168 176 </div>
169 177
170 178 <p>
171 179 <% if @copy %>
172 180 <%= hidden_field_tag 'copy', '1' %>
173 181 <%= submit_tag l(:button_copy) %>
174 182 <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %>
175 183 <% elsif @target_project %>
176 184 <%= submit_tag l(:button_move) %>
177 185 <%= submit_tag l(:button_move_and_follow), :name => 'follow' %>
178 186 <% else %>
179 187 <%= submit_tag l(:button_submit) %>
180 188 <% end %>
181 189 </p>
182 190
183 191 <% end %>
184 192
185 193 <%= javascript_tag do %>
186 194 $(window).load(function(){
187 195 $(document).on('change', 'input[data-disables]', function(){
188 196 if ($(this).prop('checked')){
189 197 $($(this).data('disables')).attr('disabled', true).val('');
190 198 } else {
191 199 $($(this).data('disables')).attr('disabled', false);
192 200 }
193 201 });
194 202 });
195 203 $(document).ready(function(){
196 204 $('input[data-disables]').trigger('change');
197 205 });
198 206 <% end %>
@@ -1,53 +1,59
1 1 <%= title l(:label_issue_new) %>
2 2
3 3 <%= call_hook(:view_issues_new_top, {:issue => @issue}) %>
4 4
5 5 <%= labelled_form_for @issue, :url => project_issues_path(@project),
6 6 :html => {:id => 'issue-form', :multipart => true} do |f| %>
7 7 <%= error_messages_for 'issue' %>
8 8 <%= hidden_field_tag 'copy_from', params[:copy_from] if params[:copy_from] %>
9 9 <div class="box tabular">
10 10 <div id="all_attributes">
11 11 <%= render :partial => 'issues/form', :locals => {:f => f} %>
12 12 </div>
13 13
14 <% if @copy_from && Setting.link_copied_issue == 'ask' %>
15 <p>
16 <label for="link_copy"><%= l(:label_link_copied_issue) %></label>
17 <%= check_box_tag 'link_copy', '1', @link_copy %>
18 </p>
19 <% end %>
14 20 <% if @copy_from && @copy_from.attachments.any? %>
15 21 <p>
16 22 <label for="copy_attachments"><%= l(:label_copy_attachments) %></label>
17 23 <%= check_box_tag 'copy_attachments', '1', @copy_attachments %>
18 24 </p>
19 25 <% end %>
20 26 <% if @copy_from && !@copy_from.leaf? %>
21 27 <p>
22 28 <label for="copy_subtasks"><%= l(:label_copy_subtasks) %></label>
23 29 <%= check_box_tag 'copy_subtasks', '1', @copy_subtasks %>
24 30 </p>
25 31 <% end %>
26 32
27 33 <p id="attachments_form"><label><%= l(:label_attachment_plural) %></label><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
28 34
29 35 <% if @issue.safe_attribute? 'watcher_user_ids' -%>
30 36 <p id="watchers_form"><label><%= l(:label_issue_watchers) %></label>
31 37 <span id="watchers_inputs">
32 38 <%= watchers_checkboxes(@issue, @available_watchers) %>
33 39 </span>
34 40 <span class="search_for_watchers">
35 41 <%= link_to l(:label_search_for_watchers),
36 42 {:controller => 'watchers', :action => 'new', :project_id => @issue.project},
37 43 :remote => true,
38 44 :method => 'get' %>
39 45 </span>
40 46 </p>
41 47 <% end %>
42 48 </div>
43 49
44 50 <%= submit_tag l(:button_create) %>
45 51 <%= submit_tag l(:button_create_and_continue), :name => 'continue' %>
46 52 <%= preview_link preview_new_issue_path(:project_id => @project), 'issue-form' %>
47 53 <% end %>
48 54
49 55 <div id="preview" class="wiki"></div>
50 56
51 57 <% content_for :header_tags do %>
52 58 <%= robot_exclusion_tag %>
53 59 <% end %>
@@ -1,31 +1,33
1 1 <%= form_tag({:action => 'edit', :tab => 'issues'}) do %>
2 2
3 3 <div class="box tabular settings">
4 4 <p><%= setting_check_box :cross_project_issue_relations %></p>
5 5
6 <p><%= setting_select :link_copied_issue, link_copied_issue_options %></p>
7
6 8 <p><%= setting_select :cross_project_subtasks, cross_project_subtasks_options %></p>
7 9
8 10 <p><%= setting_check_box :issue_group_assignment %></p>
9 11
10 12 <p><%= setting_check_box :default_issue_start_date_to_creation_date %></p>
11 13
12 14 <p><%= setting_check_box :display_subprojects_issues %></p>
13 15
14 16 <p><%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %></p>
15 17
16 18 <p><%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %></p>
17 19
18 20 <p><%= setting_text_field :issues_export_limit, :size => 6 %></p>
19 21
20 22 <p><%= setting_text_field :gantt_items_limit, :size => 6 %></p>
21 23 </div>
22 24
23 25 <fieldset class="box">
24 26 <legend><%= l(:setting_issue_list_default_columns) %></legend>
25 27 <%= render_query_columns_selection(
26 28 IssueQuery.new(:column_names => Setting.issue_list_default_columns),
27 29 :name => 'settings[issue_list_default_columns]') %>
28 30 </fieldset>
29 31
30 32 <%= submit_tag l(:button_save) %>
31 33 <% end %>
@@ -1,1118 +1,1121
1 1 en:
2 2 # Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
3 3 direction: ltr
4 4 date:
5 5 formats:
6 6 # Use the strftime parameters for formats.
7 7 # When no format has been given, it uses default.
8 8 # You can provide other formats here if you like!
9 9 default: "%m/%d/%Y"
10 10 short: "%b %d"
11 11 long: "%B %d, %Y"
12 12
13 13 day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
14 14 abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
15 15
16 16 # Don't forget the nil at the beginning; there's no such thing as a 0th month
17 17 month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
18 18 abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
19 19 # Used in date_select and datime_select.
20 20 order:
21 21 - :year
22 22 - :month
23 23 - :day
24 24
25 25 time:
26 26 formats:
27 27 default: "%m/%d/%Y %I:%M %p"
28 28 time: "%I:%M %p"
29 29 short: "%d %b %H:%M"
30 30 long: "%B %d, %Y %H:%M"
31 31 am: "am"
32 32 pm: "pm"
33 33
34 34 datetime:
35 35 distance_in_words:
36 36 half_a_minute: "half a minute"
37 37 less_than_x_seconds:
38 38 one: "less than 1 second"
39 39 other: "less than %{count} seconds"
40 40 x_seconds:
41 41 one: "1 second"
42 42 other: "%{count} seconds"
43 43 less_than_x_minutes:
44 44 one: "less than a minute"
45 45 other: "less than %{count} minutes"
46 46 x_minutes:
47 47 one: "1 minute"
48 48 other: "%{count} minutes"
49 49 about_x_hours:
50 50 one: "about 1 hour"
51 51 other: "about %{count} hours"
52 52 x_hours:
53 53 one: "1 hour"
54 54 other: "%{count} hours"
55 55 x_days:
56 56 one: "1 day"
57 57 other: "%{count} days"
58 58 about_x_months:
59 59 one: "about 1 month"
60 60 other: "about %{count} months"
61 61 x_months:
62 62 one: "1 month"
63 63 other: "%{count} months"
64 64 about_x_years:
65 65 one: "about 1 year"
66 66 other: "about %{count} years"
67 67 over_x_years:
68 68 one: "over 1 year"
69 69 other: "over %{count} years"
70 70 almost_x_years:
71 71 one: "almost 1 year"
72 72 other: "almost %{count} years"
73 73
74 74 number:
75 75 format:
76 76 separator: "."
77 77 delimiter: ""
78 78 precision: 3
79 79
80 80 human:
81 81 format:
82 82 delimiter: ""
83 83 precision: 3
84 84 storage_units:
85 85 format: "%n %u"
86 86 units:
87 87 byte:
88 88 one: "Byte"
89 89 other: "Bytes"
90 90 kb: "KB"
91 91 mb: "MB"
92 92 gb: "GB"
93 93 tb: "TB"
94 94
95 95 # Used in array.to_sentence.
96 96 support:
97 97 array:
98 98 sentence_connector: "and"
99 99 skip_last_comma: false
100 100
101 101 activerecord:
102 102 errors:
103 103 template:
104 104 header:
105 105 one: "1 error prohibited this %{model} from being saved"
106 106 other: "%{count} errors prohibited this %{model} from being saved"
107 107 messages:
108 108 inclusion: "is not included in the list"
109 109 exclusion: "is reserved"
110 110 invalid: "is invalid"
111 111 confirmation: "doesn't match confirmation"
112 112 accepted: "must be accepted"
113 113 empty: "can't be empty"
114 114 blank: "can't be blank"
115 115 too_long: "is too long (maximum is %{count} characters)"
116 116 too_short: "is too short (minimum is %{count} characters)"
117 117 wrong_length: "is the wrong length (should be %{count} characters)"
118 118 taken: "has already been taken"
119 119 not_a_number: "is not a number"
120 120 not_a_date: "is not a valid date"
121 121 greater_than: "must be greater than %{count}"
122 122 greater_than_or_equal_to: "must be greater than or equal to %{count}"
123 123 equal_to: "must be equal to %{count}"
124 124 less_than: "must be less than %{count}"
125 125 less_than_or_equal_to: "must be less than or equal to %{count}"
126 126 odd: "must be odd"
127 127 even: "must be even"
128 128 greater_than_start_date: "must be greater than start date"
129 129 not_same_project: "doesn't belong to the same project"
130 130 circular_dependency: "This relation would create a circular dependency"
131 131 cant_link_an_issue_with_a_descendant: "An issue cannot be linked to one of its subtasks"
132 132 earlier_than_minimum_start_date: "cannot be earlier than %{date} because of preceding issues"
133 133
134 134 actionview_instancetag_blank_option: Please select
135 135
136 136 general_text_No: 'No'
137 137 general_text_Yes: 'Yes'
138 138 general_text_no: 'no'
139 139 general_text_yes: 'yes'
140 140 general_lang_name: 'English'
141 141 general_csv_separator: ','
142 142 general_csv_decimal_separator: '.'
143 143 general_csv_encoding: ISO-8859-1
144 144 general_pdf_fontname: freesans
145 145 general_first_day_of_week: '7'
146 146
147 147 notice_account_updated: Account was successfully updated.
148 148 notice_account_invalid_creditentials: Invalid user or password
149 149 notice_account_password_updated: Password was successfully updated.
150 150 notice_account_wrong_password: Wrong password
151 151 notice_account_register_done: Account was successfully created. An email containing the instructions to activate your account was sent to %{email}.
152 152 notice_account_unknown_email: Unknown user.
153 153 notice_account_not_activated_yet: You haven't activated your account yet. If you want to receive a new activation email, please <a href="%{url}">click this link</a>.
154 154 notice_account_locked: Your account is locked.
155 155 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
156 156 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
157 157 notice_account_activated: Your account has been activated. You can now log in.
158 158 notice_successful_create: Successful creation.
159 159 notice_successful_update: Successful update.
160 160 notice_successful_delete: Successful deletion.
161 161 notice_successful_connection: Successful connection.
162 162 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
163 163 notice_locking_conflict: Data has been updated by another user.
164 164 notice_not_authorized: You are not authorized to access this page.
165 165 notice_not_authorized_archived_project: The project you're trying to access has been archived.
166 166 notice_email_sent: "An email was sent to %{value}"
167 167 notice_email_error: "An error occurred while sending mail (%{value})"
168 168 notice_feeds_access_key_reseted: Your Atom access key was reset.
169 169 notice_api_access_key_reseted: Your API access key was reset.
170 170 notice_failed_to_save_issues: "Failed to save %{count} issue(s) on %{total} selected: %{ids}."
171 171 notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
172 172 notice_failed_to_save_members: "Failed to save member(s): %{errors}."
173 173 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
174 174 notice_account_pending: "Your account was created and is now pending administrator approval."
175 175 notice_default_data_loaded: Default configuration successfully loaded.
176 176 notice_unable_delete_version: Unable to delete version.
177 177 notice_unable_delete_time_entry: Unable to delete time log entry.
178 178 notice_issue_done_ratios_updated: Issue done ratios updated.
179 179 notice_gantt_chart_truncated: "The chart was truncated because it exceeds the maximum number of items that can be displayed (%{max})"
180 180 notice_issue_successful_create: "Issue %{id} created."
181 181 notice_issue_update_conflict: "The issue has been updated by an other user while you were editing it."
182 182 notice_account_deleted: "Your account has been permanently deleted."
183 183 notice_user_successful_create: "User %{id} created."
184 184 notice_new_password_must_be_different: The new password must be different from the current password
185 185
186 186 error_can_t_load_default_data: "Default configuration could not be loaded: %{value}"
187 187 error_scm_not_found: "The entry or revision was not found in the repository."
188 188 error_scm_command_failed: "An error occurred when trying to access the repository: %{value}"
189 189 error_scm_annotate: "The entry does not exist or cannot be annotated."
190 190 error_scm_annotate_big_text_file: "The entry cannot be annotated, as it exceeds the maximum text file size."
191 191 error_issue_not_found_in_project: 'The issue was not found or does not belong to this project'
192 192 error_no_tracker_in_project: 'No tracker is associated to this project. Please check the Project settings.'
193 193 error_no_default_issue_status: 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
194 194 error_can_not_delete_custom_field: Unable to delete custom field
195 195 error_can_not_delete_tracker: "This tracker contains issues and cannot be deleted."
196 196 error_can_not_remove_role: "This role is in use and cannot be deleted."
197 197 error_can_not_reopen_issue_on_closed_version: 'An issue assigned to a closed version cannot be reopened'
198 198 error_can_not_archive_project: This project cannot be archived
199 199 error_issue_done_ratios_not_updated: "Issue done ratios not updated."
200 200 error_workflow_copy_source: 'Please select a source tracker or role'
201 201 error_workflow_copy_target: 'Please select target tracker(s) and role(s)'
202 202 error_unable_delete_issue_status: 'Unable to delete issue status'
203 203 error_unable_to_connect: "Unable to connect (%{value})"
204 204 error_attachment_too_big: "This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})"
205 205 error_session_expired: "Your session has expired. Please login again."
206 206 warning_attachments_not_saved: "%{count} file(s) could not be saved."
207 207
208 208 mail_subject_lost_password: "Your %{value} password"
209 209 mail_body_lost_password: 'To change your password, click on the following link:'
210 210 mail_subject_register: "Your %{value} account activation"
211 211 mail_body_register: 'To activate your account, click on the following link:'
212 212 mail_body_account_information_external: "You can use your %{value} account to log in."
213 213 mail_body_account_information: Your account information
214 214 mail_subject_account_activation_request: "%{value} account activation request"
215 215 mail_body_account_activation_request: "A new user (%{value}) has registered. The account is pending your approval:"
216 216 mail_subject_reminder: "%{count} issue(s) due in the next %{days} days"
217 217 mail_body_reminder: "%{count} issue(s) that are assigned to you are due in the next %{days} days:"
218 218 mail_subject_wiki_content_added: "'%{id}' wiki page has been added"
219 219 mail_body_wiki_content_added: "The '%{id}' wiki page has been added by %{author}."
220 220 mail_subject_wiki_content_updated: "'%{id}' wiki page has been updated"
221 221 mail_body_wiki_content_updated: "The '%{id}' wiki page has been updated by %{author}."
222 222
223 223 field_name: Name
224 224 field_description: Description
225 225 field_summary: Summary
226 226 field_is_required: Required
227 227 field_firstname: First name
228 228 field_lastname: Last name
229 229 field_mail: Email
230 230 field_filename: File
231 231 field_filesize: Size
232 232 field_downloads: Downloads
233 233 field_author: Author
234 234 field_created_on: Created
235 235 field_updated_on: Updated
236 236 field_closed_on: Closed
237 237 field_field_format: Format
238 238 field_is_for_all: For all projects
239 239 field_possible_values: Possible values
240 240 field_regexp: Regular expression
241 241 field_min_length: Minimum length
242 242 field_max_length: Maximum length
243 243 field_value: Value
244 244 field_category: Category
245 245 field_title: Title
246 246 field_project: Project
247 247 field_issue: Issue
248 248 field_status: Status
249 249 field_notes: Notes
250 250 field_is_closed: Issue closed
251 251 field_is_default: Default value
252 252 field_tracker: Tracker
253 253 field_subject: Subject
254 254 field_due_date: Due date
255 255 field_assigned_to: Assignee
256 256 field_priority: Priority
257 257 field_fixed_version: Target version
258 258 field_user: User
259 259 field_principal: Principal
260 260 field_role: Role
261 261 field_homepage: Homepage
262 262 field_is_public: Public
263 263 field_parent: Subproject of
264 264 field_is_in_roadmap: Issues displayed in roadmap
265 265 field_login: Login
266 266 field_mail_notification: Email notifications
267 267 field_admin: Administrator
268 268 field_last_login_on: Last connection
269 269 field_language: Language
270 270 field_effective_date: Date
271 271 field_password: Password
272 272 field_new_password: New password
273 273 field_password_confirmation: Confirmation
274 274 field_version: Version
275 275 field_type: Type
276 276 field_host: Host
277 277 field_port: Port
278 278 field_account: Account
279 279 field_base_dn: Base DN
280 280 field_attr_login: Login attribute
281 281 field_attr_firstname: Firstname attribute
282 282 field_attr_lastname: Lastname attribute
283 283 field_attr_mail: Email attribute
284 284 field_onthefly: On-the-fly user creation
285 285 field_start_date: Start date
286 286 field_done_ratio: "% Done"
287 287 field_auth_source: Authentication mode
288 288 field_hide_mail: Hide my email address
289 289 field_comments: Comment
290 290 field_url: URL
291 291 field_start_page: Start page
292 292 field_subproject: Subproject
293 293 field_hours: Hours
294 294 field_activity: Activity
295 295 field_spent_on: Date
296 296 field_identifier: Identifier
297 297 field_is_filter: Used as a filter
298 298 field_issue_to: Related issue
299 299 field_delay: Delay
300 300 field_assignable: Issues can be assigned to this role
301 301 field_redirect_existing_links: Redirect existing links
302 302 field_estimated_hours: Estimated time
303 303 field_column_names: Columns
304 304 field_time_entries: Log time
305 305 field_time_zone: Time zone
306 306 field_searchable: Searchable
307 307 field_default_value: Default value
308 308 field_comments_sorting: Display comments
309 309 field_parent_title: Parent page
310 310 field_editable: Editable
311 311 field_watcher: Watcher
312 312 field_identity_url: OpenID URL
313 313 field_content: Content
314 314 field_group_by: Group results by
315 315 field_sharing: Sharing
316 316 field_parent_issue: Parent task
317 317 field_member_of_group: "Assignee's group"
318 318 field_assigned_to_role: "Assignee's role"
319 319 field_text: Text field
320 320 field_visible: Visible
321 321 field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
322 322 field_issues_visibility: Issues visibility
323 323 field_is_private: Private
324 324 field_commit_logs_encoding: Commit messages encoding
325 325 field_scm_path_encoding: Path encoding
326 326 field_path_to_repository: Path to repository
327 327 field_root_directory: Root directory
328 328 field_cvsroot: CVSROOT
329 329 field_cvs_module: Module
330 330 field_repository_is_default: Main repository
331 331 field_multiple: Multiple values
332 332 field_auth_source_ldap_filter: LDAP filter
333 333 field_core_fields: Standard fields
334 334 field_timeout: "Timeout (in seconds)"
335 335 field_board_parent: Parent forum
336 336 field_private_notes: Private notes
337 337 field_inherit_members: Inherit members
338 338 field_generate_password: Generate password
339 339 field_must_change_passwd: Must change password at next logon
340 340 field_default_status: Default status
341 341 field_users_visibility: Users visibility
342 342
343 343 setting_app_title: Application title
344 344 setting_app_subtitle: Application subtitle
345 345 setting_welcome_text: Welcome text
346 346 setting_default_language: Default language
347 347 setting_login_required: Authentication required
348 348 setting_self_registration: Self-registration
349 349 setting_attachment_max_size: Maximum attachment size
350 350 setting_issues_export_limit: Issues export limit
351 351 setting_mail_from: Emission email address
352 352 setting_bcc_recipients: Blind carbon copy recipients (bcc)
353 353 setting_plain_text_mail: Plain text mail (no HTML)
354 354 setting_host_name: Host name and path
355 355 setting_text_formatting: Text formatting
356 356 setting_wiki_compression: Wiki history compression
357 357 setting_feeds_limit: Maximum number of items in Atom feeds
358 358 setting_default_projects_public: New projects are public by default
359 359 setting_autofetch_changesets: Fetch commits automatically
360 360 setting_sys_api_enabled: Enable WS for repository management
361 361 setting_commit_ref_keywords: Referencing keywords
362 362 setting_commit_fix_keywords: Fixing keywords
363 363 setting_autologin: Autologin
364 364 setting_date_format: Date format
365 365 setting_time_format: Time format
366 366 setting_cross_project_issue_relations: Allow cross-project issue relations
367 367 setting_cross_project_subtasks: Allow cross-project subtasks
368 368 setting_issue_list_default_columns: Default columns displayed on the issue list
369 369 setting_repositories_encodings: Attachments and repositories encodings
370 370 setting_emails_header: Email header
371 371 setting_emails_footer: Email footer
372 372 setting_protocol: Protocol
373 373 setting_per_page_options: Objects per page options
374 374 setting_user_format: Users display format
375 375 setting_activity_days_default: Days displayed on project activity
376 376 setting_display_subprojects_issues: Display subprojects issues on main projects by default
377 377 setting_enabled_scm: Enabled SCM
378 378 setting_mail_handler_body_delimiters: "Truncate emails after one of these lines"
379 379 setting_mail_handler_api_enabled: Enable WS for incoming emails
380 380 setting_mail_handler_api_key: API key
381 381 setting_sequential_project_identifiers: Generate sequential project identifiers
382 382 setting_gravatar_enabled: Use Gravatar user icons
383 383 setting_gravatar_default: Default Gravatar image
384 384 setting_diff_max_lines_displayed: Maximum number of diff lines displayed
385 385 setting_file_max_size_displayed: Maximum size of text files displayed inline
386 386 setting_repository_log_display_limit: Maximum number of revisions displayed on file log
387 387 setting_openid: Allow OpenID login and registration
388 388 setting_password_min_length: Minimum password length
389 389 setting_new_project_user_role_id: Role given to a non-admin user who creates a project
390 390 setting_default_projects_modules: Default enabled modules for new projects
391 391 setting_issue_done_ratio: Calculate the issue done ratio with
392 392 setting_issue_done_ratio_issue_field: Use the issue field
393 393 setting_issue_done_ratio_issue_status: Use the issue status
394 394 setting_start_of_week: Start calendars on
395 395 setting_rest_api_enabled: Enable REST web service
396 396 setting_cache_formatted_text: Cache formatted text
397 397 setting_default_notification_option: Default notification option
398 398 setting_commit_logtime_enabled: Enable time logging
399 399 setting_commit_logtime_activity_id: Activity for logged time
400 400 setting_gantt_items_limit: Maximum number of items displayed on the gantt chart
401 401 setting_issue_group_assignment: Allow issue assignment to groups
402 402 setting_default_issue_start_date_to_creation_date: Use current date as start date for new issues
403 403 setting_commit_cross_project_ref: Allow issues of all the other projects to be referenced and fixed
404 404 setting_unsubscribe: Allow users to delete their own account
405 405 setting_session_lifetime: Session maximum lifetime
406 406 setting_session_timeout: Session inactivity timeout
407 407 setting_thumbnails_enabled: Display attachment thumbnails
408 408 setting_thumbnails_size: Thumbnails size (in pixels)
409 409 setting_non_working_week_days: Non-working days
410 410 setting_jsonp_enabled: Enable JSONP support
411 411 setting_default_projects_tracker_ids: Default trackers for new projects
412 412 setting_mail_handler_excluded_filenames: Exclude attachments by name
413 413 setting_force_default_language_for_anonymous: Force default language for anonymous users
414 414 setting_force_default_language_for_loggedin: Force default language for logged-in users
415 setting_link_copied_issue: Link issues on copy
415 416
416 417 permission_add_project: Create project
417 418 permission_add_subprojects: Create subprojects
418 419 permission_edit_project: Edit project
419 420 permission_close_project: Close / reopen the project
420 421 permission_select_project_modules: Select project modules
421 422 permission_manage_members: Manage members
422 423 permission_manage_project_activities: Manage project activities
423 424 permission_manage_versions: Manage versions
424 425 permission_manage_categories: Manage issue categories
425 426 permission_view_issues: View Issues
426 427 permission_add_issues: Add issues
427 428 permission_edit_issues: Edit issues
428 429 permission_manage_issue_relations: Manage issue relations
429 430 permission_set_issues_private: Set issues public or private
430 431 permission_set_own_issues_private: Set own issues public or private
431 432 permission_add_issue_notes: Add notes
432 433 permission_edit_issue_notes: Edit notes
433 434 permission_edit_own_issue_notes: Edit own notes
434 435 permission_view_private_notes: View private notes
435 436 permission_set_notes_private: Set notes as private
436 437 permission_move_issues: Move issues
437 438 permission_delete_issues: Delete issues
438 439 permission_manage_public_queries: Manage public queries
439 440 permission_save_queries: Save queries
440 441 permission_view_gantt: View gantt chart
441 442 permission_view_calendar: View calendar
442 443 permission_view_issue_watchers: View watchers list
443 444 permission_add_issue_watchers: Add watchers
444 445 permission_delete_issue_watchers: Delete watchers
445 446 permission_log_time: Log spent time
446 447 permission_view_time_entries: View spent time
447 448 permission_edit_time_entries: Edit time logs
448 449 permission_edit_own_time_entries: Edit own time logs
449 450 permission_manage_news: Manage news
450 451 permission_comment_news: Comment news
451 452 permission_view_documents: View documents
452 453 permission_add_documents: Add documents
453 454 permission_edit_documents: Edit documents
454 455 permission_delete_documents: Delete documents
455 456 permission_manage_files: Manage files
456 457 permission_view_files: View files
457 458 permission_manage_wiki: Manage wiki
458 459 permission_rename_wiki_pages: Rename wiki pages
459 460 permission_delete_wiki_pages: Delete wiki pages
460 461 permission_view_wiki_pages: View wiki
461 462 permission_view_wiki_edits: View wiki history
462 463 permission_edit_wiki_pages: Edit wiki pages
463 464 permission_delete_wiki_pages_attachments: Delete attachments
464 465 permission_protect_wiki_pages: Protect wiki pages
465 466 permission_manage_repository: Manage repository
466 467 permission_browse_repository: Browse repository
467 468 permission_view_changesets: View changesets
468 469 permission_commit_access: Commit access
469 470 permission_manage_boards: Manage forums
470 471 permission_view_messages: View messages
471 472 permission_add_messages: Post messages
472 473 permission_edit_messages: Edit messages
473 474 permission_edit_own_messages: Edit own messages
474 475 permission_delete_messages: Delete messages
475 476 permission_delete_own_messages: Delete own messages
476 477 permission_export_wiki_pages: Export wiki pages
477 478 permission_manage_subtasks: Manage subtasks
478 479 permission_manage_related_issues: Manage related issues
479 480
480 481 project_module_issue_tracking: Issue tracking
481 482 project_module_time_tracking: Time tracking
482 483 project_module_news: News
483 484 project_module_documents: Documents
484 485 project_module_files: Files
485 486 project_module_wiki: Wiki
486 487 project_module_repository: Repository
487 488 project_module_boards: Forums
488 489 project_module_calendar: Calendar
489 490 project_module_gantt: Gantt
490 491
491 492 label_user: User
492 493 label_user_plural: Users
493 494 label_user_new: New user
494 495 label_user_anonymous: Anonymous
495 496 label_project: Project
496 497 label_project_new: New project
497 498 label_project_plural: Projects
498 499 label_x_projects:
499 500 zero: no projects
500 501 one: 1 project
501 502 other: "%{count} projects"
502 503 label_project_all: All Projects
503 504 label_project_latest: Latest projects
504 505 label_issue: Issue
505 506 label_issue_new: New issue
506 507 label_issue_plural: Issues
507 508 label_issue_view_all: View all issues
508 509 label_issues_by: "Issues by %{value}"
509 510 label_issue_added: Issue added
510 511 label_issue_updated: Issue updated
511 512 label_issue_note_added: Note added
512 513 label_issue_status_updated: Status updated
513 514 label_issue_assigned_to_updated: Assignee updated
514 515 label_issue_priority_updated: Priority updated
515 516 label_document: Document
516 517 label_document_new: New document
517 518 label_document_plural: Documents
518 519 label_document_added: Document added
519 520 label_role: Role
520 521 label_role_plural: Roles
521 522 label_role_new: New role
522 523 label_role_and_permissions: Roles and permissions
523 524 label_role_anonymous: Anonymous
524 525 label_role_non_member: Non member
525 526 label_member: Member
526 527 label_member_new: New member
527 528 label_member_plural: Members
528 529 label_tracker: Tracker
529 530 label_tracker_plural: Trackers
530 531 label_tracker_new: New tracker
531 532 label_workflow: Workflow
532 533 label_issue_status: Issue status
533 534 label_issue_status_plural: Issue statuses
534 535 label_issue_status_new: New status
535 536 label_issue_category: Issue category
536 537 label_issue_category_plural: Issue categories
537 538 label_issue_category_new: New category
538 539 label_custom_field: Custom field
539 540 label_custom_field_plural: Custom fields
540 541 label_custom_field_new: New custom field
541 542 label_enumerations: Enumerations
542 543 label_enumeration_new: New value
543 544 label_information: Information
544 545 label_information_plural: Information
545 546 label_please_login: Please log in
546 547 label_register: Register
547 548 label_login_with_open_id_option: or login with OpenID
548 549 label_password_lost: Lost password
549 550 label_home: Home
550 551 label_my_page: My page
551 552 label_my_account: My account
552 553 label_my_projects: My projects
553 554 label_my_page_block: My page block
554 555 label_administration: Administration
555 556 label_login: Sign in
556 557 label_logout: Sign out
557 558 label_help: Help
558 559 label_reported_issues: Reported issues
559 560 label_assigned_to_me_issues: Issues assigned to me
560 561 label_last_login: Last connection
561 562 label_registered_on: Registered on
562 563 label_activity: Activity
563 564 label_overall_activity: Overall activity
564 565 label_user_activity: "%{value}'s activity"
565 566 label_new: New
566 567 label_logged_as: Logged in as
567 568 label_environment: Environment
568 569 label_authentication: Authentication
569 570 label_auth_source: Authentication mode
570 571 label_auth_source_new: New authentication mode
571 572 label_auth_source_plural: Authentication modes
572 573 label_subproject_plural: Subprojects
573 574 label_subproject_new: New subproject
574 575 label_and_its_subprojects: "%{value} and its subprojects"
575 576 label_min_max_length: Min - Max length
576 577 label_list: List
577 578 label_date: Date
578 579 label_integer: Integer
579 580 label_float: Float
580 581 label_boolean: Boolean
581 582 label_string: Text
582 583 label_text: Long text
583 584 label_attribute: Attribute
584 585 label_attribute_plural: Attributes
585 586 label_no_data: No data to display
586 587 label_change_status: Change status
587 588 label_history: History
588 589 label_attachment: File
589 590 label_attachment_new: New file
590 591 label_attachment_delete: Delete file
591 592 label_attachment_plural: Files
592 593 label_file_added: File added
593 594 label_report: Report
594 595 label_report_plural: Reports
595 596 label_news: News
596 597 label_news_new: Add news
597 598 label_news_plural: News
598 599 label_news_latest: Latest news
599 600 label_news_view_all: View all news
600 601 label_news_added: News added
601 602 label_news_comment_added: Comment added to a news
602 603 label_settings: Settings
603 604 label_overview: Overview
604 605 label_version: Version
605 606 label_version_new: New version
606 607 label_version_plural: Versions
607 608 label_close_versions: Close completed versions
608 609 label_confirmation: Confirmation
609 610 label_export_to: 'Also available in:'
610 611 label_read: Read...
611 612 label_public_projects: Public projects
612 613 label_open_issues: open
613 614 label_open_issues_plural: open
614 615 label_closed_issues: closed
615 616 label_closed_issues_plural: closed
616 617 label_x_open_issues_abbr_on_total:
617 618 zero: 0 open / %{total}
618 619 one: 1 open / %{total}
619 620 other: "%{count} open / %{total}"
620 621 label_x_open_issues_abbr:
621 622 zero: 0 open
622 623 one: 1 open
623 624 other: "%{count} open"
624 625 label_x_closed_issues_abbr:
625 626 zero: 0 closed
626 627 one: 1 closed
627 628 other: "%{count} closed"
628 629 label_x_issues:
629 630 zero: 0 issues
630 631 one: 1 issue
631 632 other: "%{count} issues"
632 633 label_total: Total
633 634 label_total_time: Total time
634 635 label_permissions: Permissions
635 636 label_current_status: Current status
636 637 label_new_statuses_allowed: New statuses allowed
637 638 label_all: all
638 639 label_any: any
639 640 label_none: none
640 641 label_nobody: nobody
641 642 label_next: Next
642 643 label_previous: Previous
643 644 label_used_by: Used by
644 645 label_details: Details
645 646 label_add_note: Add a note
646 647 label_per_page: Per page
647 648 label_calendar: Calendar
648 649 label_months_from: months from
649 650 label_gantt: Gantt
650 651 label_internal: Internal
651 652 label_last_changes: "last %{count} changes"
652 653 label_change_view_all: View all changes
653 654 label_personalize_page: Personalize this page
654 655 label_comment: Comment
655 656 label_comment_plural: Comments
656 657 label_x_comments:
657 658 zero: no comments
658 659 one: 1 comment
659 660 other: "%{count} comments"
660 661 label_comment_add: Add a comment
661 662 label_comment_added: Comment added
662 663 label_comment_delete: Delete comments
663 664 label_query: Custom query
664 665 label_query_plural: Custom queries
665 666 label_query_new: New query
666 667 label_my_queries: My custom queries
667 668 label_filter_add: Add filter
668 669 label_filter_plural: Filters
669 670 label_equals: is
670 671 label_not_equals: is not
671 672 label_in_less_than: in less than
672 673 label_in_more_than: in more than
673 674 label_in_the_next_days: in the next
674 675 label_in_the_past_days: in the past
675 676 label_greater_or_equal: '>='
676 677 label_less_or_equal: '<='
677 678 label_between: between
678 679 label_in: in
679 680 label_today: today
680 681 label_all_time: all time
681 682 label_yesterday: yesterday
682 683 label_this_week: this week
683 684 label_last_week: last week
684 685 label_last_n_weeks: "last %{count} weeks"
685 686 label_last_n_days: "last %{count} days"
686 687 label_this_month: this month
687 688 label_last_month: last month
688 689 label_this_year: this year
689 690 label_date_range: Date range
690 691 label_less_than_ago: less than days ago
691 692 label_more_than_ago: more than days ago
692 693 label_ago: days ago
693 694 label_contains: contains
694 695 label_not_contains: doesn't contain
695 696 label_any_issues_in_project: any issues in project
696 697 label_any_issues_not_in_project: any issues not in project
697 698 label_no_issues_in_project: no issues in project
698 699 label_day_plural: days
699 700 label_repository: Repository
700 701 label_repository_new: New repository
701 702 label_repository_plural: Repositories
702 703 label_browse: Browse
703 704 label_branch: Branch
704 705 label_tag: Tag
705 706 label_revision: Revision
706 707 label_revision_plural: Revisions
707 708 label_revision_id: "Revision %{value}"
708 709 label_associated_revisions: Associated revisions
709 710 label_added: added
710 711 label_modified: modified
711 712 label_copied: copied
712 713 label_renamed: renamed
713 714 label_deleted: deleted
714 715 label_latest_revision: Latest revision
715 716 label_latest_revision_plural: Latest revisions
716 717 label_view_revisions: View revisions
717 718 label_view_all_revisions: View all revisions
718 719 label_max_size: Maximum size
719 720 label_sort_highest: Move to top
720 721 label_sort_higher: Move up
721 722 label_sort_lower: Move down
722 723 label_sort_lowest: Move to bottom
723 724 label_roadmap: Roadmap
724 725 label_roadmap_due_in: "Due in %{value}"
725 726 label_roadmap_overdue: "%{value} late"
726 727 label_roadmap_no_issues: No issues for this version
727 728 label_search: Search
728 729 label_result_plural: Results
729 730 label_all_words: All words
730 731 label_wiki: Wiki
731 732 label_wiki_edit: Wiki edit
732 733 label_wiki_edit_plural: Wiki edits
733 734 label_wiki_page: Wiki page
734 735 label_wiki_page_plural: Wiki pages
735 736 label_index_by_title: Index by title
736 737 label_index_by_date: Index by date
737 738 label_current_version: Current version
738 739 label_preview: Preview
739 740 label_feed_plural: Feeds
740 741 label_changes_details: Details of all changes
741 742 label_issue_tracking: Issue tracking
742 743 label_spent_time: Spent time
743 744 label_overall_spent_time: Overall spent time
744 745 label_f_hour: "%{value} hour"
745 746 label_f_hour_plural: "%{value} hours"
746 747 label_time_tracking: Time tracking
747 748 label_change_plural: Changes
748 749 label_statistics: Statistics
749 750 label_commits_per_month: Commits per month
750 751 label_commits_per_author: Commits per author
751 752 label_diff: diff
752 753 label_view_diff: View differences
753 754 label_diff_inline: inline
754 755 label_diff_side_by_side: side by side
755 756 label_options: Options
756 757 label_copy_workflow_from: Copy workflow from
757 758 label_permissions_report: Permissions report
758 759 label_watched_issues: Watched issues
759 760 label_related_issues: Related issues
760 761 label_applied_status: Applied status
761 762 label_loading: Loading...
762 763 label_relation_new: New relation
763 764 label_relation_delete: Delete relation
764 765 label_relates_to: Related to
765 766 label_duplicates: Duplicates
766 767 label_duplicated_by: Duplicated by
767 768 label_blocks: Blocks
768 769 label_blocked_by: Blocked by
769 770 label_precedes: Precedes
770 771 label_follows: Follows
771 772 label_copied_to: Copied to
772 773 label_copied_from: Copied from
773 774 label_end_to_start: end to start
774 775 label_end_to_end: end to end
775 776 label_start_to_start: start to start
776 777 label_start_to_end: start to end
777 778 label_stay_logged_in: Stay logged in
778 779 label_disabled: disabled
779 780 label_show_completed_versions: Show completed versions
780 781 label_me: me
781 782 label_board: Forum
782 783 label_board_new: New forum
783 784 label_board_plural: Forums
784 785 label_board_locked: Locked
785 786 label_board_sticky: Sticky
786 787 label_topic_plural: Topics
787 788 label_message_plural: Messages
788 789 label_message_last: Last message
789 790 label_message_new: New message
790 791 label_message_posted: Message added
791 792 label_reply_plural: Replies
792 793 label_send_information: Send account information to the user
793 794 label_year: Year
794 795 label_month: Month
795 796 label_week: Week
796 797 label_date_from: From
797 798 label_date_to: To
798 799 label_language_based: Based on user's language
799 800 label_sort_by: "Sort by %{value}"
800 801 label_send_test_email: Send a test email
801 802 label_feeds_access_key: Atom access key
802 803 label_missing_feeds_access_key: Missing a Atom access key
803 804 label_feeds_access_key_created_on: "Atom access key created %{value} ago"
804 805 label_module_plural: Modules
805 806 label_added_time_by: "Added by %{author} %{age} ago"
806 807 label_updated_time_by: "Updated by %{author} %{age} ago"
807 808 label_updated_time: "Updated %{value} ago"
808 809 label_jump_to_a_project: Jump to a project...
809 810 label_file_plural: Files
810 811 label_changeset_plural: Changesets
811 812 label_default_columns: Default columns
812 813 label_no_change_option: (No change)
813 814 label_bulk_edit_selected_issues: Bulk edit selected issues
814 815 label_bulk_edit_selected_time_entries: Bulk edit selected time entries
815 816 label_theme: Theme
816 817 label_default: Default
817 818 label_search_titles_only: Search titles only
818 819 label_user_mail_option_all: "For any event on all my projects"
819 820 label_user_mail_option_selected: "For any event on the selected projects only..."
820 821 label_user_mail_option_none: "No events"
821 822 label_user_mail_option_only_my_events: "Only for things I watch or I'm involved in"
822 823 label_user_mail_option_only_assigned: "Only for things I am assigned to"
823 824 label_user_mail_option_only_owner: "Only for things I am the owner of"
824 825 label_user_mail_no_self_notified: "I don't want to be notified of changes that I make myself"
825 826 label_registration_activation_by_email: account activation by email
826 827 label_registration_manual_activation: manual account activation
827 828 label_registration_automatic_activation: automatic account activation
828 829 label_display_per_page: "Per page: %{value}"
829 830 label_age: Age
830 831 label_change_properties: Change properties
831 832 label_general: General
832 833 label_more: More
833 834 label_scm: SCM
834 835 label_plugins: Plugins
835 836 label_ldap_authentication: LDAP authentication
836 837 label_downloads_abbr: D/L
837 838 label_optional_description: Optional description
838 839 label_add_another_file: Add another file
839 840 label_preferences: Preferences
840 841 label_chronological_order: In chronological order
841 842 label_reverse_chronological_order: In reverse chronological order
842 843 label_planning: Planning
843 844 label_incoming_emails: Incoming emails
844 845 label_generate_key: Generate a key
845 846 label_issue_watchers: Watchers
846 847 label_example: Example
847 848 label_display: Display
848 849 label_sort: Sort
849 850 label_ascending: Ascending
850 851 label_descending: Descending
851 852 label_date_from_to: From %{start} to %{end}
852 853 label_wiki_content_added: Wiki page added
853 854 label_wiki_content_updated: Wiki page updated
854 855 label_group: Group
855 856 label_group_plural: Groups
856 857 label_group_new: New group
857 858 label_group_anonymous: Anonymous users
858 859 label_group_non_member: Non member users
859 860 label_time_entry_plural: Spent time
860 861 label_version_sharing_none: Not shared
861 862 label_version_sharing_descendants: With subprojects
862 863 label_version_sharing_hierarchy: With project hierarchy
863 864 label_version_sharing_tree: With project tree
864 865 label_version_sharing_system: With all projects
865 866 label_update_issue_done_ratios: Update issue done ratios
866 867 label_copy_source: Source
867 868 label_copy_target: Target
868 869 label_copy_same_as_target: Same as target
869 870 label_display_used_statuses_only: Only display statuses that are used by this tracker
870 871 label_api_access_key: API access key
871 872 label_missing_api_access_key: Missing an API access key
872 873 label_api_access_key_created_on: "API access key created %{value} ago"
873 874 label_profile: Profile
874 875 label_subtask_plural: Subtasks
875 876 label_project_copy_notifications: Send email notifications during the project copy
876 877 label_principal_search: "Search for user or group:"
877 878 label_user_search: "Search for user:"
878 879 label_additional_workflow_transitions_for_author: Additional transitions allowed when the user is the author
879 880 label_additional_workflow_transitions_for_assignee: Additional transitions allowed when the user is the assignee
880 881 label_issues_visibility_all: All issues
881 882 label_issues_visibility_public: All non private issues
882 883 label_issues_visibility_own: Issues created by or assigned to the user
883 884 label_git_report_last_commit: Report last commit for files and directories
884 885 label_parent_revision: Parent
885 886 label_child_revision: Child
886 887 label_export_options: "%{export_format} export options"
887 888 label_copy_attachments: Copy attachments
888 889 label_copy_subtasks: Copy subtasks
889 890 label_item_position: "%{position} of %{count}"
890 891 label_completed_versions: Completed versions
891 892 label_search_for_watchers: Search for watchers to add
892 893 label_session_expiration: Session expiration
893 894 label_show_closed_projects: View closed projects
894 895 label_status_transitions: Status transitions
895 896 label_fields_permissions: Fields permissions
896 897 label_readonly: Read-only
897 898 label_required: Required
898 899 label_hidden: Hidden
899 900 label_attribute_of_project: "Project's %{name}"
900 901 label_attribute_of_issue: "Issue's %{name}"
901 902 label_attribute_of_author: "Author's %{name}"
902 903 label_attribute_of_assigned_to: "Assignee's %{name}"
903 904 label_attribute_of_user: "User's %{name}"
904 905 label_attribute_of_fixed_version: "Target version's %{name}"
905 906 label_cross_project_descendants: With subprojects
906 907 label_cross_project_tree: With project tree
907 908 label_cross_project_hierarchy: With project hierarchy
908 909 label_cross_project_system: With all projects
909 910 label_gantt_progress_line: Progress line
910 911 label_visibility_private: to me only
911 912 label_visibility_roles: to these roles only
912 913 label_visibility_public: to any users
913 914 label_link: Link
914 915 label_only: only
915 916 label_drop_down_list: drop-down list
916 917 label_checkboxes: checkboxes
917 918 label_radio_buttons: radio buttons
918 919 label_link_values_to: Link values to URL
919 920 label_custom_field_select_type: Select the type of object to which the custom field is to be attached
920 921 label_check_for_updates: Check for updates
921 922 label_latest_compatible_version: Latest compatible version
922 923 label_unknown_plugin: Unknown plugin
923 924 label_add_projects: Add projects
924 925 label_users_visibility_all: All active users
925 926 label_users_visibility_members_of_visible_projects: Members of visible projects
926 927 label_edit_attachments: Edit attached files
928 label_link_copied_issue: Link copied issue
929 label_ask: Ask
927 930
928 931 button_login: Login
929 932 button_submit: Submit
930 933 button_save: Save
931 934 button_check_all: Check all
932 935 button_uncheck_all: Uncheck all
933 936 button_collapse_all: Collapse all
934 937 button_expand_all: Expand all
935 938 button_delete: Delete
936 939 button_create: Create
937 940 button_create_and_continue: Create and continue
938 941 button_test: Test
939 942 button_edit: Edit
940 943 button_edit_associated_wikipage: "Edit associated Wiki page: %{page_title}"
941 944 button_add: Add
942 945 button_change: Change
943 946 button_apply: Apply
944 947 button_clear: Clear
945 948 button_lock: Lock
946 949 button_unlock: Unlock
947 950 button_download: Download
948 951 button_list: List
949 952 button_view: View
950 953 button_move: Move
951 954 button_move_and_follow: Move and follow
952 955 button_back: Back
953 956 button_cancel: Cancel
954 957 button_activate: Activate
955 958 button_sort: Sort
956 959 button_log_time: Log time
957 960 button_rollback: Rollback to this version
958 961 button_watch: Watch
959 962 button_unwatch: Unwatch
960 963 button_reply: Reply
961 964 button_archive: Archive
962 965 button_unarchive: Unarchive
963 966 button_reset: Reset
964 967 button_rename: Rename
965 968 button_change_password: Change password
966 969 button_copy: Copy
967 970 button_copy_and_follow: Copy and follow
968 971 button_annotate: Annotate
969 972 button_update: Update
970 973 button_configure: Configure
971 974 button_quote: Quote
972 975 button_duplicate: Duplicate
973 976 button_show: Show
974 977 button_hide: Hide
975 978 button_edit_section: Edit this section
976 979 button_export: Export
977 980 button_delete_my_account: Delete my account
978 981 button_close: Close
979 982 button_reopen: Reopen
980 983
981 984 status_active: active
982 985 status_registered: registered
983 986 status_locked: locked
984 987
985 988 project_status_active: active
986 989 project_status_closed: closed
987 990 project_status_archived: archived
988 991
989 992 version_status_open: open
990 993 version_status_locked: locked
991 994 version_status_closed: closed
992 995
993 996 field_active: Active
994 997
995 998 text_select_mail_notifications: Select actions for which email notifications should be sent.
996 999 text_regexp_info: eg. ^[A-Z0-9]+$
997 1000 text_min_max_length_info: 0 means no restriction
998 1001 text_project_destroy_confirmation: Are you sure you want to delete this project and related data?
999 1002 text_subprojects_destroy_warning: "Its subproject(s): %{value} will be also deleted."
1000 1003 text_workflow_edit: Select a role and a tracker to edit the workflow
1001 1004 text_are_you_sure: Are you sure?
1002 1005 text_journal_changed: "%{label} changed from %{old} to %{new}"
1003 1006 text_journal_changed_no_detail: "%{label} updated"
1004 1007 text_journal_set_to: "%{label} set to %{value}"
1005 1008 text_journal_deleted: "%{label} deleted (%{old})"
1006 1009 text_journal_added: "%{label} %{value} added"
1007 1010 text_tip_issue_begin_day: issue beginning this day
1008 1011 text_tip_issue_end_day: issue ending this day
1009 1012 text_tip_issue_begin_end_day: issue beginning and ending this day
1010 1013 text_project_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed, must start with a lower case letter.<br />Once saved, the identifier cannot be changed.'
1011 1014 text_caracters_maximum: "%{count} characters maximum."
1012 1015 text_caracters_minimum: "Must be at least %{count} characters long."
1013 1016 text_length_between: "Length between %{min} and %{max} characters."
1014 1017 text_tracker_no_workflow: No workflow defined for this tracker
1015 1018 text_unallowed_characters: Unallowed characters
1016 1019 text_comma_separated: Multiple values allowed (comma separated).
1017 1020 text_line_separated: Multiple values allowed (one line for each value).
1018 1021 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
1019 1022 text_issue_added: "Issue %{id} has been reported by %{author}."
1020 1023 text_issue_updated: "Issue %{id} has been updated by %{author}."
1021 1024 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content?
1022 1025 text_issue_category_destroy_question: "Some issues (%{count}) are assigned to this category. What do you want to do?"
1023 1026 text_issue_category_destroy_assignments: Remove category assignments
1024 1027 text_issue_category_reassign_to: Reassign issues to this category
1025 1028 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)."
1026 1029 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."
1027 1030 text_load_default_configuration: Load the default configuration
1028 1031 text_status_changed_by_changeset: "Applied in changeset %{value}."
1029 1032 text_time_logged_by_changeset: "Applied in changeset %{value}."
1030 1033 text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
1031 1034 text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
1032 1035 text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
1033 1036 text_select_project_modules: 'Select modules to enable for this project:'
1034 1037 text_default_administrator_account_changed: Default administrator account changed
1035 1038 text_file_repository_writable: Attachments directory writable
1036 1039 text_plugin_assets_writable: Plugin assets directory writable
1037 1040 text_rmagick_available: RMagick available (optional)
1038 1041 text_convert_available: ImageMagick convert available (optional)
1039 1042 text_destroy_time_entries_question: "%{hours} hours were reported on the issues you are about to delete. What do you want to do?"
1040 1043 text_destroy_time_entries: Delete reported hours
1041 1044 text_assign_time_entries_to_project: Assign reported hours to the project
1042 1045 text_reassign_time_entries: 'Reassign reported hours to this issue:'
1043 1046 text_user_wrote: "%{value} wrote:"
1044 1047 text_enumeration_destroy_question: "%{count} objects are assigned to this value."
1045 1048 text_enumeration_category_reassign_to: 'Reassign them to this value:'
1046 1049 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."
1047 1050 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."
1048 1051 text_diff_truncated: '... This diff was truncated because it exceeds the maximum size that can be displayed.'
1049 1052 text_custom_field_possible_values_info: 'One line for each value'
1050 1053 text_wiki_page_destroy_question: "This page has %{descendants} child page(s) and descendant(s). What do you want to do?"
1051 1054 text_wiki_page_nullify_children: "Keep child pages as root pages"
1052 1055 text_wiki_page_destroy_children: "Delete child pages and all their descendants"
1053 1056 text_wiki_page_reassign_children: "Reassign child pages to this parent page"
1054 1057 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?"
1055 1058 text_zoom_in: Zoom in
1056 1059 text_zoom_out: Zoom out
1057 1060 text_warn_on_leaving_unsaved: "The current page contains unsaved text that will be lost if you leave this page."
1058 1061 text_scm_path_encoding_note: "Default: UTF-8"
1059 1062 text_subversion_repository_note: "Examples: file:///, http://, https://, svn://, svn+[tunnelscheme]://"
1060 1063 text_git_repository_note: Repository is bare and local (e.g. /gitrepo, c:\gitrepo)
1061 1064 text_mercurial_repository_note: Local repository (e.g. /hgrepo, c:\hgrepo)
1062 1065 text_scm_command: Command
1063 1066 text_scm_command_version: Version
1064 1067 text_scm_config: You can configure your SCM commands in config/configuration.yml. Please restart the application after editing it.
1065 1068 text_scm_command_not_available: SCM command is not available. Please check settings on the administration panel.
1066 1069 text_issue_conflict_resolution_overwrite: "Apply my changes anyway (previous notes will be kept but some changes may be overwritten)"
1067 1070 text_issue_conflict_resolution_add_notes: "Add my notes and discard my other changes"
1068 1071 text_issue_conflict_resolution_cancel: "Discard all my changes and redisplay %{link}"
1069 1072 text_account_destroy_confirmation: "Are you sure you want to proceed?\nYour account will be permanently deleted, with no way to reactivate it."
1070 1073 text_session_expiration_settings: "Warning: changing these settings may expire the current sessions including yours."
1071 1074 text_project_closed: This project is closed and read-only.
1072 1075 text_turning_multiple_off: "If you disable multiple values, multiple values will be removed in order to preserve only one value per item."
1073 1076
1074 1077 default_role_manager: Manager
1075 1078 default_role_developer: Developer
1076 1079 default_role_reporter: Reporter
1077 1080 default_tracker_bug: Bug
1078 1081 default_tracker_feature: Feature
1079 1082 default_tracker_support: Support
1080 1083 default_issue_status_new: New
1081 1084 default_issue_status_in_progress: In Progress
1082 1085 default_issue_status_resolved: Resolved
1083 1086 default_issue_status_feedback: Feedback
1084 1087 default_issue_status_closed: Closed
1085 1088 default_issue_status_rejected: Rejected
1086 1089 default_doc_category_user: User documentation
1087 1090 default_doc_category_tech: Technical documentation
1088 1091 default_priority_low: Low
1089 1092 default_priority_normal: Normal
1090 1093 default_priority_high: High
1091 1094 default_priority_urgent: Urgent
1092 1095 default_priority_immediate: Immediate
1093 1096 default_activity_design: Design
1094 1097 default_activity_development: Development
1095 1098
1096 1099 enumeration_issue_priorities: Issue priorities
1097 1100 enumeration_doc_categories: Document categories
1098 1101 enumeration_activities: Activities (time tracking)
1099 1102 enumeration_system_activity: System Activity
1100 1103 description_filter: Filter
1101 1104 description_search: Searchfield
1102 1105 description_choose_project: Projects
1103 1106 description_project_scope: Search scope
1104 1107 description_notes: Notes
1105 1108 description_message_content: Message content
1106 1109 description_query_sort_criteria_attribute: Sort attribute
1107 1110 description_query_sort_criteria_direction: Sort direction
1108 1111 description_user_mail_notification: Mail notification settings
1109 1112 description_available_columns: Available Columns
1110 1113 description_selected_columns: Selected Columns
1111 1114 description_all_columns: All Columns
1112 1115 description_issue_category_reassign: Choose issue category
1113 1116 description_wiki_subpages_reassign: Choose new parent page
1114 1117 description_date_range_list: Choose range from list
1115 1118 description_date_range_interval: Choose range by selecting start and end date
1116 1119 description_date_from: Enter start date
1117 1120 description_date_to: Enter end date
1118 1121 text_repository_identifier_info: 'Only lower case letters (a-z), numbers, dashes and underscores are allowed.<br />Once saved, the identifier cannot be changed.'
@@ -1,1138 +1,1141
1 1 # French translations for Ruby on Rails
2 2 # by Christian Lescuyer (christian@flyingcoders.com)
3 3 # contributor: Sebastien Grosjean - ZenCocoon.com
4 4 # contributor: Thibaut Cuvelier - Developpez.com
5 5
6 6 fr:
7 7 direction: ltr
8 8 date:
9 9 formats:
10 10 default: "%d/%m/%Y"
11 11 short: "%e %b"
12 12 long: "%e %B %Y"
13 13 long_ordinal: "%e %B %Y"
14 14 only_day: "%e"
15 15
16 16 day_names: [dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]
17 17 abbr_day_names: [dim, lun, mar, mer, jeu, ven, sam]
18 18
19 19 # Don't forget the nil at the beginning; there's no such thing as a 0th month
20 20 month_names: [~, janvier, fΓ©vrier, mars, avril, mai, juin, juillet, aoΓ»t, septembre, octobre, novembre, dΓ©cembre]
21 21 abbr_month_names: [~, jan., fΓ©v., mar., avr., mai, juin, juil., aoΓ»t, sept., oct., nov., dΓ©c.]
22 22 # Used in date_select and datime_select.
23 23 order:
24 24 - :day
25 25 - :month
26 26 - :year
27 27
28 28 time:
29 29 formats:
30 30 default: "%d/%m/%Y %H:%M"
31 31 time: "%H:%M"
32 32 short: "%d %b %H:%M"
33 33 long: "%A %d %B %Y %H:%M:%S %Z"
34 34 long_ordinal: "%A %d %B %Y %H:%M:%S %Z"
35 35 only_second: "%S"
36 36 am: 'am'
37 37 pm: 'pm'
38 38
39 39 datetime:
40 40 distance_in_words:
41 41 half_a_minute: "30 secondes"
42 42 less_than_x_seconds:
43 43 zero: "moins d'une seconde"
44 44 one: "moins d'uneΒ seconde"
45 45 other: "moins de %{count}Β secondes"
46 46 x_seconds:
47 47 one: "1Β seconde"
48 48 other: "%{count}Β secondes"
49 49 less_than_x_minutes:
50 50 zero: "moins d'une minute"
51 51 one: "moins d'uneΒ minute"
52 52 other: "moins de %{count}Β minutes"
53 53 x_minutes:
54 54 one: "1Β minute"
55 55 other: "%{count}Β minutes"
56 56 about_x_hours:
57 57 one: "environ une heure"
58 58 other: "environ %{count}Β heures"
59 59 x_hours:
60 60 one: "une heure"
61 61 other: "%{count}Β heures"
62 62 x_days:
63 63 one: "unΒ jour"
64 64 other: "%{count}Β jours"
65 65 about_x_months:
66 66 one: "environ un mois"
67 67 other: "environ %{count}Β mois"
68 68 x_months:
69 69 one: "unΒ mois"
70 70 other: "%{count}Β mois"
71 71 about_x_years:
72 72 one: "environ un an"
73 73 other: "environ %{count}Β ans"
74 74 over_x_years:
75 75 one: "plus d'un an"
76 76 other: "plus de %{count}Β ans"
77 77 almost_x_years:
78 78 one: "presqu'un an"
79 79 other: "presque %{count} ans"
80 80 prompts:
81 81 year: "AnnΓ©e"
82 82 month: "Mois"
83 83 day: "Jour"
84 84 hour: "Heure"
85 85 minute: "Minute"
86 86 second: "Seconde"
87 87
88 88 number:
89 89 format:
90 90 precision: 3
91 91 separator: ','
92 92 delimiter: 'Β '
93 93 currency:
94 94 format:
95 95 unit: '€'
96 96 precision: 2
97 97 format: '%nΒ %u'
98 98 human:
99 99 format:
100 100 precision: 3
101 101 storage_units:
102 102 format: "%n %u"
103 103 units:
104 104 byte:
105 105 one: "octet"
106 106 other: "octets"
107 107 kb: "ko"
108 108 mb: "Mo"
109 109 gb: "Go"
110 110 tb: "To"
111 111
112 112 support:
113 113 array:
114 114 sentence_connector: 'et'
115 115 skip_last_comma: true
116 116 word_connector: ", "
117 117 two_words_connector: " et "
118 118 last_word_connector: " et "
119 119
120 120 activerecord:
121 121 errors:
122 122 template:
123 123 header:
124 124 one: "Impossible d'enregistrer %{model} : une erreur"
125 125 other: "Impossible d'enregistrer %{model} : %{count} erreurs."
126 126 body: "Veuillez vΓ©rifier les champs suivantsΒ :"
127 127 messages:
128 128 inclusion: "n'est pas inclus(e) dans la liste"
129 129 exclusion: "n'est pas disponible"
130 130 invalid: "n'est pas valide"
131 131 confirmation: "ne concorde pas avec la confirmation"
132 132 accepted: "doit Γͺtre acceptΓ©(e)"
133 133 empty: "doit Γͺtre renseignΓ©(e)"
134 134 blank: "doit Γͺtre renseignΓ©(e)"
135 135 too_long: "est trop long (pas plus de %{count} caractères)"
136 136 too_short: "est trop court (au moins %{count} caractères)"
137 137 wrong_length: "ne fait pas la bonne longueur (doit comporter %{count} caractères)"
138 138 taken: "est dΓ©jΓ  utilisΓ©"
139 139 not_a_number: "n'est pas un nombre"
140 140 not_a_date: "n'est pas une date valide"
141 141 greater_than: "doit Γͺtre supΓ©rieur Γ  %{count}"
142 142 greater_than_or_equal_to: "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{count}"
143 143 equal_to: "doit Γͺtre Γ©gal Γ  %{count}"
144 144 less_than: "doit Γͺtre infΓ©rieur Γ  %{count}"
145 145 less_than_or_equal_to: "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{count}"
146 146 odd: "doit Γͺtre impair"
147 147 even: "doit Γͺtre pair"
148 148 greater_than_start_date: "doit Γͺtre postΓ©rieure Γ  la date de dΓ©but"
149 149 not_same_project: "n'appartient pas au mΓͺme projet"
150 150 circular_dependency: "Cette relation crΓ©erait une dΓ©pendance circulaire"
151 151 cant_link_an_issue_with_a_descendant: "Une demande ne peut pas Γͺtre liΓ©e Γ  l'une de ses sous-tΓ’ches"
152 152 earlier_than_minimum_start_date: "ne peut pas Γͺtre antΓ©rieure au %{date} Γ  cause des demandes qui prΓ©cΓ¨dent"
153 153
154 154 actionview_instancetag_blank_option: Choisir
155 155
156 156 general_text_No: 'Non'
157 157 general_text_Yes: 'Oui'
158 158 general_text_no: 'non'
159 159 general_text_yes: 'oui'
160 160 general_lang_name: 'FranΓ§ais'
161 161 general_csv_separator: ';'
162 162 general_csv_decimal_separator: ','
163 163 general_csv_encoding: ISO-8859-1
164 164 general_pdf_fontname: freesans
165 165 general_first_day_of_week: '1'
166 166
167 167 notice_account_updated: Le compte a été mis à jour avec succès.
168 168 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
169 169 notice_account_password_updated: Mot de passe mis à jour avec succès.
170 170 notice_account_wrong_password: Mot de passe incorrect
171 171 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a Γ©tΓ© envoyΓ© Γ  l'adresse %{email}.
172 172 notice_account_unknown_email: Aucun compte ne correspond Γ  cette adresse.
173 173 notice_account_not_activated_yet: Vous n'avez pas encore activΓ© votre compte. Si vous voulez recevoir un nouveau message d'activation, veuillez <a href="%{url}">cliquer sur ce lien</a>.
174 174 notice_account_locked: Votre compte est verrouillΓ©.
175 175 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
176 176 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a Γ©tΓ© envoyΓ©.
177 177 notice_account_activated: Votre compte a Γ©tΓ© activΓ©. Vous pouvez Γ  prΓ©sent vous connecter.
178 178 notice_successful_create: Création effectuée avec succès.
179 179 notice_successful_update: Mise à jour effectuée avec succès.
180 180 notice_successful_delete: Suppression effectuée avec succès.
181 181 notice_successful_connection: Connexion rΓ©ussie.
182 182 notice_file_not_found: "La page Γ  laquelle vous souhaitez accΓ©der n'existe pas ou a Γ©tΓ© supprimΓ©e."
183 183 notice_locking_conflict: Les donnΓ©es ont Γ©tΓ© mises Γ  jour par un autre utilisateur. Mise Γ  jour impossible.
184 184 notice_not_authorized: "Vous n'Γͺtes pas autorisΓ© Γ  accΓ©der Γ  cette page."
185 185 notice_not_authorized_archived_project: Le projet auquel vous tentez d'accΓ©der a Γ©tΓ© archivΓ©.
186 186 notice_email_sent: "Un email a Γ©tΓ© envoyΓ© Γ  %{value}"
187 187 notice_email_error: "Erreur lors de l'envoi de l'email (%{value})"
188 188 notice_feeds_access_key_reseted: "Votre clé d'accès aux flux Atom a été réinitialisée."
189 189 notice_api_access_key_reseted: Votre clé d'accès API a été réinitialisée.
190 190 notice_failed_to_save_issues: "%{count} demande(s) sur les %{total} sΓ©lectionnΓ©es n'ont pas pu Γͺtre mise(s) Γ  jour : %{ids}."
191 191 notice_failed_to_save_time_entries: "%{count} temps passΓ©(s) sur les %{total} sΓ©lectionnΓ©s n'ont pas pu Γͺtre mis Γ  jour: %{ids}."
192 192 notice_failed_to_save_members: "Erreur lors de la sauvegarde des membres: %{errors}."
193 193 notice_no_issue_selected: "Aucune demande sΓ©lectionnΓ©e ! Cochez les demandes que vous voulez mettre Γ  jour."
194 194 notice_account_pending: "Votre compte a été créé et attend l'approbation de l'administrateur."
195 195 notice_default_data_loaded: Paramétrage par défaut chargé avec succès.
196 196 notice_unable_delete_version: Impossible de supprimer cette version.
197 197 notice_unable_delete_time_entry: Impossible de supprimer le temps passΓ©.
198 198 notice_issue_done_ratios_updated: L'avancement des demandes a Γ©tΓ© mis Γ  jour.
199 199 notice_gantt_chart_truncated: "Le diagramme a Γ©tΓ© tronquΓ© car il excΓ¨de le nombre maximal d'Γ©lΓ©ments pouvant Γͺtre affichΓ©s (%{max})"
200 200 notice_issue_successful_create: "Demande %{id} créée."
201 201 notice_issue_update_conflict: "La demande a Γ©tΓ© mise Γ  jour par un autre utilisateur pendant que vous la modifiez."
202 202 notice_account_deleted: "Votre compte a Γ©tΓ© dΓ©finitivement supprimΓ©."
203 203 notice_user_successful_create: "Utilisateur %{id} créé."
204 204 notice_new_password_must_be_different: Votre nouveau mot de passe doit Γͺtre diffΓ©rent de votre mot de passe actuel
205 205
206 206 error_can_t_load_default_data: "Une erreur s'est produite lors du chargement du paramΓ©trage : %{value}"
207 207 error_scm_not_found: "L'entrΓ©e et/ou la rΓ©vision demandΓ©e n'existe pas dans le dΓ©pΓ΄t."
208 208 error_scm_command_failed: "Une erreur s'est produite lors de l'accès au dépôt : %{value}"
209 209 error_scm_annotate: "L'entrΓ©e n'existe pas ou ne peut pas Γͺtre annotΓ©e."
210 210 error_scm_annotate_big_text_file: Cette entrΓ©e ne peut pas Γͺtre annotΓ©e car elle excΓ¨de la taille maximale.
211 211 error_issue_not_found_in_project: "La demande n'existe pas ou n'appartient pas Γ  ce projet"
212 212 error_no_tracker_in_project: "Aucun tracker n'est associΓ© Γ  ce projet. VΓ©rifier la configuration du projet."
213 213 error_no_default_issue_status: "Aucun statut de demande n'est dΓ©fini par dΓ©faut. VΓ©rifier votre configuration (Administration -> Statuts de demandes)."
214 214 error_can_not_delete_custom_field: Impossible de supprimer le champ personnalisΓ©
215 215 error_can_not_delete_tracker: Ce tracker contient des demandes et ne peut pas Γͺtre supprimΓ©.
216 216 error_can_not_remove_role: Ce rΓ΄le est utilisΓ© et ne peut pas Γͺtre supprimΓ©.
217 217 error_can_not_reopen_issue_on_closed_version: 'Une demande assignΓ©e Γ  une version fermΓ©e ne peut pas Γͺtre rΓ©ouverte'
218 218 error_can_not_archive_project: "Ce projet ne peut pas Γͺtre archivΓ©"
219 219 error_issue_done_ratios_not_updated: L'avancement des demandes n'a pas pu Γͺtre mis Γ  jour.
220 220 error_workflow_copy_source: 'Veuillez sΓ©lectionner un tracker et/ou un rΓ΄le source'
221 221 error_workflow_copy_target: 'Veuillez sΓ©lectionner les trackers et rΓ΄les cibles'
222 222 error_unable_delete_issue_status: Impossible de supprimer le statut de demande
223 223 error_unable_to_connect: Connexion impossible (%{value})
224 224 error_attachment_too_big: Ce fichier ne peut pas Γͺtre attachΓ© car il excΓ¨de la taille maximale autorisΓ©e (%{max_size})
225 225 error_session_expired: "Votre session a expirΓ©. Veuillez vous reconnecter."
226 226 warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu Γͺtre sauvegardΓ©s."
227 227
228 228 mail_subject_lost_password: "Votre mot de passe %{value}"
229 229 mail_body_lost_password: 'Pour changer votre mot de passe, cliquez sur le lien suivant :'
230 230 mail_subject_register: "Activation de votre compte %{value}"
231 231 mail_body_register: 'Pour activer votre compte, cliquez sur le lien suivant :'
232 232 mail_body_account_information_external: "Vous pouvez utiliser votre compte %{value} pour vous connecter."
233 233 mail_body_account_information: Paramètres de connexion de votre compte
234 234 mail_subject_account_activation_request: "Demande d'activation d'un compte %{value}"
235 235 mail_body_account_activation_request: "Un nouvel utilisateur (%{value}) s'est inscrit. Son compte nΓ©cessite votre approbation :"
236 236 mail_subject_reminder: "%{count} demande(s) arrivent Γ  Γ©chΓ©ance (%{days})"
237 237 mail_body_reminder: "%{count} demande(s) qui vous sont assignΓ©es arrivent Γ  Γ©chΓ©ance dans les %{days} prochains jours :"
238 238 mail_subject_wiki_content_added: "Page wiki '%{id}' ajoutΓ©e"
239 239 mail_body_wiki_content_added: "La page wiki '%{id}' a Γ©tΓ© ajoutΓ©e par %{author}."
240 240 mail_subject_wiki_content_updated: "Page wiki '%{id}' mise Γ  jour"
241 241 mail_body_wiki_content_updated: "La page wiki '%{id}' a Γ©tΓ© mise Γ  jour par %{author}."
242 242
243 243 field_name: Nom
244 244 field_description: Description
245 245 field_summary: RΓ©sumΓ©
246 246 field_is_required: Obligatoire
247 247 field_firstname: PrΓ©nom
248 248 field_lastname: Nom
249 249 field_mail: Email
250 250 field_filename: Fichier
251 251 field_filesize: Taille
252 252 field_downloads: TΓ©lΓ©chargements
253 253 field_author: Auteur
254 254 field_created_on: Créé
255 255 field_updated_on: Mis-Γ -jour
256 256 field_closed_on: FermΓ©
257 257 field_field_format: Format
258 258 field_is_for_all: Pour tous les projets
259 259 field_possible_values: Valeurs possibles
260 260 field_regexp: Expression régulière
261 261 field_min_length: Longueur minimum
262 262 field_max_length: Longueur maximum
263 263 field_value: Valeur
264 264 field_category: CatΓ©gorie
265 265 field_title: Titre
266 266 field_project: Projet
267 267 field_issue: Demande
268 268 field_status: Statut
269 269 field_notes: Notes
270 270 field_is_closed: Demande fermΓ©e
271 271 field_is_default: Valeur par dΓ©faut
272 272 field_tracker: Tracker
273 273 field_subject: Sujet
274 274 field_due_date: EchΓ©ance
275 275 field_assigned_to: AssignΓ© Γ 
276 276 field_priority: PrioritΓ©
277 277 field_fixed_version: Version cible
278 278 field_user: Utilisateur
279 279 field_principal: Principal
280 280 field_role: RΓ΄le
281 281 field_homepage: Site web
282 282 field_is_public: Public
283 283 field_parent: Sous-projet de
284 284 field_is_in_roadmap: Demandes affichΓ©es dans la roadmap
285 285 field_login: Identifiant
286 286 field_mail_notification: Notifications par mail
287 287 field_admin: Administrateur
288 288 field_last_login_on: Dernière connexion
289 289 field_language: Langue
290 290 field_effective_date: Date
291 291 field_password: Mot de passe
292 292 field_new_password: Nouveau mot de passe
293 293 field_password_confirmation: Confirmation
294 294 field_version: Version
295 295 field_type: Type
296 296 field_host: HΓ΄te
297 297 field_port: Port
298 298 field_account: Compte
299 299 field_base_dn: Base DN
300 300 field_attr_login: Attribut Identifiant
301 301 field_attr_firstname: Attribut PrΓ©nom
302 302 field_attr_lastname: Attribut Nom
303 303 field_attr_mail: Attribut Email
304 304 field_onthefly: CrΓ©ation des utilisateurs Γ  la volΓ©e
305 305 field_start_date: DΓ©but
306 306 field_done_ratio: "% rΓ©alisΓ©"
307 307 field_auth_source: Mode d'authentification
308 308 field_hide_mail: Cacher mon adresse mail
309 309 field_comments: Commentaire
310 310 field_url: URL
311 311 field_start_page: Page de dΓ©marrage
312 312 field_subproject: Sous-projet
313 313 field_hours: Heures
314 314 field_activity: ActivitΓ©
315 315 field_spent_on: Date
316 316 field_identifier: Identifiant
317 317 field_is_filter: UtilisΓ© comme filtre
318 318 field_issue_to: Demande liΓ©e
319 319 field_delay: Retard
320 320 field_assignable: Demandes assignables Γ  ce rΓ΄le
321 321 field_redirect_existing_links: Rediriger les liens existants
322 322 field_estimated_hours: Temps estimΓ©
323 323 field_column_names: Colonnes
324 324 field_time_entries: Temps passΓ©
325 325 field_time_zone: Fuseau horaire
326 326 field_searchable: UtilisΓ© pour les recherches
327 327 field_default_value: Valeur par dΓ©faut
328 328 field_comments_sorting: Afficher les commentaires
329 329 field_parent_title: Page parent
330 330 field_editable: Modifiable
331 331 field_watcher: Observateur
332 332 field_identity_url: URL OpenID
333 333 field_content: Contenu
334 334 field_group_by: Grouper par
335 335 field_sharing: Partage
336 336 field_parent_issue: TΓ’che parente
337 337 field_member_of_group: Groupe de l'assignΓ©
338 338 field_assigned_to_role: RΓ΄le de l'assignΓ©
339 339 field_text: Champ texte
340 340 field_visible: Visible
341 341 field_warn_on_leaving_unsaved: "M'avertir lorsque je quitte une page contenant du texte non sauvegardΓ©"
342 342 field_issues_visibility: VisibilitΓ© des demandes
343 343 field_is_private: PrivΓ©e
344 344 field_commit_logs_encoding: Encodage des messages de commit
345 345 field_scm_path_encoding: Encodage des chemins
346 346 field_path_to_repository: Chemin du dΓ©pΓ΄t
347 347 field_root_directory: RΓ©pertoire racine
348 348 field_cvsroot: CVSROOT
349 349 field_cvs_module: Module
350 350 field_repository_is_default: DΓ©pΓ΄t principal
351 351 field_multiple: Valeurs multiples
352 352 field_auth_source_ldap_filter: Filtre LDAP
353 353 field_core_fields: Champs standards
354 354 field_timeout: "Timeout (en secondes)"
355 355 field_board_parent: Forum parent
356 356 field_private_notes: Notes privΓ©es
357 357 field_inherit_members: HΓ©riter les membres
358 358 field_generate_password: GΓ©nΓ©rer un mot de passe
359 359 field_must_change_passwd: Doit changer de mot de passe Γ  la prochaine connexion
360 360 field_default_status: Statut par dΓ©faut
361 361 field_users_visibility: VisibilitΓ© des utilisateurs
362 362
363 363 setting_app_title: Titre de l'application
364 364 setting_app_subtitle: Sous-titre de l'application
365 365 setting_welcome_text: Texte d'accueil
366 366 setting_default_language: Langue par dΓ©faut
367 367 setting_login_required: Authentification obligatoire
368 368 setting_self_registration: Inscription des nouveaux utilisateurs
369 369 setting_attachment_max_size: Taille maximale des fichiers
370 370 setting_issues_export_limit: Limite d'exportation des demandes
371 371 setting_mail_from: Adresse d'Γ©mission
372 372 setting_bcc_recipients: Destinataires en copie cachΓ©e (cci)
373 373 setting_plain_text_mail: Mail en texte brut (non HTML)
374 374 setting_host_name: Nom d'hΓ΄te et chemin
375 375 setting_text_formatting: Formatage du texte
376 376 setting_wiki_compression: Compression de l'historique des pages wiki
377 377 setting_feeds_limit: Nombre maximal d'Γ©lΓ©ments dans les flux Atom
378 378 setting_default_projects_public: DΓ©finir les nouveaux projets comme publics par dΓ©faut
379 379 setting_autofetch_changesets: RΓ©cupΓ©ration automatique des commits
380 380 setting_sys_api_enabled: Activer les WS pour la gestion des dΓ©pΓ΄ts
381 381 setting_commit_ref_keywords: Mots-clΓ©s de rΓ©fΓ©rencement
382 382 setting_commit_fix_keywords: Mots-clΓ©s de rΓ©solution
383 383 setting_autologin: DurΓ©e maximale de connexion automatique
384 384 setting_date_format: Format de date
385 385 setting_time_format: Format d'heure
386 386 setting_cross_project_issue_relations: Autoriser les relations entre demandes de diffΓ©rents projets
387 387 setting_cross_project_subtasks: Autoriser les sous-tΓ’ches dans des projets diffΓ©rents
388 388 setting_issue_list_default_columns: Colonnes affichΓ©es par dΓ©faut sur la liste des demandes
389 389 setting_repositories_encodings: Encodages des fichiers et des dΓ©pΓ΄ts
390 390 setting_emails_header: En-tΓͺte des emails
391 391 setting_emails_footer: Pied-de-page des emails
392 392 setting_protocol: Protocole
393 393 setting_per_page_options: Options d'objets affichΓ©s par page
394 394 setting_user_format: Format d'affichage des utilisateurs
395 395 setting_activity_days_default: Nombre de jours affichΓ©s sur l'activitΓ© des projets
396 396 setting_display_subprojects_issues: Afficher par dΓ©faut les demandes des sous-projets sur les projets principaux
397 397 setting_enabled_scm: SCM activΓ©s
398 398 setting_mail_handler_body_delimiters: "Tronquer les emails après l'une de ces lignes"
399 399 setting_mail_handler_api_enabled: "Activer le WS pour la rΓ©ception d'emails"
400 400 setting_mail_handler_api_key: ClΓ© de protection de l'API
401 401 setting_sequential_project_identifiers: GΓ©nΓ©rer des identifiants de projet sΓ©quentiels
402 402 setting_gravatar_enabled: Afficher les Gravatar des utilisateurs
403 403 setting_gravatar_default: Image Gravatar par dΓ©faut
404 404 setting_diff_max_lines_displayed: Nombre maximum de lignes de diff affichΓ©es
405 405 setting_file_max_size_displayed: Taille maximum des fichiers texte affichΓ©s en ligne
406 406 setting_repository_log_display_limit: "Nombre maximum de rΓ©visions affichΓ©es sur l'historique d'un fichier"
407 407 setting_openid: "Autoriser l'authentification et l'enregistrement OpenID"
408 408 setting_password_min_length: Longueur minimum des mots de passe
409 409 setting_new_project_user_role_id: RΓ΄le donnΓ© Γ  un utilisateur non-administrateur qui crΓ©e un projet
410 410 setting_default_projects_modules: Modules activΓ©s par dΓ©faut pour les nouveaux projets
411 411 setting_issue_done_ratio: Calcul de l'avancement des demandes
412 412 setting_issue_done_ratio_issue_field: 'Utiliser le champ % effectuΓ©'
413 413 setting_issue_done_ratio_issue_status: Utiliser le statut
414 414 setting_start_of_week: Jour de dΓ©but des calendriers
415 415 setting_rest_api_enabled: Activer l'API REST
416 416 setting_cache_formatted_text: Mettre en cache le texte formatΓ©
417 417 setting_default_notification_option: Option de notification par dΓ©faut
418 418 setting_commit_logtime_enabled: Permettre la saisie de temps
419 419 setting_commit_logtime_activity_id: ActivitΓ© pour le temps saisi
420 420 setting_gantt_items_limit: Nombre maximum d'Γ©lΓ©ments affichΓ©s sur le gantt
421 421 setting_issue_group_assignment: Permettre l'assignement des demandes aux groupes
422 422 setting_default_issue_start_date_to_creation_date: Donner Γ  la date de dΓ©but d'une nouvelle demande la valeur de la date du jour
423 423 setting_commit_cross_project_ref: Permettre le rΓ©fΓ©rencement et la rΓ©solution des demandes de tous les autres projets
424 424 setting_unsubscribe: Permettre aux utilisateurs de supprimer leur propre compte
425 425 setting_session_lifetime: DurΓ©e de vie maximale des sessions
426 426 setting_session_timeout: DurΓ©e maximale d'inactivitΓ©
427 427 setting_thumbnails_enabled: Afficher les vignettes des images
428 428 setting_thumbnails_size: Taille des vignettes (en pixels)
429 429 setting_non_working_week_days: Jours non travaillΓ©s
430 430 setting_jsonp_enabled: Activer le support JSONP
431 431 setting_default_projects_tracker_ids: Trackers par dΓ©faut pour les nouveaux projets
432 432 setting_mail_handler_excluded_filenames: Exclure les fichiers attachΓ©s par leur nom
433 433 setting_force_default_language_for_anonymous: Forcer la langue par dΓ©fault pour les utilisateurs anonymes
434 434 setting_force_default_language_for_loggedin: Forcer la langue par dΓ©fault pour les utilisateurs identifiΓ©s
435 setting_link_copied_issue: Lier les demandes lors de la copie
435 436
436 437 permission_add_project: CrΓ©er un projet
437 438 permission_add_subprojects: CrΓ©er des sous-projets
438 439 permission_edit_project: Modifier le projet
439 440 permission_close_project: Fermer / rΓ©ouvrir le projet
440 441 permission_select_project_modules: Choisir les modules
441 442 permission_manage_members: GΓ©rer les membres
442 443 permission_manage_project_activities: GΓ©rer les activitΓ©s
443 444 permission_manage_versions: GΓ©rer les versions
444 445 permission_manage_categories: GΓ©rer les catΓ©gories de demandes
445 446 permission_view_issues: Voir les demandes
446 447 permission_add_issues: CrΓ©er des demandes
447 448 permission_edit_issues: Modifier les demandes
448 449 permission_manage_issue_relations: GΓ©rer les relations
449 450 permission_set_issues_private: Rendre les demandes publiques ou privΓ©es
450 451 permission_set_own_issues_private: Rendre ses propres demandes publiques ou privΓ©es
451 452 permission_add_issue_notes: Ajouter des notes
452 453 permission_edit_issue_notes: Modifier les notes
453 454 permission_edit_own_issue_notes: Modifier ses propres notes
454 455 permission_view_private_notes: Voir les notes privΓ©es
455 456 permission_set_notes_private: Rendre les notes privΓ©es
456 457 permission_move_issues: DΓ©placer les demandes
457 458 permission_delete_issues: Supprimer les demandes
458 459 permission_manage_public_queries: GΓ©rer les requΓͺtes publiques
459 460 permission_save_queries: Sauvegarder les requΓͺtes
460 461 permission_view_gantt: Voir le gantt
461 462 permission_view_calendar: Voir le calendrier
462 463 permission_view_issue_watchers: Voir la liste des observateurs
463 464 permission_add_issue_watchers: Ajouter des observateurs
464 465 permission_delete_issue_watchers: Supprimer des observateurs
465 466 permission_log_time: Saisir le temps passΓ©
466 467 permission_view_time_entries: Voir le temps passΓ©
467 468 permission_edit_time_entries: Modifier les temps passΓ©s
468 469 permission_edit_own_time_entries: Modifier son propre temps passΓ©
469 470 permission_manage_news: GΓ©rer les annonces
470 471 permission_comment_news: Commenter les annonces
471 472 permission_view_documents: Voir les documents
472 473 permission_add_documents: Ajouter des documents
473 474 permission_edit_documents: Modifier les documents
474 475 permission_delete_documents: Supprimer les documents
475 476 permission_manage_files: GΓ©rer les fichiers
476 477 permission_view_files: Voir les fichiers
477 478 permission_manage_wiki: GΓ©rer le wiki
478 479 permission_rename_wiki_pages: Renommer les pages
479 480 permission_delete_wiki_pages: Supprimer les pages
480 481 permission_view_wiki_pages: Voir le wiki
481 482 permission_view_wiki_edits: "Voir l'historique des modifications"
482 483 permission_edit_wiki_pages: Modifier les pages
483 484 permission_delete_wiki_pages_attachments: Supprimer les fichiers joints
484 485 permission_protect_wiki_pages: ProtΓ©ger les pages
485 486 permission_manage_repository: GΓ©rer le dΓ©pΓ΄t de sources
486 487 permission_browse_repository: Parcourir les sources
487 488 permission_view_changesets: Voir les rΓ©visions
488 489 permission_commit_access: Droit de commit
489 490 permission_manage_boards: GΓ©rer les forums
490 491 permission_view_messages: Voir les messages
491 492 permission_add_messages: Poster un message
492 493 permission_edit_messages: Modifier les messages
493 494 permission_edit_own_messages: Modifier ses propres messages
494 495 permission_delete_messages: Supprimer les messages
495 496 permission_delete_own_messages: Supprimer ses propres messages
496 497 permission_export_wiki_pages: Exporter les pages
497 498 permission_manage_subtasks: GΓ©rer les sous-tΓ’ches
498 499 permission_manage_related_issues: GΓ©rer les demandes associΓ©es
499 500
500 501 project_module_issue_tracking: Suivi des demandes
501 502 project_module_time_tracking: Suivi du temps passΓ©
502 503 project_module_news: Publication d'annonces
503 504 project_module_documents: Publication de documents
504 505 project_module_files: Publication de fichiers
505 506 project_module_wiki: Wiki
506 507 project_module_repository: DΓ©pΓ΄t de sources
507 508 project_module_boards: Forums de discussion
508 509 project_module_calendar: Calendrier
509 510 project_module_gantt: Gantt
510 511
511 512 label_user: Utilisateur
512 513 label_user_plural: Utilisateurs
513 514 label_user_new: Nouvel utilisateur
514 515 label_user_anonymous: Anonyme
515 516 label_project: Projet
516 517 label_project_new: Nouveau projet
517 518 label_project_plural: Projets
518 519 label_x_projects:
519 520 zero: aucun projet
520 521 one: un projet
521 522 other: "%{count} projets"
522 523 label_project_all: Tous les projets
523 524 label_project_latest: Derniers projets
524 525 label_issue: Demande
525 526 label_issue_new: Nouvelle demande
526 527 label_issue_plural: Demandes
527 528 label_issue_view_all: Voir toutes les demandes
528 529 label_issues_by: "Demandes par %{value}"
529 530 label_issue_added: Demande ajoutΓ©e
530 531 label_issue_updated: Demande mise Γ  jour
531 532 label_issue_note_added: Note ajoutΓ©e
532 533 label_issue_status_updated: Statut changΓ©
533 534 label_issue_assigned_to_updated: AssignΓ© changΓ©
534 535 label_issue_priority_updated: PrioritΓ© changΓ©e
535 536 label_document: Document
536 537 label_document_new: Nouveau document
537 538 label_document_plural: Documents
538 539 label_document_added: Document ajoutΓ©
539 540 label_role: RΓ΄le
540 541 label_role_plural: RΓ΄les
541 542 label_role_new: Nouveau rΓ΄le
542 543 label_role_and_permissions: RΓ΄les et permissions
543 544 label_role_anonymous: Anonyme
544 545 label_role_non_member: Non membre
545 546 label_member: Membre
546 547 label_member_new: Nouveau membre
547 548 label_member_plural: Membres
548 549 label_tracker: Tracker
549 550 label_tracker_plural: Trackers
550 551 label_tracker_new: Nouveau tracker
551 552 label_workflow: Workflow
552 553 label_issue_status: Statut de demandes
553 554 label_issue_status_plural: Statuts de demandes
554 555 label_issue_status_new: Nouveau statut
555 556 label_issue_category: CatΓ©gorie de demandes
556 557 label_issue_category_plural: CatΓ©gories de demandes
557 558 label_issue_category_new: Nouvelle catΓ©gorie
558 559 label_custom_field: Champ personnalisΓ©
559 560 label_custom_field_plural: Champs personnalisΓ©s
560 561 label_custom_field_new: Nouveau champ personnalisΓ©
561 562 label_enumerations: Listes de valeurs
562 563 label_enumeration_new: Nouvelle valeur
563 564 label_information: Information
564 565 label_information_plural: Informations
565 566 label_please_login: Identification
566 567 label_register: S'enregistrer
567 568 label_login_with_open_id_option: S'authentifier avec OpenID
568 569 label_password_lost: Mot de passe perdu
569 570 label_home: Accueil
570 571 label_my_page: Ma page
571 572 label_my_account: Mon compte
572 573 label_my_projects: Mes projets
573 574 label_my_page_block: Blocs disponibles
574 575 label_administration: Administration
575 576 label_login: Connexion
576 577 label_logout: DΓ©connexion
577 578 label_help: Aide
578 579 label_reported_issues: Demandes soumises
579 580 label_assigned_to_me_issues: Demandes qui me sont assignΓ©es
580 581 label_last_login: Dernière connexion
581 582 label_registered_on: Inscrit le
582 583 label_activity: ActivitΓ©
583 584 label_overall_activity: ActivitΓ© globale
584 585 label_user_activity: "ActivitΓ© de %{value}"
585 586 label_new: Nouveau
586 587 label_logged_as: ConnectΓ© en tant que
587 588 label_environment: Environnement
588 589 label_authentication: Authentification
589 590 label_auth_source: Mode d'authentification
590 591 label_auth_source_new: Nouveau mode d'authentification
591 592 label_auth_source_plural: Modes d'authentification
592 593 label_subproject_plural: Sous-projets
593 594 label_subproject_new: Nouveau sous-projet
594 595 label_and_its_subprojects: "%{value} et ses sous-projets"
595 596 label_min_max_length: Longueurs mini - maxi
596 597 label_list: Liste
597 598 label_date: Date
598 599 label_integer: Entier
599 600 label_float: Nombre dΓ©cimal
600 601 label_boolean: BoolΓ©en
601 602 label_string: Texte
602 603 label_text: Texte long
603 604 label_attribute: Attribut
604 605 label_attribute_plural: Attributs
605 606 label_no_data: Aucune donnΓ©e Γ  afficher
606 607 label_change_status: Changer le statut
607 608 label_history: Historique
608 609 label_attachment: Fichier
609 610 label_attachment_new: Nouveau fichier
610 611 label_attachment_delete: Supprimer le fichier
611 612 label_attachment_plural: Fichiers
612 613 label_file_added: Fichier ajoutΓ©
613 614 label_report: Rapport
614 615 label_report_plural: Rapports
615 616 label_news: Annonce
616 617 label_news_new: Nouvelle annonce
617 618 label_news_plural: Annonces
618 619 label_news_latest: Dernières annonces
619 620 label_news_view_all: Voir toutes les annonces
620 621 label_news_added: Annonce ajoutΓ©e
621 622 label_news_comment_added: Commentaire ajoutΓ© Γ  une annonce
622 623 label_settings: Configuration
623 624 label_overview: AperΓ§u
624 625 label_version: Version
625 626 label_version_new: Nouvelle version
626 627 label_version_plural: Versions
627 628 label_close_versions: Fermer les versions terminΓ©es
628 629 label_confirmation: Confirmation
629 630 label_export_to: 'Formats disponibles :'
630 631 label_read: Lire...
631 632 label_public_projects: Projets publics
632 633 label_open_issues: ouvert
633 634 label_open_issues_plural: ouverts
634 635 label_closed_issues: fermΓ©
635 636 label_closed_issues_plural: fermΓ©s
636 637 label_x_open_issues_abbr_on_total:
637 638 zero: 0 ouverte sur %{total}
638 639 one: 1 ouverte sur %{total}
639 640 other: "%{count} ouvertes sur %{total}"
640 641 label_x_open_issues_abbr:
641 642 zero: 0 ouverte
642 643 one: 1 ouverte
643 644 other: "%{count} ouvertes"
644 645 label_x_closed_issues_abbr:
645 646 zero: 0 fermΓ©e
646 647 one: 1 fermΓ©e
647 648 other: "%{count} fermΓ©es"
648 649 label_x_issues:
649 650 zero: 0 demande
650 651 one: 1 demande
651 652 other: "%{count} demandes"
652 653 label_total: Total
653 654 label_total_time: Temps total
654 655 label_permissions: Permissions
655 656 label_current_status: Statut actuel
656 657 label_new_statuses_allowed: Nouveaux statuts autorisΓ©s
657 658 label_all: tous
658 659 label_any: tous
659 660 label_none: aucun
660 661 label_nobody: personne
661 662 label_next: Suivant
662 663 label_previous: PrΓ©cΓ©dent
663 664 label_used_by: UtilisΓ© par
664 665 label_details: DΓ©tails
665 666 label_add_note: Ajouter une note
666 667 label_per_page: Par page
667 668 label_calendar: Calendrier
668 669 label_months_from: mois depuis
669 670 label_gantt: Gantt
670 671 label_internal: Interne
671 672 label_last_changes: "%{count} derniers changements"
672 673 label_change_view_all: Voir tous les changements
673 674 label_personalize_page: Personnaliser cette page
674 675 label_comment: Commentaire
675 676 label_comment_plural: Commentaires
676 677 label_x_comments:
677 678 zero: aucun commentaire
678 679 one: un commentaire
679 680 other: "%{count} commentaires"
680 681 label_comment_add: Ajouter un commentaire
681 682 label_comment_added: Commentaire ajoutΓ©
682 683 label_comment_delete: Supprimer les commentaires
683 684 label_query: Rapport personnalisΓ©
684 685 label_query_plural: Rapports personnalisΓ©s
685 686 label_query_new: Nouveau rapport
686 687 label_my_queries: Mes rapports personnalisΓ©s
687 688 label_filter_add: Ajouter le filtre
688 689 label_filter_plural: Filtres
689 690 label_equals: Γ©gal
690 691 label_not_equals: diffΓ©rent
691 692 label_in_less_than: dans moins de
692 693 label_in_more_than: dans plus de
693 694 label_in_the_next_days: dans les prochains jours
694 695 label_in_the_past_days: dans les derniers jours
695 696 label_greater_or_equal: '>='
696 697 label_less_or_equal: '<='
697 698 label_between: entre
698 699 label_in: dans
699 700 label_today: aujourd'hui
700 701 label_all_time: toute la pΓ©riode
701 702 label_yesterday: hier
702 703 label_this_week: cette semaine
703 704 label_last_week: la semaine dernière
704 705 label_last_n_weeks: "les %{count} dernières semaines"
705 706 label_last_n_days: "les %{count} derniers jours"
706 707 label_this_month: ce mois-ci
707 708 label_last_month: le mois dernier
708 709 label_this_year: cette annΓ©e
709 710 label_date_range: PΓ©riode
710 711 label_less_than_ago: il y a moins de
711 712 label_more_than_ago: il y a plus de
712 713 label_ago: il y a
713 714 label_contains: contient
714 715 label_not_contains: ne contient pas
715 716 label_any_issues_in_project: une demande du projet
716 717 label_any_issues_not_in_project: une demande hors du projet
717 718 label_no_issues_in_project: aucune demande du projet
718 719 label_day_plural: jours
719 720 label_repository: DΓ©pΓ΄t
720 721 label_repository_new: Nouveau dΓ©pΓ΄t
721 722 label_repository_plural: DΓ©pΓ΄ts
722 723 label_browse: Parcourir
723 724 label_branch: Branche
724 725 label_tag: Tag
725 726 label_revision: RΓ©vision
726 727 label_revision_plural: RΓ©visions
727 728 label_revision_id: "RΓ©vision %{value}"
728 729 label_associated_revisions: RΓ©visions associΓ©es
729 730 label_added: ajoutΓ©
730 731 label_modified: modifiΓ©
731 732 label_copied: copiΓ©
732 733 label_renamed: renommΓ©
733 734 label_deleted: supprimΓ©
734 735 label_latest_revision: Dernière révision
735 736 label_latest_revision_plural: Dernières révisions
736 737 label_view_revisions: Voir les rΓ©visions
737 738 label_view_all_revisions: Voir toutes les rΓ©visions
738 739 label_max_size: Taille maximale
739 740 label_sort_highest: Remonter en premier
740 741 label_sort_higher: Remonter
741 742 label_sort_lower: Descendre
742 743 label_sort_lowest: Descendre en dernier
743 744 label_roadmap: Roadmap
744 745 label_roadmap_due_in: "Γ‰chΓ©ance dans %{value}"
745 746 label_roadmap_overdue: "En retard de %{value}"
746 747 label_roadmap_no_issues: Aucune demande pour cette version
747 748 label_search: Recherche
748 749 label_result_plural: RΓ©sultats
749 750 label_all_words: Tous les mots
750 751 label_wiki: Wiki
751 752 label_wiki_edit: RΓ©vision wiki
752 753 label_wiki_edit_plural: RΓ©visions wiki
753 754 label_wiki_page: Page wiki
754 755 label_wiki_page_plural: Pages wiki
755 756 label_index_by_title: Index par titre
756 757 label_index_by_date: Index par date
757 758 label_current_version: Version actuelle
758 759 label_preview: PrΓ©visualisation
759 760 label_feed_plural: Flux Atom
760 761 label_changes_details: DΓ©tails de tous les changements
761 762 label_issue_tracking: Suivi des demandes
762 763 label_spent_time: Temps passΓ©
763 764 label_overall_spent_time: Temps passΓ© global
764 765 label_f_hour: "%{value} heure"
765 766 label_f_hour_plural: "%{value} heures"
766 767 label_time_tracking: Suivi du temps
767 768 label_change_plural: Changements
768 769 label_statistics: Statistiques
769 770 label_commits_per_month: Commits par mois
770 771 label_commits_per_author: Commits par auteur
771 772 label_diff: diff
772 773 label_view_diff: Voir les diffΓ©rences
773 774 label_diff_inline: en ligne
774 775 label_diff_side_by_side: cΓ΄te Γ  cΓ΄te
775 776 label_options: Options
776 777 label_copy_workflow_from: Copier le workflow de
777 778 label_permissions_report: Synthèse des permissions
778 779 label_watched_issues: Demandes surveillΓ©es
779 780 label_related_issues: Demandes liΓ©es
780 781 label_applied_status: Statut appliquΓ©
781 782 label_loading: Chargement...
782 783 label_relation_new: Nouvelle relation
783 784 label_relation_delete: Supprimer la relation
784 785 label_relates_to: LiΓ© Γ 
785 786 label_duplicates: Duplique
786 787 label_duplicated_by: DupliquΓ© par
787 788 label_blocks: Bloque
788 789 label_blocked_by: BloquΓ© par
789 790 label_precedes: Précède
790 791 label_follows: Suit
791 792 label_copied_to: CopiΓ© vers
792 793 label_copied_from: CopiΓ© depuis
793 794 label_end_to_start: fin Γ  dΓ©but
794 795 label_end_to_end: fin Γ  fin
795 796 label_start_to_start: dΓ©but Γ  dΓ©but
796 797 label_start_to_end: dΓ©but Γ  fin
797 798 label_stay_logged_in: Rester connectΓ©
798 799 label_disabled: dΓ©sactivΓ©
799 800 label_show_completed_versions: Voir les versions passΓ©es
800 801 label_me: moi
801 802 label_board: Forum
802 803 label_board_new: Nouveau forum
803 804 label_board_plural: Forums
804 805 label_board_locked: VerrouillΓ©
805 806 label_board_sticky: Sticky
806 807 label_topic_plural: Discussions
807 808 label_message_plural: Messages
808 809 label_message_last: Dernier message
809 810 label_message_new: Nouveau message
810 811 label_message_posted: Message ajoutΓ©
811 812 label_reply_plural: RΓ©ponses
812 813 label_send_information: Envoyer les informations Γ  l'utilisateur
813 814 label_year: AnnΓ©e
814 815 label_month: Mois
815 816 label_week: Semaine
816 817 label_date_from: Du
817 818 label_date_to: Au
818 819 label_language_based: BasΓ© sur la langue de l'utilisateur
819 820 label_sort_by: "Trier par %{value}"
820 821 label_send_test_email: Envoyer un email de test
821 822 label_feeds_access_key: Clé d'accès Atom
822 823 label_missing_feeds_access_key: Clé d'accès Atom manquante
823 824 label_feeds_access_key_created_on: "Clé d'accès Atom créée il y a %{value}"
824 825 label_module_plural: Modules
825 826 label_added_time_by: "AjoutΓ© par %{author} il y a %{age}"
826 827 label_updated_time_by: "Mis Γ  jour par %{author} il y a %{age}"
827 828 label_updated_time: "Mis Γ  jour il y a %{value}"
828 829 label_jump_to_a_project: Aller Γ  un projet...
829 830 label_file_plural: Fichiers
830 831 label_changeset_plural: RΓ©visions
831 832 label_default_columns: Colonnes par dΓ©faut
832 833 label_no_change_option: (Pas de changement)
833 834 label_bulk_edit_selected_issues: Modifier les demandes sΓ©lectionnΓ©es
834 835 label_bulk_edit_selected_time_entries: Modifier les temps passΓ©s sΓ©lectionnΓ©s
835 836 label_theme: Thème
836 837 label_default: DΓ©faut
837 838 label_search_titles_only: Uniquement dans les titres
838 839 label_user_mail_option_all: "Pour tous les Γ©vΓ©nements de tous mes projets"
839 840 label_user_mail_option_selected: "Pour tous les Γ©vΓ©nements des projets sΓ©lectionnΓ©s..."
840 841 label_user_mail_option_none: Aucune notification
841 842 label_user_mail_option_only_my_events: Seulement pour ce que je surveille
842 843 label_user_mail_option_only_assigned: Seulement pour ce qui m'est assignΓ©
843 844 label_user_mail_option_only_owner: Seulement pour ce que j'ai créé
844 845 label_user_mail_no_self_notified: "Je ne veux pas Γͺtre notifiΓ© des changements que j'effectue"
845 846 label_registration_activation_by_email: activation du compte par email
846 847 label_registration_manual_activation: activation manuelle du compte
847 848 label_registration_automatic_activation: activation automatique du compte
848 849 label_display_per_page: "Par page : %{value}"
849 850 label_age: Γ‚ge
850 851 label_change_properties: Changer les propriΓ©tΓ©s
851 852 label_general: GΓ©nΓ©ral
852 853 label_more: Plus
853 854 label_scm: SCM
854 855 label_plugins: Plugins
855 856 label_ldap_authentication: Authentification LDAP
856 857 label_downloads_abbr: D/L
857 858 label_optional_description: Description facultative
858 859 label_add_another_file: Ajouter un autre fichier
859 860 label_preferences: PrΓ©fΓ©rences
860 861 label_chronological_order: Dans l'ordre chronologique
861 862 label_reverse_chronological_order: Dans l'ordre chronologique inverse
862 863 label_planning: Planning
863 864 label_incoming_emails: Emails entrants
864 865 label_generate_key: GΓ©nΓ©rer une clΓ©
865 866 label_issue_watchers: Observateurs
866 867 label_example: Exemple
867 868 label_display: Affichage
868 869 label_sort: Tri
869 870 label_ascending: Croissant
870 871 label_descending: DΓ©croissant
871 872 label_date_from_to: Du %{start} au %{end}
872 873 label_wiki_content_added: Page wiki ajoutΓ©e
873 874 label_wiki_content_updated: Page wiki mise Γ  jour
874 875 label_group: Groupe
875 876 label_group_plural: Groupes
876 877 label_group_new: Nouveau groupe
877 878 label_group_anonymous: Utilisateurs anonymes
878 879 label_group_non_member: Utilisateurs non membres
879 880 label_time_entry_plural: Temps passΓ©
880 881 label_version_sharing_none: Non partagΓ©
881 882 label_version_sharing_descendants: Avec les sous-projets
882 883 label_version_sharing_hierarchy: Avec toute la hiΓ©rarchie
883 884 label_version_sharing_tree: Avec tout l'arbre
884 885 label_version_sharing_system: Avec tous les projets
885 886 label_update_issue_done_ratios: Mettre Γ  jour l'avancement des demandes
886 887 label_copy_source: Source
887 888 label_copy_target: Cible
888 889 label_copy_same_as_target: Comme la cible
889 890 label_display_used_statuses_only: N'afficher que les statuts utilisΓ©s dans ce tracker
890 891 label_api_access_key: Clé d'accès API
891 892 label_missing_api_access_key: Clé d'accès API manquante
892 893 label_api_access_key_created_on: Clé d'accès API créée il y a %{value}
893 894 label_profile: Profil
894 895 label_subtask_plural: Sous-tΓ’ches
895 896 label_project_copy_notifications: Envoyer les notifications durant la copie du projet
896 897 label_principal_search: "Rechercher un utilisateur ou un groupe :"
897 898 label_user_search: "Rechercher un utilisateur :"
898 899 label_additional_workflow_transitions_for_author: Autorisations supplémentaires lorsque l'utilisateur a créé la demande
899 900 label_additional_workflow_transitions_for_assignee: Autorisations supplΓ©mentaires lorsque la demande est assignΓ©e Γ  l'utilisateur
900 901 label_issues_visibility_all: Toutes les demandes
901 902 label_issues_visibility_public: Toutes les demandes non privΓ©es
902 903 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
903 904 label_git_report_last_commit: Afficher le dernier commit des fichiers et rΓ©pertoires
904 905 label_parent_revision: Parent
905 906 label_child_revision: Enfant
906 907 label_export_options: Options d'exportation %{export_format}
907 908 label_copy_attachments: Copier les fichiers
908 909 label_copy_subtasks: Copier les sous-tΓ’ches
909 910 label_item_position: "%{position} sur %{count}"
910 911 label_completed_versions: Versions passΓ©es
911 912 label_search_for_watchers: Rechercher des observateurs
912 913 label_session_expiration: Expiration des sessions
913 914 label_show_closed_projects: Voir les projets fermΓ©s
914 915 label_status_transitions: Changements de statut
915 916 label_fields_permissions: Permissions sur les champs
916 917 label_readonly: Lecture
917 918 label_required: Obligatoire
918 919 label_hidden: CachΓ©
919 920 label_attribute_of_project: "%{name} du projet"
920 921 label_attribute_of_issue: "%{name} de la demande"
921 922 label_attribute_of_author: "%{name} de l'auteur"
922 923 label_attribute_of_assigned_to: "%{name} de l'assignΓ©"
923 924 label_attribute_of_user: "%{name} de l'utilisateur"
924 925 label_attribute_of_fixed_version: "%{name} de la version cible"
925 926 label_cross_project_descendants: Avec les sous-projets
926 927 label_cross_project_tree: Avec tout l'arbre
927 928 label_cross_project_hierarchy: Avec toute la hiΓ©rarchie
928 929 label_cross_project_system: Avec tous les projets
929 930 label_gantt_progress_line: Ligne de progression
930 931 label_visibility_private: par moi uniquement
931 932 label_visibility_roles: par ces rΓ΄les uniquement
932 933 label_visibility_public: par tout le monde
933 934 label_link: Lien
934 935 label_only: seulement
935 936 label_drop_down_list: liste dΓ©roulante
936 937 label_checkboxes: cases Γ  cocher
937 938 label_radio_buttons: boutons radio
938 939 label_link_values_to: Lier les valeurs vers l'URL
939 940 label_custom_field_select_type: Selectionner le type d'objet auquel attacher le champ personnalisΓ©
940 941 label_check_for_updates: VΓ©rifier les mises Γ  jour
941 942 label_latest_compatible_version: Dernière version compatible
942 943 label_unknown_plugin: Plugin inconnu
943 944 label_add_projects: Ajouter des projets
944 945 label_users_visibility_all: Tous les utilisateurs actifs
945 946 label_users_visibility_members_of_visible_projects: Membres des projets visibles
946 947 label_edit_attachments: Modifier les fichiers attachΓ©s
948 label_link_copied_issue: Lier la demande copiΓ©e
949 label_ask: Demander
947 950
948 951 button_login: Connexion
949 952 button_submit: Soumettre
950 953 button_save: Sauvegarder
951 954 button_check_all: Tout cocher
952 955 button_uncheck_all: Tout dΓ©cocher
953 956 button_collapse_all: Plier tout
954 957 button_expand_all: DΓ©plier tout
955 958 button_delete: Supprimer
956 959 button_create: CrΓ©er
957 960 button_create_and_continue: CrΓ©er et continuer
958 961 button_test: Tester
959 962 button_edit: Modifier
960 963 button_edit_associated_wikipage: "Modifier la page wiki associΓ©e: %{page_title}"
961 964 button_add: Ajouter
962 965 button_change: Changer
963 966 button_apply: Appliquer
964 967 button_clear: Effacer
965 968 button_lock: Verrouiller
966 969 button_unlock: DΓ©verrouiller
967 970 button_download: TΓ©lΓ©charger
968 971 button_list: Lister
969 972 button_view: Voir
970 973 button_move: DΓ©placer
971 974 button_move_and_follow: DΓ©placer et suivre
972 975 button_back: Retour
973 976 button_cancel: Annuler
974 977 button_activate: Activer
975 978 button_sort: Trier
976 979 button_log_time: Saisir temps
977 980 button_rollback: Revenir Γ  cette version
978 981 button_watch: Surveiller
979 982 button_unwatch: Ne plus surveiller
980 983 button_reply: RΓ©pondre
981 984 button_archive: Archiver
982 985 button_unarchive: DΓ©sarchiver
983 986 button_reset: RΓ©initialiser
984 987 button_rename: Renommer
985 988 button_change_password: Changer de mot de passe
986 989 button_copy: Copier
987 990 button_copy_and_follow: Copier et suivre
988 991 button_annotate: Annoter
989 992 button_update: Mettre Γ  jour
990 993 button_configure: Configurer
991 994 button_quote: Citer
992 995 button_duplicate: Dupliquer
993 996 button_show: Afficher
994 997 button_hide: Cacher
995 998 button_edit_section: Modifier cette section
996 999 button_export: Exporter
997 1000 button_delete_my_account: Supprimer mon compte
998 1001 button_close: Fermer
999 1002 button_reopen: RΓ©ouvrir
1000 1003
1001 1004 status_active: actif
1002 1005 status_registered: enregistrΓ©
1003 1006 status_locked: verrouillΓ©
1004 1007
1005 1008 project_status_active: actif
1006 1009 project_status_closed: fermΓ©
1007 1010 project_status_archived: archivΓ©
1008 1011
1009 1012 version_status_open: ouvert
1010 1013 version_status_locked: verrouillΓ©
1011 1014 version_status_closed: fermΓ©
1012 1015
1013 1016 field_active: Actif
1014 1017
1015 1018 text_select_mail_notifications: Actions pour lesquelles une notification par e-mail est envoyΓ©e
1016 1019 text_regexp_info: ex. ^[A-Z0-9]+$
1017 1020 text_min_max_length_info: 0 pour aucune restriction
1018 1021 text_project_destroy_confirmation: Êtes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
1019 1022 text_subprojects_destroy_warning: "Ses sous-projets : %{value} seront Γ©galement supprimΓ©s."
1020 1023 text_workflow_edit: SΓ©lectionner un tracker et un rΓ΄le pour Γ©diter le workflow
1021 1024 text_are_you_sure: Êtes-vous sûr ?
1022 1025 text_journal_changed: "%{label} changΓ© de %{old} Γ  %{new}"
1023 1026 text_journal_changed_no_detail: "%{label} mis Γ  jour"
1024 1027 text_journal_set_to: "%{label} mis Γ  %{value}"
1025 1028 text_journal_deleted: "%{label} %{old} supprimΓ©"
1026 1029 text_journal_added: "%{label} %{value} ajoutΓ©"
1027 1030 text_tip_issue_begin_day: tΓ’che commenΓ§ant ce jour
1028 1031 text_tip_issue_end_day: tΓ’che finissant ce jour
1029 1032 text_tip_issue_begin_end_day: tΓ’che commenΓ§ant et finissant ce jour
1030 1033 text_project_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisΓ©s, doit commencer par une minuscule.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
1031 1034 text_caracters_maximum: "%{count} caractères maximum."
1032 1035 text_caracters_minimum: "%{count} caractères minimum."
1033 1036 text_length_between: "Longueur comprise entre %{min} et %{max} caractères."
1034 1037 text_tracker_no_workflow: Aucun worflow n'est dΓ©fini pour ce tracker
1035 1038 text_unallowed_characters: Caractères non autorisés
1036 1039 text_comma_separated: Plusieurs valeurs possibles (sΓ©parΓ©es par des virgules).
1037 1040 text_line_separated: Plusieurs valeurs possibles (une valeur par ligne).
1038 1041 text_issues_ref_in_commit_messages: RΓ©fΓ©rencement et rΓ©solution des demandes dans les commentaires de commits
1039 1042 text_issue_added: "La demande %{id} a Γ©tΓ© soumise par %{author}."
1040 1043 text_issue_updated: "La demande %{id} a Γ©tΓ© mise Γ  jour par %{author}."
1041 1044 text_wiki_destroy_confirmation: Etes-vous sΓ»r de vouloir supprimer ce wiki et tout son contenu ?
1042 1045 text_issue_category_destroy_question: "%{count} demandes sont affectΓ©es Γ  cette catΓ©gorie. Que voulez-vous faire ?"
1043 1046 text_issue_category_destroy_assignments: N'affecter les demandes Γ  aucune autre catΓ©gorie
1044 1047 text_issue_category_reassign_to: RΓ©affecter les demandes Γ  cette catΓ©gorie
1045 1048 text_user_mail_option: "Pour les projets non sΓ©lectionnΓ©s, vous recevrez seulement des notifications pour ce que vous surveillez ou Γ  quoi vous participez (exemple: demandes dont vous Γͺtes l'auteur ou la personne assignΓ©e)."
1046 1049 text_no_configuration_data: "Les rΓ΄les, trackers, statuts et le workflow ne sont pas encore paramΓ©trΓ©s.\nIl est vivement recommandΓ© de charger le paramΓ©trage par defaut. Vous pourrez le modifier une fois chargΓ©."
1047 1050 text_load_default_configuration: Charger le paramΓ©trage par dΓ©faut
1048 1051 text_status_changed_by_changeset: "AppliquΓ© par commit %{value}."
1049 1052 text_time_logged_by_changeset: "AppliquΓ© par commit %{value}"
1050 1053 text_issues_destroy_confirmation: 'Êtes-vous sûr de vouloir supprimer la ou les demandes(s) selectionnée(s) ?'
1051 1054 text_issues_destroy_descendants_confirmation: "Cela entrainera Γ©galement la suppression de %{count} sous-tΓ’che(s)."
1052 1055 text_time_entries_destroy_confirmation: "Etes-vous sΓ»r de vouloir supprimer les temps passΓ©s sΓ©lectionnΓ©s ?"
1053 1056 text_select_project_modules: 'SΓ©lectionner les modules Γ  activer pour ce projet :'
1054 1057 text_default_administrator_account_changed: Compte administrateur par dΓ©faut changΓ©
1055 1058 text_file_repository_writable: RΓ©pertoire de stockage des fichiers accessible en Γ©criture
1056 1059 text_plugin_assets_writable: RΓ©pertoire public des plugins accessible en Γ©criture
1057 1060 text_rmagick_available: Bibliothèque RMagick présente (optionnelle)
1058 1061 text_convert_available: Binaire convert de ImageMagick prΓ©sent (optionel)
1059 1062 text_destroy_time_entries_question: "%{hours} heures ont Γ©tΓ© enregistrΓ©es sur les demandes Γ  supprimer. Que voulez-vous faire ?"
1060 1063 text_destroy_time_entries: Supprimer les heures
1061 1064 text_assign_time_entries_to_project: Reporter les heures sur le projet
1062 1065 text_reassign_time_entries: 'Reporter les heures sur cette demande:'
1063 1066 text_user_wrote: "%{value} a Γ©crit :"
1064 1067 text_enumeration_destroy_question: "Cette valeur est affectΓ©e Γ  %{count} objets."
1065 1068 text_enumeration_category_reassign_to: 'RΓ©affecter les objets Γ  cette valeur:'
1066 1069 text_email_delivery_not_configured: "L'envoi de mail n'est pas configurΓ©, les notifications sont dΓ©sactivΓ©es.\nConfigurez votre serveur SMTP dans config/configuration.yml et redΓ©marrez l'application pour les activer."
1067 1070 text_repository_usernames_mapping: "Vous pouvez sΓ©lectionner ou modifier l'utilisateur Redmine associΓ© Γ  chaque nom d'utilisateur figurant dans l'historique du dΓ©pΓ΄t.\nLes utilisateurs avec le mΓͺme identifiant ou la mΓͺme adresse mail seront automatiquement associΓ©s."
1068 1071 text_diff_truncated: '... Ce diffΓ©rentiel a Γ©tΓ© tronquΓ© car il excΓ¨de la taille maximale pouvant Γͺtre affichΓ©e.'
1069 1072 text_custom_field_possible_values_info: 'Une ligne par valeur'
1070 1073 text_wiki_page_destroy_question: "Cette page possède %{descendants} sous-page(s) et descendante(s). Que voulez-vous faire ?"
1071 1074 text_wiki_page_nullify_children: "Conserver les sous-pages en tant que pages racines"
1072 1075 text_wiki_page_destroy_children: "Supprimer les sous-pages et toutes leurs descedantes"
1073 1076 text_wiki_page_reassign_children: "RΓ©affecter les sous-pages Γ  cette page"
1074 1077 text_own_membership_delete_confirmation: "Vous allez supprimer tout ou partie de vos permissions sur ce projet et ne serez peut-Γͺtre plus autorisΓ© Γ  modifier ce projet.\nEtes-vous sΓ»r de vouloir continuer ?"
1075 1078 text_zoom_in: Zoom avant
1076 1079 text_zoom_out: Zoom arrière
1077 1080 text_warn_on_leaving_unsaved: "Cette page contient du texte non sauvegardΓ© qui sera perdu si vous quittez la page."
1078 1081 text_scm_path_encoding_note: "DΓ©faut : UTF-8"
1079 1082 text_subversion_repository_note: "Exemples (en fonction des protocoles supportΓ©s) : file:///, http://, https://, svn://, svn+[tunnelscheme]://"
1080 1083 text_git_repository_note: "Chemin vers un dΓ©pΓ΄t vide et local (exemples : /gitrepo, c:\\gitrepo)"
1081 1084 text_mercurial_repository_note: "Chemin vers un dΓ©pΓ΄t local (exemples : /hgrepo, c:\\hgrepo)"
1082 1085 text_scm_command: Commande
1083 1086 text_scm_command_version: Version
1084 1087 text_scm_config: Vous pouvez configurer les commandes des SCM dans config/configuration.yml. Redémarrer l'application après modification.
1085 1088 text_scm_command_not_available: Ce SCM n'est pas disponible. Vérifier les paramètres dans la section administration.
1086 1089 text_issue_conflict_resolution_overwrite: "Appliquer quand mΓͺme ma mise Γ  jour (les notes prΓ©cΓ©dentes seront conservΓ©es mais des changements pourront Γͺtre Γ©crasΓ©s)"
1087 1090 text_issue_conflict_resolution_add_notes: "Ajouter mes notes et ignorer mes autres changements"
1088 1091 text_issue_conflict_resolution_cancel: "Annuler ma mise Γ  jour et rΓ©afficher %{link}"
1089 1092 text_account_destroy_confirmation: "Êtes-vous sûr de vouloir continuer ?\nVotre compte sera définitivement supprimé, sans aucune possibilité de le réactiver."
1090 1093 text_session_expiration_settings: "Attention : le changement de ces paramètres peut entrainer l'expiration des sessions utilisateurs en cours, y compris la vôtre."
1091 1094 text_project_closed: Ce projet est fermΓ© et accessible en lecture seule.
1092 1095 text_turning_multiple_off: "Si vous dΓ©sactivez les valeurs multiples, les valeurs multiples seront supprimΓ©es pour n'en conserver qu'une par objet."
1093 1096
1094 1097 default_role_manager: Manager
1095 1098 default_role_developer: DΓ©veloppeur
1096 1099 default_role_reporter: Rapporteur
1097 1100 default_tracker_bug: Anomalie
1098 1101 default_tracker_feature: Evolution
1099 1102 default_tracker_support: Assistance
1100 1103 default_issue_status_new: Nouveau
1101 1104 default_issue_status_in_progress: En cours
1102 1105 default_issue_status_resolved: RΓ©solu
1103 1106 default_issue_status_feedback: Commentaire
1104 1107 default_issue_status_closed: FermΓ©
1105 1108 default_issue_status_rejected: RejetΓ©
1106 1109 default_doc_category_user: Documentation utilisateur
1107 1110 default_doc_category_tech: Documentation technique
1108 1111 default_priority_low: Bas
1109 1112 default_priority_normal: Normal
1110 1113 default_priority_high: Haut
1111 1114 default_priority_urgent: Urgent
1112 1115 default_priority_immediate: ImmΓ©diat
1113 1116 default_activity_design: Conception
1114 1117 default_activity_development: DΓ©veloppement
1115 1118
1116 1119 enumeration_issue_priorities: PrioritΓ©s des demandes
1117 1120 enumeration_doc_categories: CatΓ©gories des documents
1118 1121 enumeration_activities: ActivitΓ©s (suivi du temps)
1119 1122 enumeration_system_activity: Activité système
1120 1123 description_filter: Filtre
1121 1124 description_search: Champ de recherche
1122 1125 description_choose_project: Projets
1123 1126 description_project_scope: Périmètre de recherche
1124 1127 description_notes: Notes
1125 1128 description_message_content: Contenu du message
1126 1129 description_query_sort_criteria_attribute: Critère de tri
1127 1130 description_query_sort_criteria_direction: Ordre de tri
1128 1131 description_user_mail_notification: Option de notification
1129 1132 description_available_columns: Colonnes disponibles
1130 1133 description_selected_columns: Colonnes sΓ©lectionnΓ©es
1131 1134 description_all_columns: Toutes les colonnes
1132 1135 description_issue_category_reassign: Choisir une catΓ©gorie
1133 1136 description_wiki_subpages_reassign: Choisir une nouvelle page parent
1134 1137 description_date_range_list: Choisir une pΓ©riode prΓ©dΓ©finie
1135 1138 description_date_range_interval: Choisir une pΓ©riode
1136 1139 description_date_from: Date de dΓ©but
1137 1140 description_date_to: Date de fin
1138 1141 text_repository_identifier_info: 'Seuls les lettres minuscules (a-z), chiffres, tirets et tirets bas sont autorisΓ©s.<br />Un fois sauvegardΓ©, l''identifiant ne pourra plus Γͺtre modifiΓ©.'
@@ -1,234 +1,236
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18
19 19 # DO NOT MODIFY THIS FILE !!!
20 20 # Settings can be defined through the application in Admin -> Settings
21 21
22 22 app_title:
23 23 default: Redmine
24 24 app_subtitle:
25 25 default: Project management
26 26 welcome_text:
27 27 default:
28 28 login_required:
29 29 default: 0
30 30 self_registration:
31 31 default: '2'
32 32 lost_password:
33 33 default: 1
34 34 unsubscribe:
35 35 default: 1
36 36 password_min_length:
37 37 format: int
38 38 default: 8
39 39 # Maximum lifetime of user sessions in minutes
40 40 session_lifetime:
41 41 format: int
42 42 default: 0
43 43 # User session timeout in minutes
44 44 session_timeout:
45 45 format: int
46 46 default: 0
47 47 attachment_max_size:
48 48 format: int
49 49 default: 5120
50 50 issues_export_limit:
51 51 format: int
52 52 default: 500
53 53 activity_days_default:
54 54 format: int
55 55 default: 30
56 56 per_page_options:
57 57 default: '25,50,100'
58 58 mail_from:
59 59 default: redmine@example.net
60 60 bcc_recipients:
61 61 default: 1
62 62 plain_text_mail:
63 63 default: 0
64 64 text_formatting:
65 65 default: textile
66 66 cache_formatted_text:
67 67 default: 0
68 68 wiki_compression:
69 69 default: ""
70 70 default_language:
71 71 default: en
72 72 force_default_language_for_anonymous:
73 73 default: 0
74 74 force_default_language_for_loggedin:
75 75 default: 0
76 76 host_name:
77 77 default: localhost:3000
78 78 protocol:
79 79 default: http
80 80 feeds_limit:
81 81 format: int
82 82 default: 15
83 83 gantt_items_limit:
84 84 format: int
85 85 default: 500
86 86 # Maximum size of files that can be displayed
87 87 # inline through the file viewer (in KB)
88 88 file_max_size_displayed:
89 89 format: int
90 90 default: 512
91 91 diff_max_lines_displayed:
92 92 format: int
93 93 default: 1500
94 94 enabled_scm:
95 95 serialized: true
96 96 default:
97 97 - Subversion
98 98 - Darcs
99 99 - Mercurial
100 100 - Cvs
101 101 - Bazaar
102 102 - Git
103 103 autofetch_changesets:
104 104 default: 1
105 105 sys_api_enabled:
106 106 default: 0
107 107 sys_api_key:
108 108 default: ''
109 109 commit_cross_project_ref:
110 110 default: 0
111 111 commit_ref_keywords:
112 112 default: 'refs,references,IssueID'
113 113 commit_update_keywords:
114 114 serialized: true
115 115 default: []
116 116 commit_logtime_enabled:
117 117 default: 0
118 118 commit_logtime_activity_id:
119 119 format: int
120 120 default: 0
121 121 # autologin duration in days
122 122 # 0 means autologin is disabled
123 123 autologin:
124 124 format: int
125 125 default: 0
126 126 # date format
127 127 date_format:
128 128 default: ''
129 129 time_format:
130 130 default: ''
131 131 user_format:
132 132 default: :firstname_lastname
133 133 format: symbol
134 134 cross_project_issue_relations:
135 135 default: 0
136 136 # Enables subtasks to be in other projects
137 137 cross_project_subtasks:
138 138 default: 'tree'
139 link_copied_issue:
140 default: 'ask'
139 141 issue_group_assignment:
140 142 default: 0
141 143 default_issue_start_date_to_creation_date:
142 144 default: 1
143 145 notified_events:
144 146 serialized: true
145 147 default:
146 148 - issue_added
147 149 - issue_updated
148 150 mail_handler_body_delimiters:
149 151 default: ''
150 152 mail_handler_excluded_filenames:
151 153 default: ''
152 154 mail_handler_api_enabled:
153 155 default: 0
154 156 mail_handler_api_key:
155 157 default:
156 158 issue_list_default_columns:
157 159 serialized: true
158 160 default:
159 161 - tracker
160 162 - status
161 163 - priority
162 164 - subject
163 165 - assigned_to
164 166 - updated_on
165 167 display_subprojects_issues:
166 168 default: 1
167 169 issue_done_ratio:
168 170 default: 'issue_field'
169 171 default_projects_public:
170 172 default: 1
171 173 default_projects_modules:
172 174 serialized: true
173 175 default:
174 176 - issue_tracking
175 177 - time_tracking
176 178 - news
177 179 - documents
178 180 - files
179 181 - wiki
180 182 - repository
181 183 - boards
182 184 - calendar
183 185 - gantt
184 186 default_projects_tracker_ids:
185 187 serialized: true
186 188 default:
187 189 # Role given to a non-admin user who creates a project
188 190 new_project_user_role_id:
189 191 format: int
190 192 default: ''
191 193 sequential_project_identifiers:
192 194 default: 0
193 195 # encodings used to convert repository files content to UTF-8
194 196 # multiple values accepted, comma separated
195 197 repositories_encodings:
196 198 default: ''
197 199 # encoding used to convert commit logs to UTF-8
198 200 commit_logs_encoding:
199 201 default: 'UTF-8'
200 202 repository_log_display_limit:
201 203 format: int
202 204 default: 100
203 205 ui_theme:
204 206 default: ''
205 207 emails_footer:
206 208 default: |-
207 209 You have received this notification because you have either subscribed to it, or are involved in it.
208 210 To change your notification preferences, please click here: http://hostname/my/account
209 211 gravatar_enabled:
210 212 default: 0
211 213 openid:
212 214 default: 0
213 215 gravatar_default:
214 216 default: ''
215 217 start_of_week:
216 218 default: ''
217 219 rest_api_enabled:
218 220 default: 0
219 221 jsonp_enabled:
220 222 default: 0
221 223 default_notification_option:
222 224 default: 'only_my_events'
223 225 emails_header:
224 226 default: ''
225 227 thumbnails_enabled:
226 228 default: 0
227 229 thumbnails_size:
228 230 format: int
229 231 default: 100
230 232 non_working_week_days:
231 233 serialized: true
232 234 default:
233 235 - '6'
234 236 - '7'
@@ -1,4051 +1,4062
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19
20 20 class IssuesControllerTest < ActionController::TestCase
21 21 fixtures :projects,
22 22 :users,
23 23 :roles,
24 24 :members,
25 25 :member_roles,
26 26 :issues,
27 27 :issue_statuses,
28 28 :issue_relations,
29 29 :versions,
30 30 :trackers,
31 31 :projects_trackers,
32 32 :issue_categories,
33 33 :enabled_modules,
34 34 :enumerations,
35 35 :attachments,
36 36 :workflows,
37 37 :custom_fields,
38 38 :custom_values,
39 39 :custom_fields_projects,
40 40 :custom_fields_trackers,
41 41 :time_entries,
42 42 :journals,
43 43 :journal_details,
44 44 :queries,
45 45 :repositories,
46 46 :changesets
47 47
48 48 include Redmine::I18n
49 49
50 50 def setup
51 51 User.current = nil
52 52 end
53 53
54 54 def test_index
55 55 with_settings :default_language => "en" do
56 56 get :index
57 57 assert_response :success
58 58 assert_template 'index'
59 59 assert_not_nil assigns(:issues)
60 60 assert_nil assigns(:project)
61 61
62 62 # links to visible issues
63 63 assert_select 'a[href="/issues/1"]', :text => /#{ESCAPED_UCANT} print recipes/
64 64 assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
65 65 # private projects hidden
66 66 assert_select 'a[href="/issues/6"]', 0
67 67 assert_select 'a[href="/issues/4"]', 0
68 68 # project column
69 69 assert_select 'th', :text => /Project/
70 70 end
71 71 end
72 72
73 73 def test_index_should_not_list_issues_when_module_disabled
74 74 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
75 75 get :index
76 76 assert_response :success
77 77 assert_template 'index'
78 78 assert_not_nil assigns(:issues)
79 79 assert_nil assigns(:project)
80 80
81 81 assert_select 'a[href="/issues/1"]', 0
82 82 assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
83 83 end
84 84
85 85 def test_index_should_list_visible_issues_only
86 86 get :index, :per_page => 100
87 87 assert_response :success
88 88 assert_not_nil assigns(:issues)
89 89 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
90 90 end
91 91
92 92 def test_index_with_project
93 93 Setting.display_subprojects_issues = 0
94 94 get :index, :project_id => 1
95 95 assert_response :success
96 96 assert_template 'index'
97 97 assert_not_nil assigns(:issues)
98 98
99 99 assert_select 'a[href="/issues/1"]', :text => /#{ESCAPED_UCANT} print recipes/
100 100 assert_select 'a[href="/issues/5"]', 0
101 101 end
102 102
103 103 def test_index_with_project_and_subprojects
104 104 Setting.display_subprojects_issues = 1
105 105 get :index, :project_id => 1
106 106 assert_response :success
107 107 assert_template 'index'
108 108 assert_not_nil assigns(:issues)
109 109
110 110 assert_select 'a[href="/issues/1"]', :text => /#{ESCAPED_UCANT} print recipes/
111 111 assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
112 112 assert_select 'a[href="/issues/6"]', 0
113 113 end
114 114
115 115 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
116 116 @request.session[:user_id] = 2
117 117 Setting.display_subprojects_issues = 1
118 118 get :index, :project_id => 1
119 119 assert_response :success
120 120 assert_template 'index'
121 121 assert_not_nil assigns(:issues)
122 122
123 123 assert_select 'a[href="/issues/1"]', :text => /#{ESCAPED_UCANT} print recipes/
124 124 assert_select 'a[href="/issues/5"]', :text => /Subproject issue/
125 125 assert_select 'a[href="/issues/6"]', :text => /Issue of a private subproject/
126 126 end
127 127
128 128 def test_index_with_project_and_default_filter
129 129 get :index, :project_id => 1, :set_filter => 1
130 130 assert_response :success
131 131 assert_template 'index'
132 132 assert_not_nil assigns(:issues)
133 133
134 134 query = assigns(:query)
135 135 assert_not_nil query
136 136 # default filter
137 137 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
138 138 end
139 139
140 140 def test_index_with_project_and_filter
141 141 get :index, :project_id => 1, :set_filter => 1,
142 142 :f => ['tracker_id'],
143 143 :op => {'tracker_id' => '='},
144 144 :v => {'tracker_id' => ['1']}
145 145 assert_response :success
146 146 assert_template 'index'
147 147 assert_not_nil assigns(:issues)
148 148
149 149 query = assigns(:query)
150 150 assert_not_nil query
151 151 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
152 152 end
153 153
154 154 def test_index_with_short_filters
155 155 to_test = {
156 156 'status_id' => {
157 157 'o' => { :op => 'o', :values => [''] },
158 158 'c' => { :op => 'c', :values => [''] },
159 159 '7' => { :op => '=', :values => ['7'] },
160 160 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
161 161 '=7' => { :op => '=', :values => ['7'] },
162 162 '!3' => { :op => '!', :values => ['3'] },
163 163 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
164 164 'subject' => {
165 165 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
166 166 'o' => { :op => '=', :values => ['o'] },
167 167 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
168 168 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
169 169 'tracker_id' => {
170 170 '3' => { :op => '=', :values => ['3'] },
171 171 '=3' => { :op => '=', :values => ['3'] }},
172 172 'start_date' => {
173 173 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
174 174 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
175 175 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
176 176 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
177 177 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
178 178 '<t+2' => { :op => '<t+', :values => ['2'] },
179 179 '>t+2' => { :op => '>t+', :values => ['2'] },
180 180 't+2' => { :op => 't+', :values => ['2'] },
181 181 't' => { :op => 't', :values => [''] },
182 182 'w' => { :op => 'w', :values => [''] },
183 183 '>t-2' => { :op => '>t-', :values => ['2'] },
184 184 '<t-2' => { :op => '<t-', :values => ['2'] },
185 185 't-2' => { :op => 't-', :values => ['2'] }},
186 186 'created_on' => {
187 187 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
188 188 '<t-2' => { :op => '<t-', :values => ['2'] },
189 189 '>t-2' => { :op => '>t-', :values => ['2'] },
190 190 't-2' => { :op => 't-', :values => ['2'] }},
191 191 'cf_1' => {
192 192 'c' => { :op => '=', :values => ['c'] },
193 193 '!c' => { :op => '!', :values => ['c'] },
194 194 '!*' => { :op => '!*', :values => [''] },
195 195 '*' => { :op => '*', :values => [''] }},
196 196 'estimated_hours' => {
197 197 '=13.4' => { :op => '=', :values => ['13.4'] },
198 198 '>=45' => { :op => '>=', :values => ['45'] },
199 199 '<=125' => { :op => '<=', :values => ['125'] },
200 200 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
201 201 '!*' => { :op => '!*', :values => [''] },
202 202 '*' => { :op => '*', :values => [''] }}
203 203 }
204 204
205 205 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
206 206
207 207 to_test.each do |field, expression_and_expected|
208 208 expression_and_expected.each do |filter_expression, expected|
209 209
210 210 get :index, :set_filter => 1, field => filter_expression
211 211
212 212 assert_response :success
213 213 assert_template 'index'
214 214 assert_not_nil assigns(:issues)
215 215
216 216 query = assigns(:query)
217 217 assert_not_nil query
218 218 assert query.has_filter?(field)
219 219 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
220 220 end
221 221 end
222 222 end
223 223
224 224 def test_index_with_project_and_empty_filters
225 225 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
226 226 assert_response :success
227 227 assert_template 'index'
228 228 assert_not_nil assigns(:issues)
229 229
230 230 query = assigns(:query)
231 231 assert_not_nil query
232 232 # no filter
233 233 assert_equal({}, query.filters)
234 234 end
235 235
236 236 def test_index_with_project_custom_field_filter
237 237 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
238 238 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
239 239 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
240 240 filter_name = "project.cf_#{field.id}"
241 241 @request.session[:user_id] = 1
242 242
243 243 get :index, :set_filter => 1,
244 244 :f => [filter_name],
245 245 :op => {filter_name => '='},
246 246 :v => {filter_name => ['Foo']}
247 247 assert_response :success
248 248 assert_template 'index'
249 249 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
250 250 end
251 251
252 252 def test_index_with_query
253 253 get :index, :project_id => 1, :query_id => 5
254 254 assert_response :success
255 255 assert_template 'index'
256 256 assert_not_nil assigns(:issues)
257 257 assert_nil assigns(:issue_count_by_group)
258 258 end
259 259
260 260 def test_index_with_query_grouped_by_tracker
261 261 get :index, :project_id => 1, :query_id => 6
262 262 assert_response :success
263 263 assert_template 'index'
264 264 assert_not_nil assigns(:issues)
265 265 assert_not_nil assigns(:issue_count_by_group)
266 266 end
267 267
268 268 def test_index_with_query_grouped_by_list_custom_field
269 269 get :index, :project_id => 1, :query_id => 9
270 270 assert_response :success
271 271 assert_template 'index'
272 272 assert_not_nil assigns(:issues)
273 273 assert_not_nil assigns(:issue_count_by_group)
274 274 end
275 275
276 276 def test_index_with_query_grouped_by_user_custom_field
277 277 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
278 278 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
279 279 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
280 280 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
281 281 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
282 282
283 283 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
284 284 assert_response :success
285 285
286 286 assert_select 'tr.group', 3
287 287 assert_select 'tr.group' do
288 288 assert_select 'a', :text => 'John Smith'
289 289 assert_select 'span.count', :text => '1'
290 290 end
291 291 assert_select 'tr.group' do
292 292 assert_select 'a', :text => 'Dave Lopper'
293 293 assert_select 'span.count', :text => '2'
294 294 end
295 295 end
296 296
297 297 def test_index_with_query_grouped_by_tracker_in_normal_order
298 298 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
299 299
300 300 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc'
301 301 assert_response :success
302 302
303 303 trackers = assigns(:issues).map(&:tracker).uniq
304 304 assert_equal [1, 2, 3], trackers.map(&:id)
305 305 end
306 306
307 307 def test_index_with_query_grouped_by_tracker_in_reverse_order
308 308 3.times {|i| Issue.generate!(:tracker_id => (i + 1))}
309 309
310 310 get :index, :set_filter => 1, :group_by => 'tracker', :sort => 'id:desc,tracker:desc'
311 311 assert_response :success
312 312
313 313 trackers = assigns(:issues).map(&:tracker).uniq
314 314 assert_equal [3, 2, 1], trackers.map(&:id)
315 315 end
316 316
317 317 def test_index_with_query_id_and_project_id_should_set_session_query
318 318 get :index, :project_id => 1, :query_id => 4
319 319 assert_response :success
320 320 assert_kind_of Hash, session[:query]
321 321 assert_equal 4, session[:query][:id]
322 322 assert_equal 1, session[:query][:project_id]
323 323 end
324 324
325 325 def test_index_with_invalid_query_id_should_respond_404
326 326 get :index, :project_id => 1, :query_id => 999
327 327 assert_response 404
328 328 end
329 329
330 330 def test_index_with_cross_project_query_in_session_should_show_project_issues
331 331 q = IssueQuery.create!(:name => "test", :user_id => 2, :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
332 332 @request.session[:query] = {:id => q.id, :project_id => 1}
333 333
334 334 with_settings :display_subprojects_issues => '0' do
335 335 get :index, :project_id => 1
336 336 end
337 337 assert_response :success
338 338 assert_not_nil assigns(:query)
339 339 assert_equal q.id, assigns(:query).id
340 340 assert_equal 1, assigns(:query).project_id
341 341 assert_equal [1], assigns(:issues).map(&:project_id).uniq
342 342 end
343 343
344 344 def test_private_query_should_not_be_available_to_other_users
345 345 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
346 346 @request.session[:user_id] = 3
347 347
348 348 get :index, :query_id => q.id
349 349 assert_response 403
350 350 end
351 351
352 352 def test_private_query_should_be_available_to_its_user
353 353 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PRIVATE, :project => nil)
354 354 @request.session[:user_id] = 2
355 355
356 356 get :index, :query_id => q.id
357 357 assert_response :success
358 358 end
359 359
360 360 def test_public_query_should_be_available_to_other_users
361 361 q = IssueQuery.create!(:name => "private", :user => User.find(2), :visibility => IssueQuery::VISIBILITY_PUBLIC, :project => nil)
362 362 @request.session[:user_id] = 3
363 363
364 364 get :index, :query_id => q.id
365 365 assert_response :success
366 366 end
367 367
368 368 def test_index_should_omit_page_param_in_export_links
369 369 get :index, :page => 2
370 370 assert_response :success
371 371 assert_select 'a.atom[href="/issues.atom"]'
372 372 assert_select 'a.csv[href="/issues.csv"]'
373 373 assert_select 'a.pdf[href="/issues.pdf"]'
374 374 assert_select 'form#csv-export-form[action=/issues.csv]'
375 375 end
376 376
377 377 def test_index_should_not_warn_when_not_exceeding_export_limit
378 378 with_settings :issues_export_limit => 200 do
379 379 get :index
380 380 assert_select '#csv-export-options p.icon-warning', 0
381 381 end
382 382 end
383 383
384 384 def test_index_should_warn_when_exceeding_export_limit
385 385 with_settings :issues_export_limit => 2 do
386 386 get :index
387 387 assert_select '#csv-export-options p.icon-warning', :text => %r{limit: 2}
388 388 end
389 389 end
390 390
391 391 def test_index_csv
392 392 get :index, :format => 'csv'
393 393 assert_response :success
394 394 assert_not_nil assigns(:issues)
395 395 assert_equal 'text/csv; header=present', @response.content_type
396 396 assert @response.body.starts_with?("#,")
397 397 lines = @response.body.chomp.split("\n")
398 398 assert_equal assigns(:query).columns.size, lines[0].split(',').size
399 399 end
400 400
401 401 def test_index_csv_with_project
402 402 get :index, :project_id => 1, :format => 'csv'
403 403 assert_response :success
404 404 assert_not_nil assigns(:issues)
405 405 assert_equal 'text/csv; header=present', @response.content_type
406 406 end
407 407
408 408 def test_index_csv_with_description
409 409 Issue.generate!(:description => 'test_index_csv_with_description')
410 410
411 411 with_settings :default_language => 'en' do
412 412 get :index, :format => 'csv', :description => '1'
413 413 assert_response :success
414 414 assert_not_nil assigns(:issues)
415 415 end
416 416
417 417 assert_equal 'text/csv; header=present', response.content_type
418 418 headers = response.body.chomp.split("\n").first.split(',')
419 419 assert_include 'Description', headers
420 420 assert_include 'test_index_csv_with_description', response.body
421 421 end
422 422
423 423 def test_index_csv_with_spent_time_column
424 424 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
425 425 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
426 426
427 427 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
428 428 assert_response :success
429 429 assert_equal 'text/csv; header=present', @response.content_type
430 430 lines = @response.body.chomp.split("\n")
431 431 assert_include "#{issue.id},#{issue.subject},7.33", lines
432 432 end
433 433
434 434 def test_index_csv_with_all_columns
435 435 get :index, :format => 'csv', :columns => 'all'
436 436 assert_response :success
437 437 assert_not_nil assigns(:issues)
438 438 assert_equal 'text/csv; header=present', @response.content_type
439 439 assert_match /\A#,/, response.body
440 440 lines = response.body.chomp.split("\n")
441 441 assert_equal assigns(:query).available_inline_columns.size, lines[0].split(',').size
442 442 end
443 443
444 444 def test_index_csv_with_multi_column_field
445 445 CustomField.find(1).update_attribute :multiple, true
446 446 issue = Issue.find(1)
447 447 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
448 448 issue.save!
449 449
450 450 get :index, :format => 'csv', :columns => 'all'
451 451 assert_response :success
452 452 lines = @response.body.chomp.split("\n")
453 453 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
454 454 end
455 455
456 456 def test_index_csv_should_format_float_custom_fields_with_csv_decimal_separator
457 457 field = IssueCustomField.create!(:name => 'Float', :is_for_all => true, :tracker_ids => [1], :field_format => 'float')
458 458 issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id => '185.6'})
459 459
460 460 with_settings :default_language => 'fr' do
461 461 get :index, :format => 'csv', :columns => 'all'
462 462 assert_response :success
463 463 issue_line = response.body.chomp.split("\n").map {|line| line.split(';')}.detect {|line| line[0]==issue.id.to_s}
464 464 assert_include '185,60', issue_line
465 465 end
466 466
467 467 with_settings :default_language => 'en' do
468 468 get :index, :format => 'csv', :columns => 'all'
469 469 assert_response :success
470 470 issue_line = response.body.chomp.split("\n").map {|line| line.split(',')}.detect {|line| line[0]==issue.id.to_s}
471 471 assert_include '185.60', issue_line
472 472 end
473 473 end
474 474
475 475 def test_index_csv_should_fill_parent_column_with_parent_id
476 476 Issue.delete_all
477 477 parent = Issue.generate!
478 478 child = Issue.generate!(:parent_issue_id => parent.id)
479 479
480 480 with_settings :default_language => 'en' do
481 481 get :index, :format => 'csv', :c => %w(parent)
482 482 end
483 483 lines = response.body.split("\n")
484 484 assert_include "#{child.id},#{parent.id}", lines
485 485 end
486 486
487 487 def test_index_csv_big_5
488 488 with_settings :default_language => "zh-TW" do
489 489 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88".force_encoding('UTF-8')
490 490 str_big5 = "\xa4@\xa4\xeb".force_encoding('Big5')
491 491 issue = Issue.generate!(:subject => str_utf8)
492 492
493 493 get :index, :project_id => 1,
494 494 :f => ['subject'],
495 495 :op => '=', :values => [str_utf8],
496 496 :format => 'csv'
497 497 assert_equal 'text/csv; header=present', @response.content_type
498 498 lines = @response.body.chomp.split("\n")
499 499 s1 = "\xaa\xac\xbaA".force_encoding('Big5')
500 500 assert_include s1, lines[0]
501 501 assert_include str_big5, lines[1]
502 502 end
503 503 end
504 504
505 505 def test_index_csv_cannot_convert_should_be_replaced_big_5
506 506 with_settings :default_language => "zh-TW" do
507 507 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85".force_encoding('UTF-8')
508 508 issue = Issue.generate!(:subject => str_utf8)
509 509
510 510 get :index, :project_id => 1,
511 511 :f => ['subject'],
512 512 :op => '=', :values => [str_utf8],
513 513 :c => ['status', 'subject'],
514 514 :format => 'csv',
515 515 :set_filter => 1
516 516 assert_equal 'text/csv; header=present', @response.content_type
517 517 lines = @response.body.chomp.split("\n")
518 518 s1 = "\xaa\xac\xbaA".force_encoding('Big5') # status
519 519 assert lines[0].include?(s1)
520 520 s2 = lines[1].split(",")[2]
521 521 s3 = "\xa5H?".force_encoding('Big5') # subject
522 522 assert_equal s3, s2
523 523 end
524 524 end
525 525
526 526 def test_index_csv_tw
527 527 with_settings :default_language => "zh-TW" do
528 528 str1 = "test_index_csv_tw"
529 529 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
530 530
531 531 get :index, :project_id => 1,
532 532 :f => ['subject'],
533 533 :op => '=', :values => [str1],
534 534 :c => ['estimated_hours', 'subject'],
535 535 :format => 'csv',
536 536 :set_filter => 1
537 537 assert_equal 'text/csv; header=present', @response.content_type
538 538 lines = @response.body.chomp.split("\n")
539 539 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
540 540 end
541 541 end
542 542
543 543 def test_index_csv_fr
544 544 with_settings :default_language => "fr" do
545 545 str1 = "test_index_csv_fr"
546 546 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
547 547
548 548 get :index, :project_id => 1,
549 549 :f => ['subject'],
550 550 :op => '=', :values => [str1],
551 551 :c => ['estimated_hours', 'subject'],
552 552 :format => 'csv',
553 553 :set_filter => 1
554 554 assert_equal 'text/csv; header=present', @response.content_type
555 555 lines = @response.body.chomp.split("\n")
556 556 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
557 557 end
558 558 end
559 559
560 560 def test_index_pdf
561 561 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
562 562 with_settings :default_language => lang do
563 563
564 564 get :index
565 565 assert_response :success
566 566 assert_template 'index'
567 567
568 568 get :index, :format => 'pdf'
569 569 assert_response :success
570 570 assert_not_nil assigns(:issues)
571 571 assert_equal 'application/pdf', @response.content_type
572 572
573 573 get :index, :project_id => 1, :format => 'pdf'
574 574 assert_response :success
575 575 assert_not_nil assigns(:issues)
576 576 assert_equal 'application/pdf', @response.content_type
577 577
578 578 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
579 579 assert_response :success
580 580 assert_not_nil assigns(:issues)
581 581 assert_equal 'application/pdf', @response.content_type
582 582 end
583 583 end
584 584 end
585 585
586 586 def test_index_pdf_with_query_grouped_by_list_custom_field
587 587 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
588 588 assert_response :success
589 589 assert_not_nil assigns(:issues)
590 590 assert_not_nil assigns(:issue_count_by_group)
591 591 assert_equal 'application/pdf', @response.content_type
592 592 end
593 593
594 594 def test_index_atom
595 595 get :index, :project_id => 'ecookbook', :format => 'atom'
596 596 assert_response :success
597 597 assert_template 'common/feed'
598 598 assert_equal 'application/atom+xml', response.content_type
599 599
600 600 assert_select 'feed' do
601 601 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
602 602 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
603 603 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
604 604 end
605 605 end
606 606
607 607 def test_index_sort
608 608 get :index, :sort => 'tracker,id:desc'
609 609 assert_response :success
610 610
611 611 sort_params = @request.session['issues_index_sort']
612 612 assert sort_params.is_a?(String)
613 613 assert_equal 'tracker,id:desc', sort_params
614 614
615 615 issues = assigns(:issues)
616 616 assert_not_nil issues
617 617 assert !issues.empty?
618 618 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
619 619 assert_select 'table.issues.sort-by-tracker.sort-asc'
620 620 end
621 621
622 622 def test_index_sort_by_field_not_included_in_columns
623 623 Setting.issue_list_default_columns = %w(subject author)
624 624 get :index, :sort => 'tracker'
625 625 end
626 626
627 627 def test_index_sort_by_assigned_to
628 628 get :index, :sort => 'assigned_to'
629 629 assert_response :success
630 630 assignees = assigns(:issues).collect(&:assigned_to).compact
631 631 assert_equal assignees.sort, assignees
632 632 assert_select 'table.issues.sort-by-assigned-to.sort-asc'
633 633 end
634 634
635 635 def test_index_sort_by_assigned_to_desc
636 636 get :index, :sort => 'assigned_to:desc'
637 637 assert_response :success
638 638 assignees = assigns(:issues).collect(&:assigned_to).compact
639 639 assert_equal assignees.sort.reverse, assignees
640 640 assert_select 'table.issues.sort-by-assigned-to.sort-desc'
641 641 end
642 642
643 643 def test_index_group_by_assigned_to
644 644 get :index, :group_by => 'assigned_to', :sort => 'priority'
645 645 assert_response :success
646 646 end
647 647
648 648 def test_index_sort_by_author
649 649 get :index, :sort => 'author'
650 650 assert_response :success
651 651 authors = assigns(:issues).collect(&:author)
652 652 assert_equal authors.sort, authors
653 653 end
654 654
655 655 def test_index_sort_by_author_desc
656 656 get :index, :sort => 'author:desc'
657 657 assert_response :success
658 658 authors = assigns(:issues).collect(&:author)
659 659 assert_equal authors.sort.reverse, authors
660 660 end
661 661
662 662 def test_index_group_by_author
663 663 get :index, :group_by => 'author', :sort => 'priority'
664 664 assert_response :success
665 665 end
666 666
667 667 def test_index_sort_by_spent_hours
668 668 get :index, :sort => 'spent_hours:desc'
669 669 assert_response :success
670 670 hours = assigns(:issues).collect(&:spent_hours)
671 671 assert_equal hours.sort.reverse, hours
672 672 end
673 673
674 674 def test_index_sort_by_user_custom_field
675 675 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
676 676 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
677 677 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
678 678 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
679 679 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
680 680
681 681 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
682 682 assert_response :success
683 683
684 684 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
685 685 end
686 686
687 687 def test_index_with_columns
688 688 columns = ['tracker', 'subject', 'assigned_to']
689 689 get :index, :set_filter => 1, :c => columns
690 690 assert_response :success
691 691
692 692 # query should use specified columns
693 693 query = assigns(:query)
694 694 assert_kind_of IssueQuery, query
695 695 assert_equal columns, query.column_names.map(&:to_s)
696 696
697 697 # columns should be stored in session
698 698 assert_kind_of Hash, session[:query]
699 699 assert_kind_of Array, session[:query][:column_names]
700 700 assert_equal columns, session[:query][:column_names].map(&:to_s)
701 701
702 702 # ensure only these columns are kept in the selected columns list
703 703 assert_select 'select#selected_columns option' do
704 704 assert_select 'option', 3
705 705 assert_select 'option[value=tracker]'
706 706 assert_select 'option[value=project]', 0
707 707 end
708 708 end
709 709
710 710 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
711 711 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
712 712 get :index, :set_filter => 1
713 713
714 714 # query should use specified columns
715 715 query = assigns(:query)
716 716 assert_kind_of IssueQuery, query
717 717 assert_equal [:id, :project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
718 718 end
719 719
720 720 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
721 721 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
722 722 columns = ['id', 'tracker', 'subject', 'assigned_to']
723 723 get :index, :set_filter => 1, :c => columns
724 724
725 725 # query should use specified columns
726 726 query = assigns(:query)
727 727 assert_kind_of IssueQuery, query
728 728 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
729 729 end
730 730
731 731 def test_index_with_default_columns_should_respect_default_columns_order
732 732 columns = ['assigned_to', 'subject', 'status', 'tracker']
733 733 with_settings :issue_list_default_columns => columns do
734 734 get :index, :project_id => 1, :set_filter => 1
735 735
736 736 query = assigns(:query)
737 737 assert_equal (['id'] + columns).map(&:to_sym), query.columns.map(&:name)
738 738 end
739 739 end
740 740
741 741 def test_index_with_custom_field_column
742 742 columns = %w(tracker subject cf_2)
743 743 get :index, :set_filter => 1, :c => columns
744 744 assert_response :success
745 745
746 746 # query should use specified columns
747 747 query = assigns(:query)
748 748 assert_kind_of IssueQuery, query
749 749 assert_equal columns, query.column_names.map(&:to_s)
750 750
751 751 assert_select 'table.issues td.cf_2.string'
752 752 end
753 753
754 754 def test_index_with_multi_custom_field_column
755 755 field = CustomField.find(1)
756 756 field.update_attribute :multiple, true
757 757 issue = Issue.find(1)
758 758 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
759 759 issue.save!
760 760
761 761 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
762 762 assert_response :success
763 763
764 764 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
765 765 end
766 766
767 767 def test_index_with_multi_user_custom_field_column
768 768 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
769 769 :tracker_ids => [1], :is_for_all => true)
770 770 issue = Issue.find(1)
771 771 issue.custom_field_values = {field.id => ['2', '3']}
772 772 issue.save!
773 773
774 774 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
775 775 assert_response :success
776 776
777 777 assert_select "table.issues td.cf_#{field.id}" do
778 778 assert_select 'a', 2
779 779 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
780 780 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
781 781 end
782 782 end
783 783
784 784 def test_index_with_date_column
785 785 with_settings :date_format => '%d/%m/%Y' do
786 786 Issue.find(1).update_attribute :start_date, '1987-08-24'
787 787 get :index, :set_filter => 1, :c => %w(start_date)
788 788 assert_select "table.issues td.start_date", :text => '24/08/1987'
789 789 end
790 790 end
791 791
792 792 def test_index_with_done_ratio_column
793 793 Issue.find(1).update_attribute :done_ratio, 40
794 794 get :index, :set_filter => 1, :c => %w(done_ratio)
795 795 assert_select 'table.issues td.done_ratio' do
796 796 assert_select 'table.progress' do
797 797 assert_select 'td.closed[style=?]', 'width: 40%;'
798 798 end
799 799 end
800 800 end
801 801
802 802 def test_index_with_spent_hours_column
803 803 get :index, :set_filter => 1, :c => %w(subject spent_hours)
804 804 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
805 805 end
806 806
807 807 def test_index_should_not_show_spent_hours_column_without_permission
808 808 Role.anonymous.remove_permission! :view_time_entries
809 809 get :index, :set_filter => 1, :c => %w(subject spent_hours)
810 810 assert_select 'td.spent_hours', 0
811 811 end
812 812
813 813 def test_index_with_fixed_version_column
814 814 get :index, :set_filter => 1, :c => %w(fixed_version)
815 815 assert_select 'table.issues td.fixed_version' do
816 816 assert_select 'a[href=?]', '/versions/2', :text => '1.0'
817 817 end
818 818 end
819 819
820 820 def test_index_with_relations_column
821 821 IssueRelation.delete_all
822 822 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
823 823 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
824 824 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
825 825 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
826 826
827 827 get :index, :set_filter => 1, :c => %w(subject relations)
828 828 assert_response :success
829 829 assert_select "tr#issue-1 td.relations" do
830 830 assert_select "span", 3
831 831 assert_select "span", :text => "Related to #7"
832 832 assert_select "span", :text => "Related to #8"
833 833 assert_select "span", :text => "Blocks #11"
834 834 end
835 835 assert_select "tr#issue-2 td.relations" do
836 836 assert_select "span", 1
837 837 assert_select "span", :text => "Blocked by #12"
838 838 end
839 839 assert_select "tr#issue-3 td.relations" do
840 840 assert_select "span", 0
841 841 end
842 842
843 843 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
844 844 assert_response :success
845 845 assert_equal 'text/csv; header=present', response.content_type
846 846 lines = response.body.chomp.split("\n")
847 847 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
848 848 assert_include '2,Blocked by #12', lines
849 849 assert_include '3,""', lines
850 850
851 851 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
852 852 assert_response :success
853 853 assert_equal 'application/pdf', response.content_type
854 854 end
855 855
856 856 def test_index_with_description_column
857 857 get :index, :set_filter => 1, :c => %w(subject description)
858 858
859 859 assert_select 'table.issues thead th', 3 # columns: chekbox + id + subject
860 860 assert_select 'td.description[colspan=3]', :text => 'Unable to print recipes'
861 861
862 862 get :index, :set_filter => 1, :c => %w(subject description), :format => 'pdf'
863 863 assert_response :success
864 864 assert_equal 'application/pdf', response.content_type
865 865 end
866 866
867 867 def test_index_with_parent_column
868 868 Issue.delete_all
869 869 parent = Issue.generate!
870 870 child = Issue.generate!(:parent_issue_id => parent.id)
871 871
872 872 get :index, :c => %w(parent)
873 873
874 874 assert_select 'td.parent', :text => "#{parent.tracker} ##{parent.id}"
875 875 assert_select 'td.parent a[title=?]', parent.subject
876 876 end
877 877
878 878 def test_index_send_html_if_query_is_invalid
879 879 get :index, :f => ['start_date'], :op => {:start_date => '='}
880 880 assert_equal 'text/html', @response.content_type
881 881 assert_template 'index'
882 882 end
883 883
884 884 def test_index_send_nothing_if_query_is_invalid
885 885 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
886 886 assert_equal 'text/csv', @response.content_type
887 887 assert @response.body.blank?
888 888 end
889 889
890 890 def test_show_by_anonymous
891 891 get :show, :id => 1
892 892 assert_response :success
893 893 assert_template 'show'
894 894 assert_equal Issue.find(1), assigns(:issue)
895 895 assert_select 'div.issue div.description', :text => /Unable to print recipes/
896 896 # anonymous role is allowed to add a note
897 897 assert_select 'form#issue-form' do
898 898 assert_select 'fieldset' do
899 899 assert_select 'legend', :text => 'Notes'
900 900 assert_select 'textarea[name=?]', 'issue[notes]'
901 901 end
902 902 end
903 903 assert_select 'title', :text => "Bug #1: #{ESCAPED_UCANT} print recipes - eCookbook - Redmine"
904 904 end
905 905
906 906 def test_show_by_manager
907 907 @request.session[:user_id] = 2
908 908 get :show, :id => 1
909 909 assert_response :success
910 910 assert_select 'a', :text => /Quote/
911 911 assert_select 'form#issue-form' do
912 912 assert_select 'fieldset' do
913 913 assert_select 'legend', :text => 'Change properties'
914 914 assert_select 'input[name=?]', 'issue[subject]'
915 915 end
916 916 assert_select 'fieldset' do
917 917 assert_select 'legend', :text => 'Log time'
918 918 assert_select 'input[name=?]', 'time_entry[hours]'
919 919 end
920 920 assert_select 'fieldset' do
921 921 assert_select 'legend', :text => 'Notes'
922 922 assert_select 'textarea[name=?]', 'issue[notes]'
923 923 end
924 924 end
925 925 end
926 926
927 927 def test_show_should_display_update_form
928 928 @request.session[:user_id] = 2
929 929 get :show, :id => 1
930 930 assert_response :success
931 931
932 932 assert_select 'form#issue-form' do
933 933 assert_select 'input[name=?]', 'issue[is_private]'
934 934 assert_select 'select[name=?]', 'issue[project_id]'
935 935 assert_select 'select[name=?]', 'issue[tracker_id]'
936 936 assert_select 'input[name=?]', 'issue[subject]'
937 937 assert_select 'textarea[name=?]', 'issue[description]'
938 938 assert_select 'select[name=?]', 'issue[status_id]'
939 939 assert_select 'select[name=?]', 'issue[priority_id]'
940 940 assert_select 'select[name=?]', 'issue[assigned_to_id]'
941 941 assert_select 'select[name=?]', 'issue[category_id]'
942 942 assert_select 'select[name=?]', 'issue[fixed_version_id]'
943 943 assert_select 'input[name=?]', 'issue[parent_issue_id]'
944 944 assert_select 'input[name=?]', 'issue[start_date]'
945 945 assert_select 'input[name=?]', 'issue[due_date]'
946 946 assert_select 'select[name=?]', 'issue[done_ratio]'
947 947 assert_select 'input[name=?]', 'issue[custom_field_values][2]'
948 948 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
949 949 assert_select 'textarea[name=?]', 'issue[notes]'
950 950 end
951 951 end
952 952
953 953 def test_show_should_display_update_form_with_minimal_permissions
954 954 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
955 955 WorkflowTransition.delete_all :role_id => 1
956 956
957 957 @request.session[:user_id] = 2
958 958 get :show, :id => 1
959 959 assert_response :success
960 960
961 961 assert_select 'form#issue-form' do
962 962 assert_select 'input[name=?]', 'issue[is_private]', 0
963 963 assert_select 'select[name=?]', 'issue[project_id]', 0
964 964 assert_select 'select[name=?]', 'issue[tracker_id]', 0
965 965 assert_select 'input[name=?]', 'issue[subject]', 0
966 966 assert_select 'textarea[name=?]', 'issue[description]', 0
967 967 assert_select 'select[name=?]', 'issue[status_id]', 0
968 968 assert_select 'select[name=?]', 'issue[priority_id]', 0
969 969 assert_select 'select[name=?]', 'issue[assigned_to_id]', 0
970 970 assert_select 'select[name=?]', 'issue[category_id]', 0
971 971 assert_select 'select[name=?]', 'issue[fixed_version_id]', 0
972 972 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
973 973 assert_select 'input[name=?]', 'issue[start_date]', 0
974 974 assert_select 'input[name=?]', 'issue[due_date]', 0
975 975 assert_select 'select[name=?]', 'issue[done_ratio]', 0
976 976 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
977 977 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
978 978 assert_select 'textarea[name=?]', 'issue[notes]'
979 979 end
980 980 end
981 981
982 982 def test_show_should_display_update_form_with_workflow_permissions
983 983 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
984 984
985 985 @request.session[:user_id] = 2
986 986 get :show, :id => 1
987 987 assert_response :success
988 988
989 989 assert_select 'form#issue-form' do
990 990 assert_select 'input[name=?]', 'issue[is_private]', 0
991 991 assert_select 'select[name=?]', 'issue[project_id]', 0
992 992 assert_select 'select[name=?]', 'issue[tracker_id]', 0
993 993 assert_select 'input[name=?]', 'issue[subject]', 0
994 994 assert_select 'textarea[name=?]', 'issue[description]', 0
995 995 assert_select 'select[name=?]', 'issue[status_id]'
996 996 assert_select 'select[name=?]', 'issue[priority_id]', 0
997 997 assert_select 'select[name=?]', 'issue[assigned_to_id]'
998 998 assert_select 'select[name=?]', 'issue[category_id]', 0
999 999 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1000 1000 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1001 1001 assert_select 'input[name=?]', 'issue[start_date]', 0
1002 1002 assert_select 'input[name=?]', 'issue[due_date]', 0
1003 1003 assert_select 'select[name=?]', 'issue[done_ratio]'
1004 1004 assert_select 'input[name=?]', 'issue[custom_field_values][2]', 0
1005 1005 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1006 1006 assert_select 'textarea[name=?]', 'issue[notes]'
1007 1007 end
1008 1008 end
1009 1009
1010 1010 def test_show_should_not_display_update_form_without_permissions
1011 1011 Role.find(1).update_attribute :permissions, [:view_issues]
1012 1012
1013 1013 @request.session[:user_id] = 2
1014 1014 get :show, :id => 1
1015 1015 assert_response :success
1016 1016
1017 1017 assert_select 'form#issue-form', 0
1018 1018 end
1019 1019
1020 1020 def test_update_form_should_not_display_inactive_enumerations
1021 1021 assert !IssuePriority.find(15).active?
1022 1022
1023 1023 @request.session[:user_id] = 2
1024 1024 get :show, :id => 1
1025 1025 assert_response :success
1026 1026
1027 1027 assert_select 'form#issue-form' do
1028 1028 assert_select 'select[name=?]', 'issue[priority_id]' do
1029 1029 assert_select 'option[value="4"]'
1030 1030 assert_select 'option[value="15"]', 0
1031 1031 end
1032 1032 end
1033 1033 end
1034 1034
1035 1035 def test_update_form_should_allow_attachment_upload
1036 1036 @request.session[:user_id] = 2
1037 1037 get :show, :id => 1
1038 1038
1039 1039 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
1040 1040 assert_select 'input[type=file][name=?]', 'attachments[dummy][file]'
1041 1041 end
1042 1042 end
1043 1043
1044 1044 def test_show_should_deny_anonymous_access_without_permission
1045 1045 Role.anonymous.remove_permission!(:view_issues)
1046 1046 get :show, :id => 1
1047 1047 assert_response :redirect
1048 1048 end
1049 1049
1050 1050 def test_show_should_deny_anonymous_access_to_private_issue
1051 1051 Issue.where(:id => 1).update_all(["is_private = ?", true])
1052 1052 get :show, :id => 1
1053 1053 assert_response :redirect
1054 1054 end
1055 1055
1056 1056 def test_show_should_deny_non_member_access_without_permission
1057 1057 Role.non_member.remove_permission!(:view_issues)
1058 1058 @request.session[:user_id] = 9
1059 1059 get :show, :id => 1
1060 1060 assert_response 403
1061 1061 end
1062 1062
1063 1063 def test_show_should_deny_non_member_access_to_private_issue
1064 1064 Issue.where(:id => 1).update_all(["is_private = ?", true])
1065 1065 @request.session[:user_id] = 9
1066 1066 get :show, :id => 1
1067 1067 assert_response 403
1068 1068 end
1069 1069
1070 1070 def test_show_should_deny_member_access_without_permission
1071 1071 Role.find(1).remove_permission!(:view_issues)
1072 1072 @request.session[:user_id] = 2
1073 1073 get :show, :id => 1
1074 1074 assert_response 403
1075 1075 end
1076 1076
1077 1077 def test_show_should_deny_member_access_to_private_issue_without_permission
1078 1078 Issue.where(:id => 1).update_all(["is_private = ?", true])
1079 1079 @request.session[:user_id] = 3
1080 1080 get :show, :id => 1
1081 1081 assert_response 403
1082 1082 end
1083 1083
1084 1084 def test_show_should_allow_author_access_to_private_issue
1085 1085 Issue.where(:id => 1).update_all(["is_private = ?, author_id = 3", true])
1086 1086 @request.session[:user_id] = 3
1087 1087 get :show, :id => 1
1088 1088 assert_response :success
1089 1089 end
1090 1090
1091 1091 def test_show_should_allow_assignee_access_to_private_issue
1092 1092 Issue.where(:id => 1).update_all(["is_private = ?, assigned_to_id = 3", true])
1093 1093 @request.session[:user_id] = 3
1094 1094 get :show, :id => 1
1095 1095 assert_response :success
1096 1096 end
1097 1097
1098 1098 def test_show_should_allow_member_access_to_private_issue_with_permission
1099 1099 Issue.where(:id => 1).update_all(["is_private = ?", true])
1100 1100 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1101 1101 @request.session[:user_id] = 3
1102 1102 get :show, :id => 1
1103 1103 assert_response :success
1104 1104 end
1105 1105
1106 1106 def test_show_should_not_disclose_relations_to_invisible_issues
1107 1107 Setting.cross_project_issue_relations = '1'
1108 1108 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1109 1109 # Relation to a private project issue
1110 1110 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1111 1111
1112 1112 get :show, :id => 1
1113 1113 assert_response :success
1114 1114
1115 1115 assert_select 'div#relations' do
1116 1116 assert_select 'a', :text => /#2$/
1117 1117 assert_select 'a', :text => /#4$/, :count => 0
1118 1118 end
1119 1119 end
1120 1120
1121 1121 def test_show_should_list_subtasks
1122 1122 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1123 1123
1124 1124 get :show, :id => 1
1125 1125 assert_response :success
1126 1126
1127 1127 assert_select 'div#issue_tree' do
1128 1128 assert_select 'td.subject', :text => /Child Issue/
1129 1129 end
1130 1130 end
1131 1131
1132 1132 def test_show_should_list_parents
1133 1133 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1134 1134
1135 1135 get :show, :id => issue.id
1136 1136 assert_response :success
1137 1137
1138 1138 assert_select 'div.subject' do
1139 1139 assert_select 'h3', 'Child Issue'
1140 1140 assert_select 'a[href="/issues/1"]'
1141 1141 end
1142 1142 end
1143 1143
1144 1144 def test_show_should_not_display_prev_next_links_without_query_in_session
1145 1145 get :show, :id => 1
1146 1146 assert_response :success
1147 1147 assert_nil assigns(:prev_issue_id)
1148 1148 assert_nil assigns(:next_issue_id)
1149 1149
1150 1150 assert_select 'div.next-prev-links', 0
1151 1151 end
1152 1152
1153 1153 def test_show_should_display_prev_next_links_with_query_in_session
1154 1154 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1155 1155 @request.session['issues_index_sort'] = 'id'
1156 1156
1157 1157 with_settings :display_subprojects_issues => '0' do
1158 1158 get :show, :id => 3
1159 1159 end
1160 1160
1161 1161 assert_response :success
1162 1162 # Previous and next issues for all projects
1163 1163 assert_equal 2, assigns(:prev_issue_id)
1164 1164 assert_equal 5, assigns(:next_issue_id)
1165 1165
1166 1166 count = Issue.open.visible.count
1167 1167
1168 1168 assert_select 'div.next-prev-links' do
1169 1169 assert_select 'a[href="/issues/2"]', :text => /Previous/
1170 1170 assert_select 'a[href="/issues/5"]', :text => /Next/
1171 1171 assert_select 'span.position', :text => "3 of #{count}"
1172 1172 end
1173 1173 end
1174 1174
1175 1175 def test_show_should_display_prev_next_links_with_saved_query_in_session
1176 1176 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1,
1177 1177 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1178 1178 :sort_criteria => [['id', 'asc']])
1179 1179 @request.session[:query] = {:id => query.id, :project_id => nil}
1180 1180
1181 1181 get :show, :id => 11
1182 1182
1183 1183 assert_response :success
1184 1184 assert_equal query, assigns(:query)
1185 1185 # Previous and next issues for all projects
1186 1186 assert_equal 8, assigns(:prev_issue_id)
1187 1187 assert_equal 12, assigns(:next_issue_id)
1188 1188
1189 1189 assert_select 'div.next-prev-links' do
1190 1190 assert_select 'a[href="/issues/8"]', :text => /Previous/
1191 1191 assert_select 'a[href="/issues/12"]', :text => /Next/
1192 1192 end
1193 1193 end
1194 1194
1195 1195 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1196 1196 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1197 1197
1198 1198 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1199 1199 @request.session['issues_index_sort'] = assoc_sort
1200 1200
1201 1201 get :show, :id => 3
1202 1202 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1203 1203
1204 1204 assert_select 'div.next-prev-links' do
1205 1205 assert_select 'a', :text => /(Previous|Next)/
1206 1206 end
1207 1207 end
1208 1208 end
1209 1209
1210 1210 def test_show_should_display_prev_next_links_with_project_query_in_session
1211 1211 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1212 1212 @request.session['issues_index_sort'] = 'id'
1213 1213
1214 1214 with_settings :display_subprojects_issues => '0' do
1215 1215 get :show, :id => 3
1216 1216 end
1217 1217
1218 1218 assert_response :success
1219 1219 # Previous and next issues inside project
1220 1220 assert_equal 2, assigns(:prev_issue_id)
1221 1221 assert_equal 7, assigns(:next_issue_id)
1222 1222
1223 1223 assert_select 'div.next-prev-links' do
1224 1224 assert_select 'a[href="/issues/2"]', :text => /Previous/
1225 1225 assert_select 'a[href="/issues/7"]', :text => /Next/
1226 1226 end
1227 1227 end
1228 1228
1229 1229 def test_show_should_not_display_prev_link_for_first_issue
1230 1230 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1231 1231 @request.session['issues_index_sort'] = 'id'
1232 1232
1233 1233 with_settings :display_subprojects_issues => '0' do
1234 1234 get :show, :id => 1
1235 1235 end
1236 1236
1237 1237 assert_response :success
1238 1238 assert_nil assigns(:prev_issue_id)
1239 1239 assert_equal 2, assigns(:next_issue_id)
1240 1240
1241 1241 assert_select 'div.next-prev-links' do
1242 1242 assert_select 'a', :text => /Previous/, :count => 0
1243 1243 assert_select 'a[href="/issues/2"]', :text => /Next/
1244 1244 end
1245 1245 end
1246 1246
1247 1247 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1248 1248 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1249 1249 @request.session['issues_index_sort'] = 'id'
1250 1250
1251 1251 get :show, :id => 1
1252 1252
1253 1253 assert_response :success
1254 1254 assert_nil assigns(:prev_issue_id)
1255 1255 assert_nil assigns(:next_issue_id)
1256 1256
1257 1257 assert_select 'a', :text => /Previous/, :count => 0
1258 1258 assert_select 'a', :text => /Next/, :count => 0
1259 1259 end
1260 1260
1261 1261 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1262 1262 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1263 1263 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1264 1264 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1265 1265 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1266 1266 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1267 1267
1268 1268 query = IssueQuery.create!(:name => 'test', :visibility => IssueQuery::VISIBILITY_PUBLIC, :user_id => 1, :filters => {},
1269 1269 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1270 1270 @request.session[:query] = {:id => query.id, :project_id => nil}
1271 1271
1272 1272 get :show, :id => 3
1273 1273 assert_response :success
1274 1274
1275 1275 assert_equal 2, assigns(:prev_issue_id)
1276 1276 assert_equal 1, assigns(:next_issue_id)
1277 1277
1278 1278 assert_select 'div.next-prev-links' do
1279 1279 assert_select 'a[href="/issues/2"]', :text => /Previous/
1280 1280 assert_select 'a[href="/issues/1"]', :text => /Next/
1281 1281 end
1282 1282 end
1283 1283
1284 1284 def test_show_should_display_link_to_the_assignee
1285 1285 get :show, :id => 2
1286 1286 assert_response :success
1287 1287 assert_select '.assigned-to' do
1288 1288 assert_select 'a[href="/users/3"]'
1289 1289 end
1290 1290 end
1291 1291
1292 1292 def test_show_should_display_visible_changesets_from_other_projects
1293 1293 project = Project.find(2)
1294 1294 issue = project.issues.first
1295 1295 issue.changeset_ids = [102]
1296 1296 issue.save!
1297 1297 # changesets from other projects should be displayed even if repository
1298 1298 # is disabled on issue's project
1299 1299 project.disable_module! :repository
1300 1300
1301 1301 @request.session[:user_id] = 2
1302 1302 get :show, :id => issue.id
1303 1303
1304 1304 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1305 1305 end
1306 1306
1307 1307 def test_show_should_display_watchers
1308 1308 @request.session[:user_id] = 2
1309 1309 Issue.find(1).add_watcher User.find(2)
1310 1310
1311 1311 get :show, :id => 1
1312 1312 assert_select 'div#watchers ul' do
1313 1313 assert_select 'li' do
1314 1314 assert_select 'a[href="/users/2"]'
1315 1315 assert_select 'a img[alt=Delete]'
1316 1316 end
1317 1317 end
1318 1318 end
1319 1319
1320 1320 def test_show_should_display_watchers_with_gravatars
1321 1321 @request.session[:user_id] = 2
1322 1322 Issue.find(1).add_watcher User.find(2)
1323 1323
1324 1324 with_settings :gravatar_enabled => '1' do
1325 1325 get :show, :id => 1
1326 1326 end
1327 1327
1328 1328 assert_select 'div#watchers ul' do
1329 1329 assert_select 'li' do
1330 1330 assert_select 'img.gravatar'
1331 1331 assert_select 'a[href="/users/2"]'
1332 1332 assert_select 'a img[alt=Delete]'
1333 1333 end
1334 1334 end
1335 1335 end
1336 1336
1337 1337 def test_show_with_thumbnails_enabled_should_display_thumbnails
1338 1338 @request.session[:user_id] = 2
1339 1339
1340 1340 with_settings :thumbnails_enabled => '1' do
1341 1341 get :show, :id => 14
1342 1342 assert_response :success
1343 1343 end
1344 1344
1345 1345 assert_select 'div.thumbnails' do
1346 1346 assert_select 'a[href="/attachments/16/testfile.png"]' do
1347 1347 assert_select 'img[src="/attachments/thumbnail/16"]'
1348 1348 end
1349 1349 end
1350 1350 end
1351 1351
1352 1352 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1353 1353 @request.session[:user_id] = 2
1354 1354
1355 1355 with_settings :thumbnails_enabled => '0' do
1356 1356 get :show, :id => 14
1357 1357 assert_response :success
1358 1358 end
1359 1359
1360 1360 assert_select 'div.thumbnails', 0
1361 1361 end
1362 1362
1363 1363 def test_show_with_multi_custom_field
1364 1364 field = CustomField.find(1)
1365 1365 field.update_attribute :multiple, true
1366 1366 issue = Issue.find(1)
1367 1367 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1368 1368 issue.save!
1369 1369
1370 1370 get :show, :id => 1
1371 1371 assert_response :success
1372 1372
1373 1373 assert_select 'td', :text => 'MySQL, Oracle'
1374 1374 end
1375 1375
1376 1376 def test_show_with_multi_user_custom_field
1377 1377 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1378 1378 :tracker_ids => [1], :is_for_all => true)
1379 1379 issue = Issue.find(1)
1380 1380 issue.custom_field_values = {field.id => ['2', '3']}
1381 1381 issue.save!
1382 1382
1383 1383 get :show, :id => 1
1384 1384 assert_response :success
1385 1385
1386 1386 assert_select "td.cf_#{field.id}", :text => 'Dave Lopper, John Smith' do
1387 1387 assert_select 'a', :text => 'Dave Lopper'
1388 1388 assert_select 'a', :text => 'John Smith'
1389 1389 end
1390 1390 end
1391 1391
1392 1392 def test_show_should_display_private_notes_with_permission_only
1393 1393 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1394 1394 @request.session[:user_id] = 2
1395 1395
1396 1396 get :show, :id => 2
1397 1397 assert_response :success
1398 1398 assert_include journal, assigns(:journals)
1399 1399
1400 1400 Role.find(1).remove_permission! :view_private_notes
1401 1401 get :show, :id => 2
1402 1402 assert_response :success
1403 1403 assert_not_include journal, assigns(:journals)
1404 1404 end
1405 1405
1406 1406 def test_show_atom
1407 1407 get :show, :id => 2, :format => 'atom'
1408 1408 assert_response :success
1409 1409 assert_template 'journals/index'
1410 1410 # Inline image
1411 1411 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1412 1412 end
1413 1413
1414 1414 def test_show_export_to_pdf
1415 1415 issue = Issue.find(3)
1416 1416 assert issue.relations.select{|r| r.other_issue(issue).visible?}.present?
1417 1417 get :show, :id => 3, :format => 'pdf'
1418 1418 assert_response :success
1419 1419 assert_equal 'application/pdf', @response.content_type
1420 1420 assert @response.body.starts_with?('%PDF')
1421 1421 assert_not_nil assigns(:issue)
1422 1422 end
1423 1423
1424 1424 def test_export_to_pdf_with_utf8_u_fffd
1425 1425 # U+FFFD
1426 1426 s = "\xef\xbf\xbd"
1427 1427 s.force_encoding('UTF-8') if s.respond_to?(:force_encoding)
1428 1428 issue = Issue.generate!(:subject => s)
1429 1429 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
1430 1430 with_settings :default_language => lang do
1431 1431 get :show, :id => issue.id, :format => 'pdf'
1432 1432 assert_response :success
1433 1433 assert_equal 'application/pdf', @response.content_type
1434 1434 assert @response.body.starts_with?('%PDF')
1435 1435 assert_not_nil assigns(:issue)
1436 1436 end
1437 1437 end
1438 1438 end
1439 1439
1440 1440 def test_show_export_to_pdf_with_ancestors
1441 1441 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1442 1442
1443 1443 get :show, :id => issue.id, :format => 'pdf'
1444 1444 assert_response :success
1445 1445 assert_equal 'application/pdf', @response.content_type
1446 1446 assert @response.body.starts_with?('%PDF')
1447 1447 end
1448 1448
1449 1449 def test_show_export_to_pdf_with_descendants
1450 1450 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1451 1451 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1452 1452 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1453 1453
1454 1454 get :show, :id => 1, :format => 'pdf'
1455 1455 assert_response :success
1456 1456 assert_equal 'application/pdf', @response.content_type
1457 1457 assert @response.body.starts_with?('%PDF')
1458 1458 end
1459 1459
1460 1460 def test_show_export_to_pdf_with_journals
1461 1461 get :show, :id => 1, :format => 'pdf'
1462 1462 assert_response :success
1463 1463 assert_equal 'application/pdf', @response.content_type
1464 1464 assert @response.body.starts_with?('%PDF')
1465 1465 end
1466 1466
1467 1467 def test_show_export_to_pdf_with_changesets
1468 1468 [[100], [100, 101], [100, 101, 102]].each do |cs|
1469 1469 issue1 = Issue.find(3)
1470 1470 issue1.changesets = Changeset.find(cs)
1471 1471 issue1.save!
1472 1472 issue = Issue.find(3)
1473 1473 assert_equal issue.changesets.count, cs.size
1474 1474 get :show, :id => 3, :format => 'pdf'
1475 1475 assert_response :success
1476 1476 assert_equal 'application/pdf', @response.content_type
1477 1477 assert @response.body.starts_with?('%PDF')
1478 1478 end
1479 1479 end
1480 1480
1481 1481 def test_show_invalid_should_respond_with_404
1482 1482 get :show, :id => 999
1483 1483 assert_response 404
1484 1484 end
1485 1485
1486 1486 def test_get_new
1487 1487 @request.session[:user_id] = 2
1488 1488 get :new, :project_id => 1, :tracker_id => 1
1489 1489 assert_response :success
1490 1490 assert_template 'new'
1491 1491
1492 1492 assert_select 'form#issue-form' do
1493 1493 assert_select 'input[name=?]', 'issue[is_private]'
1494 1494 assert_select 'select[name=?]', 'issue[project_id]', 0
1495 1495 assert_select 'select[name=?]', 'issue[tracker_id]'
1496 1496 assert_select 'input[name=?]', 'issue[subject]'
1497 1497 assert_select 'textarea[name=?]', 'issue[description]'
1498 1498 assert_select 'select[name=?]', 'issue[status_id]'
1499 1499 assert_select 'select[name=?]', 'issue[priority_id]'
1500 1500 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1501 1501 assert_select 'select[name=?]', 'issue[category_id]'
1502 1502 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1503 1503 assert_select 'input[name=?]', 'issue[parent_issue_id]'
1504 1504 assert_select 'input[name=?]', 'issue[start_date]'
1505 1505 assert_select 'input[name=?]', 'issue[due_date]'
1506 1506 assert_select 'select[name=?]', 'issue[done_ratio]'
1507 1507 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1508 1508 assert_select 'input[name=?]', 'issue[watcher_user_ids][]'
1509 1509 end
1510 1510
1511 1511 # Be sure we don't display inactive IssuePriorities
1512 1512 assert ! IssuePriority.find(15).active?
1513 1513 assert_select 'select[name=?]', 'issue[priority_id]' do
1514 1514 assert_select 'option[value="15"]', 0
1515 1515 end
1516 1516 end
1517 1517
1518 1518 def test_get_new_with_minimal_permissions
1519 1519 Role.find(1).update_attribute :permissions, [:add_issues]
1520 1520 WorkflowTransition.delete_all :role_id => 1
1521 1521
1522 1522 @request.session[:user_id] = 2
1523 1523 get :new, :project_id => 1, :tracker_id => 1
1524 1524 assert_response :success
1525 1525 assert_template 'new'
1526 1526
1527 1527 assert_select 'form#issue-form' do
1528 1528 assert_select 'input[name=?]', 'issue[is_private]', 0
1529 1529 assert_select 'select[name=?]', 'issue[project_id]', 0
1530 1530 assert_select 'select[name=?]', 'issue[tracker_id]'
1531 1531 assert_select 'input[name=?]', 'issue[subject]'
1532 1532 assert_select 'textarea[name=?]', 'issue[description]'
1533 1533 assert_select 'select[name=?]', 'issue[status_id]'
1534 1534 assert_select 'select[name=?]', 'issue[priority_id]'
1535 1535 assert_select 'select[name=?]', 'issue[assigned_to_id]'
1536 1536 assert_select 'select[name=?]', 'issue[category_id]'
1537 1537 assert_select 'select[name=?]', 'issue[fixed_version_id]'
1538 1538 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
1539 1539 assert_select 'input[name=?]', 'issue[start_date]'
1540 1540 assert_select 'input[name=?]', 'issue[due_date]'
1541 1541 assert_select 'select[name=?]', 'issue[done_ratio]'
1542 1542 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Default string'
1543 1543 assert_select 'input[name=?]', 'issue[watcher_user_ids][]', 0
1544 1544 end
1545 1545 end
1546 1546
1547 1547 def test_new_should_select_default_status
1548 1548 @request.session[:user_id] = 2
1549 1549
1550 1550 get :new, :project_id => 1
1551 1551 assert_response :success
1552 1552 assert_template 'new'
1553 1553 assert_select 'select[name=?]', 'issue[status_id]' do
1554 1554 assert_select 'option[value="1"][selected=selected]'
1555 1555 end
1556 1556 assert_select 'input[name=was_default_status][value="1"]'
1557 1557 end
1558 1558
1559 1559 def test_get_new_with_list_custom_field
1560 1560 @request.session[:user_id] = 2
1561 1561 get :new, :project_id => 1, :tracker_id => 1
1562 1562 assert_response :success
1563 1563 assert_template 'new'
1564 1564
1565 1565 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1566 1566 assert_select 'option', 4
1567 1567 assert_select 'option[value=MySQL]', :text => 'MySQL'
1568 1568 end
1569 1569 end
1570 1570
1571 1571 def test_get_new_with_multi_custom_field
1572 1572 field = IssueCustomField.find(1)
1573 1573 field.update_attribute :multiple, true
1574 1574
1575 1575 @request.session[:user_id] = 2
1576 1576 get :new, :project_id => 1, :tracker_id => 1
1577 1577 assert_response :success
1578 1578 assert_template 'new'
1579 1579
1580 1580 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1581 1581 assert_select 'option', 3
1582 1582 assert_select 'option[value=MySQL]', :text => 'MySQL'
1583 1583 end
1584 1584 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1585 1585 end
1586 1586
1587 1587 def test_get_new_with_multi_user_custom_field
1588 1588 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1589 1589 :tracker_ids => [1], :is_for_all => true)
1590 1590
1591 1591 @request.session[:user_id] = 2
1592 1592 get :new, :project_id => 1, :tracker_id => 1
1593 1593 assert_response :success
1594 1594 assert_template 'new'
1595 1595
1596 1596 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1597 1597 assert_select 'option', Project.find(1).users.count
1598 1598 assert_select 'option[value="2"]', :text => 'John Smith'
1599 1599 end
1600 1600 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1601 1601 end
1602 1602
1603 1603 def test_get_new_with_date_custom_field
1604 1604 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1605 1605
1606 1606 @request.session[:user_id] = 2
1607 1607 get :new, :project_id => 1, :tracker_id => 1
1608 1608 assert_response :success
1609 1609
1610 1610 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1611 1611 end
1612 1612
1613 1613 def test_get_new_with_text_custom_field
1614 1614 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1615 1615
1616 1616 @request.session[:user_id] = 2
1617 1617 get :new, :project_id => 1, :tracker_id => 1
1618 1618 assert_response :success
1619 1619
1620 1620 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1621 1621 end
1622 1622
1623 1623 def test_get_new_without_default_start_date_is_creation_date
1624 1624 with_settings :default_issue_start_date_to_creation_date => 0 do
1625 1625 @request.session[:user_id] = 2
1626 1626 get :new, :project_id => 1, :tracker_id => 1
1627 1627 assert_response :success
1628 1628 assert_template 'new'
1629 1629 assert_select 'input[name=?]', 'issue[start_date]'
1630 1630 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1631 1631 end
1632 1632 end
1633 1633
1634 1634 def test_get_new_with_default_start_date_is_creation_date
1635 1635 with_settings :default_issue_start_date_to_creation_date => 1 do
1636 1636 @request.session[:user_id] = 2
1637 1637 get :new, :project_id => 1, :tracker_id => 1
1638 1638 assert_response :success
1639 1639 assert_template 'new'
1640 1640 assert_select 'input[name=?][value=?]', 'issue[start_date]',
1641 1641 Date.today.to_s
1642 1642 end
1643 1643 end
1644 1644
1645 1645 def test_get_new_form_should_allow_attachment_upload
1646 1646 @request.session[:user_id] = 2
1647 1647 get :new, :project_id => 1, :tracker_id => 1
1648 1648
1649 1649 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1650 1650 assert_select 'input[name=?][type=file]', 'attachments[dummy][file]'
1651 1651 end
1652 1652 end
1653 1653
1654 1654 def test_get_new_should_prefill_the_form_from_params
1655 1655 @request.session[:user_id] = 2
1656 1656 get :new, :project_id => 1,
1657 1657 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1658 1658
1659 1659 issue = assigns(:issue)
1660 1660 assert_equal 3, issue.tracker_id
1661 1661 assert_equal 'Prefilled', issue.description
1662 1662 assert_equal 'Custom field value', issue.custom_field_value(2)
1663 1663
1664 1664 assert_select 'select[name=?]', 'issue[tracker_id]' do
1665 1665 assert_select 'option[value="3"][selected=selected]'
1666 1666 end
1667 1667 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1668 1668 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1669 1669 end
1670 1670
1671 1671 def test_get_new_should_mark_required_fields
1672 1672 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1673 1673 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1674 1674 WorkflowPermission.delete_all
1675 1675 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1676 1676 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1677 1677 @request.session[:user_id] = 2
1678 1678
1679 1679 get :new, :project_id => 1
1680 1680 assert_response :success
1681 1681 assert_template 'new'
1682 1682
1683 1683 assert_select 'label[for=issue_start_date]' do
1684 1684 assert_select 'span[class=required]', 0
1685 1685 end
1686 1686 assert_select 'label[for=issue_due_date]' do
1687 1687 assert_select 'span[class=required]'
1688 1688 end
1689 1689 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1690 1690 assert_select 'span[class=required]', 0
1691 1691 end
1692 1692 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1693 1693 assert_select 'span[class=required]'
1694 1694 end
1695 1695 end
1696 1696
1697 1697 def test_get_new_should_not_display_readonly_fields
1698 1698 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1699 1699 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1700 1700 WorkflowPermission.delete_all
1701 1701 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1702 1702 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1703 1703 @request.session[:user_id] = 2
1704 1704
1705 1705 get :new, :project_id => 1
1706 1706 assert_response :success
1707 1707 assert_template 'new'
1708 1708
1709 1709 assert_select 'input[name=?]', 'issue[start_date]'
1710 1710 assert_select 'input[name=?]', 'issue[due_date]', 0
1711 1711 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1712 1712 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1713 1713 end
1714 1714
1715 1715 def test_get_new_without_tracker_id
1716 1716 @request.session[:user_id] = 2
1717 1717 get :new, :project_id => 1
1718 1718 assert_response :success
1719 1719 assert_template 'new'
1720 1720
1721 1721 issue = assigns(:issue)
1722 1722 assert_not_nil issue
1723 1723 assert_equal Project.find(1).trackers.first, issue.tracker
1724 1724 end
1725 1725
1726 1726 def test_get_new_with_no_default_status_should_display_an_error
1727 1727 @request.session[:user_id] = 2
1728 1728 IssueStatus.delete_all
1729 1729
1730 1730 get :new, :project_id => 1
1731 1731 assert_response 500
1732 1732 assert_select_error /No default issue/
1733 1733 end
1734 1734
1735 1735 def test_get_new_with_no_tracker_should_display_an_error
1736 1736 @request.session[:user_id] = 2
1737 1737 Tracker.delete_all
1738 1738
1739 1739 get :new, :project_id => 1
1740 1740 assert_response 500
1741 1741 assert_select_error /No tracker/
1742 1742 end
1743 1743
1744 1744 def test_update_form_for_new_issue
1745 1745 @request.session[:user_id] = 2
1746 1746 xhr :post, :update_form, :project_id => 1,
1747 1747 :issue => {:tracker_id => 2,
1748 1748 :subject => 'This is the test_new issue',
1749 1749 :description => 'This is the description',
1750 1750 :priority_id => 5}
1751 1751 assert_response :success
1752 1752 assert_template 'update_form'
1753 1753 assert_template :partial => '_form'
1754 1754 assert_equal 'text/javascript', response.content_type
1755 1755
1756 1756 issue = assigns(:issue)
1757 1757 assert_kind_of Issue, issue
1758 1758 assert_equal 1, issue.project_id
1759 1759 assert_equal 2, issue.tracker_id
1760 1760 assert_equal 'This is the test_new issue', issue.subject
1761 1761 end
1762 1762
1763 1763 def test_update_form_for_new_issue_should_propose_transitions_based_on_initial_status
1764 1764 @request.session[:user_id] = 2
1765 1765 WorkflowTransition.delete_all
1766 1766 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1767 1767 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1768 1768 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1769 1769
1770 1770 xhr :post, :update_form, :project_id => 1,
1771 1771 :issue => {:tracker_id => 1,
1772 1772 :status_id => 5,
1773 1773 :subject => 'This is an issue'}
1774 1774
1775 1775 assert_equal 5, assigns(:issue).status_id
1776 1776 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1777 1777 end
1778 1778
1779 1779 def test_update_form_with_default_status_should_ignore_submitted_status_id_if_equals
1780 1780 @request.session[:user_id] = 2
1781 1781 tracker = Tracker.find(2)
1782 1782 tracker.update! :default_status_id => 2
1783 1783 tracker.generate_transitions! 2, 1, :clear => true
1784 1784
1785 1785 xhr :post, :update_form, :project_id => 1,
1786 1786 :issue => {:tracker_id => 2,
1787 1787 :status_id => 1},
1788 1788 :was_default_status => 1
1789 1789
1790 1790 assert_equal 2, assigns(:issue).status_id
1791 1791 end
1792 1792
1793 1793 def test_post_create
1794 1794 @request.session[:user_id] = 2
1795 1795 assert_difference 'Issue.count' do
1796 1796 post :create, :project_id => 1,
1797 1797 :issue => {:tracker_id => 3,
1798 1798 :status_id => 2,
1799 1799 :subject => 'This is the test_new issue',
1800 1800 :description => 'This is the description',
1801 1801 :priority_id => 5,
1802 1802 :start_date => '2010-11-07',
1803 1803 :estimated_hours => '',
1804 1804 :custom_field_values => {'2' => 'Value for field 2'}}
1805 1805 end
1806 1806 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1807 1807
1808 1808 issue = Issue.find_by_subject('This is the test_new issue')
1809 1809 assert_not_nil issue
1810 1810 assert_equal 2, issue.author_id
1811 1811 assert_equal 3, issue.tracker_id
1812 1812 assert_equal 2, issue.status_id
1813 1813 assert_equal Date.parse('2010-11-07'), issue.start_date
1814 1814 assert_nil issue.estimated_hours
1815 1815 v = issue.custom_values.where(:custom_field_id => 2).first
1816 1816 assert_not_nil v
1817 1817 assert_equal 'Value for field 2', v.value
1818 1818 end
1819 1819
1820 1820 def test_post_new_with_group_assignment
1821 1821 group = Group.find(11)
1822 1822 project = Project.find(1)
1823 1823 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1824 1824
1825 1825 with_settings :issue_group_assignment => '1' do
1826 1826 @request.session[:user_id] = 2
1827 1827 assert_difference 'Issue.count' do
1828 1828 post :create, :project_id => project.id,
1829 1829 :issue => {:tracker_id => 3,
1830 1830 :status_id => 1,
1831 1831 :subject => 'This is the test_new_with_group_assignment issue',
1832 1832 :assigned_to_id => group.id}
1833 1833 end
1834 1834 end
1835 1835 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1836 1836
1837 1837 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1838 1838 assert_not_nil issue
1839 1839 assert_equal group, issue.assigned_to
1840 1840 end
1841 1841
1842 1842 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1843 1843 with_settings :default_issue_start_date_to_creation_date => 0 do
1844 1844 @request.session[:user_id] = 2
1845 1845 assert_difference 'Issue.count' do
1846 1846 post :create, :project_id => 1,
1847 1847 :issue => {:tracker_id => 3,
1848 1848 :status_id => 2,
1849 1849 :subject => 'This is the test_new issue',
1850 1850 :description => 'This is the description',
1851 1851 :priority_id => 5,
1852 1852 :estimated_hours => '',
1853 1853 :custom_field_values => {'2' => 'Value for field 2'}}
1854 1854 end
1855 1855 assert_redirected_to :controller => 'issues', :action => 'show',
1856 1856 :id => Issue.last.id
1857 1857 issue = Issue.find_by_subject('This is the test_new issue')
1858 1858 assert_not_nil issue
1859 1859 assert_nil issue.start_date
1860 1860 end
1861 1861 end
1862 1862
1863 1863 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1864 1864 with_settings :default_issue_start_date_to_creation_date => 1 do
1865 1865 @request.session[:user_id] = 2
1866 1866 assert_difference 'Issue.count' do
1867 1867 post :create, :project_id => 1,
1868 1868 :issue => {:tracker_id => 3,
1869 1869 :status_id => 2,
1870 1870 :subject => 'This is the test_new issue',
1871 1871 :description => 'This is the description',
1872 1872 :priority_id => 5,
1873 1873 :estimated_hours => '',
1874 1874 :custom_field_values => {'2' => 'Value for field 2'}}
1875 1875 end
1876 1876 assert_redirected_to :controller => 'issues', :action => 'show',
1877 1877 :id => Issue.last.id
1878 1878 issue = Issue.find_by_subject('This is the test_new issue')
1879 1879 assert_not_nil issue
1880 1880 assert_equal Date.today, issue.start_date
1881 1881 end
1882 1882 end
1883 1883
1884 1884 def test_post_create_and_continue
1885 1885 @request.session[:user_id] = 2
1886 1886 assert_difference 'Issue.count' do
1887 1887 post :create, :project_id => 1,
1888 1888 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1889 1889 :continue => ''
1890 1890 end
1891 1891
1892 1892 issue = Issue.order('id DESC').first
1893 1893 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1894 1894 assert_not_nil flash[:notice], "flash was not set"
1895 1895 assert_include %|<a href="/issues/#{issue.id}" title="This is first issue">##{issue.id}</a>|, flash[:notice], "issue link not found in the flash message"
1896 1896 end
1897 1897
1898 1898 def test_post_create_without_custom_fields_param
1899 1899 @request.session[:user_id] = 2
1900 1900 assert_difference 'Issue.count' do
1901 1901 post :create, :project_id => 1,
1902 1902 :issue => {:tracker_id => 1,
1903 1903 :subject => 'This is the test_new issue',
1904 1904 :description => 'This is the description',
1905 1905 :priority_id => 5}
1906 1906 end
1907 1907 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1908 1908 end
1909 1909
1910 1910 def test_post_create_with_multi_custom_field
1911 1911 field = IssueCustomField.find_by_name('Database')
1912 1912 field.update_attribute(:multiple, true)
1913 1913
1914 1914 @request.session[:user_id] = 2
1915 1915 assert_difference 'Issue.count' do
1916 1916 post :create, :project_id => 1,
1917 1917 :issue => {:tracker_id => 1,
1918 1918 :subject => 'This is the test_new issue',
1919 1919 :description => 'This is the description',
1920 1920 :priority_id => 5,
1921 1921 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1922 1922 end
1923 1923 assert_response 302
1924 1924 issue = Issue.order('id DESC').first
1925 1925 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1926 1926 end
1927 1927
1928 1928 def test_post_create_with_empty_multi_custom_field
1929 1929 field = IssueCustomField.find_by_name('Database')
1930 1930 field.update_attribute(:multiple, true)
1931 1931
1932 1932 @request.session[:user_id] = 2
1933 1933 assert_difference 'Issue.count' do
1934 1934 post :create, :project_id => 1,
1935 1935 :issue => {:tracker_id => 1,
1936 1936 :subject => 'This is the test_new issue',
1937 1937 :description => 'This is the description',
1938 1938 :priority_id => 5,
1939 1939 :custom_field_values => {'1' => ['']}}
1940 1940 end
1941 1941 assert_response 302
1942 1942 issue = Issue.order('id DESC').first
1943 1943 assert_equal [''], issue.custom_field_value(1).sort
1944 1944 end
1945 1945
1946 1946 def test_post_create_with_multi_user_custom_field
1947 1947 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1948 1948 :tracker_ids => [1], :is_for_all => true)
1949 1949
1950 1950 @request.session[:user_id] = 2
1951 1951 assert_difference 'Issue.count' do
1952 1952 post :create, :project_id => 1,
1953 1953 :issue => {:tracker_id => 1,
1954 1954 :subject => 'This is the test_new issue',
1955 1955 :description => 'This is the description',
1956 1956 :priority_id => 5,
1957 1957 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1958 1958 end
1959 1959 assert_response 302
1960 1960 issue = Issue.order('id DESC').first
1961 1961 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1962 1962 end
1963 1963
1964 1964 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1965 1965 field = IssueCustomField.find_by_name('Database')
1966 1966 field.update_attribute(:is_required, true)
1967 1967
1968 1968 @request.session[:user_id] = 2
1969 1969 assert_no_difference 'Issue.count' do
1970 1970 post :create, :project_id => 1,
1971 1971 :issue => {:tracker_id => 1,
1972 1972 :subject => 'This is the test_new issue',
1973 1973 :description => 'This is the description',
1974 1974 :priority_id => 5}
1975 1975 end
1976 1976 assert_response :success
1977 1977 assert_template 'new'
1978 1978 issue = assigns(:issue)
1979 1979 assert_not_nil issue
1980 1980 assert_select_error /Database #{ESCAPED_CANT} be blank/
1981 1981 end
1982 1982
1983 1983 def test_create_should_validate_required_fields
1984 1984 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1985 1985 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1986 1986 WorkflowPermission.delete_all
1987 1987 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1988 1988 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1989 1989 @request.session[:user_id] = 2
1990 1990
1991 1991 assert_no_difference 'Issue.count' do
1992 1992 post :create, :project_id => 1, :issue => {
1993 1993 :tracker_id => 2,
1994 1994 :status_id => 1,
1995 1995 :subject => 'Test',
1996 1996 :start_date => '',
1997 1997 :due_date => '',
1998 1998 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
1999 1999 }
2000 2000 assert_response :success
2001 2001 assert_template 'new'
2002 2002 end
2003 2003
2004 2004 assert_select_error /Due date #{ESCAPED_CANT} be blank/i
2005 2005 assert_select_error /Bar #{ESCAPED_CANT} be blank/i
2006 2006 end
2007 2007
2008 2008 def test_create_should_ignore_readonly_fields
2009 2009 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2010 2010 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
2011 2011 WorkflowPermission.delete_all
2012 2012 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
2013 2013 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
2014 2014 @request.session[:user_id] = 2
2015 2015
2016 2016 assert_difference 'Issue.count' do
2017 2017 post :create, :project_id => 1, :issue => {
2018 2018 :tracker_id => 2,
2019 2019 :status_id => 1,
2020 2020 :subject => 'Test',
2021 2021 :start_date => '2012-07-14',
2022 2022 :due_date => '2012-07-16',
2023 2023 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
2024 2024 }
2025 2025 assert_response 302
2026 2026 end
2027 2027
2028 2028 issue = Issue.order('id DESC').first
2029 2029 assert_equal Date.parse('2012-07-14'), issue.start_date
2030 2030 assert_nil issue.due_date
2031 2031 assert_equal 'value1', issue.custom_field_value(cf1)
2032 2032 assert_nil issue.custom_field_value(cf2)
2033 2033 end
2034 2034
2035 2035 def test_post_create_with_watchers
2036 2036 @request.session[:user_id] = 2
2037 2037 ActionMailer::Base.deliveries.clear
2038 2038
2039 2039 assert_difference 'Watcher.count', 2 do
2040 2040 post :create, :project_id => 1,
2041 2041 :issue => {:tracker_id => 1,
2042 2042 :subject => 'This is a new issue with watchers',
2043 2043 :description => 'This is the description',
2044 2044 :priority_id => 5,
2045 2045 :watcher_user_ids => ['2', '3']}
2046 2046 end
2047 2047 issue = Issue.find_by_subject('This is a new issue with watchers')
2048 2048 assert_not_nil issue
2049 2049 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
2050 2050
2051 2051 # Watchers added
2052 2052 assert_equal [2, 3], issue.watcher_user_ids.sort
2053 2053 assert issue.watched_by?(User.find(3))
2054 2054 # Watchers notified
2055 2055 mail = ActionMailer::Base.deliveries.last
2056 2056 assert_not_nil mail
2057 2057 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
2058 2058 end
2059 2059
2060 2060 def test_post_create_subissue
2061 2061 @request.session[:user_id] = 2
2062 2062
2063 2063 assert_difference 'Issue.count' do
2064 2064 post :create, :project_id => 1,
2065 2065 :issue => {:tracker_id => 1,
2066 2066 :subject => 'This is a child issue',
2067 2067 :parent_issue_id => '2'}
2068 2068 assert_response 302
2069 2069 end
2070 2070 issue = Issue.order('id DESC').first
2071 2071 assert_equal Issue.find(2), issue.parent
2072 2072 end
2073 2073
2074 2074 def test_post_create_subissue_with_sharp_parent_id
2075 2075 @request.session[:user_id] = 2
2076 2076
2077 2077 assert_difference 'Issue.count' do
2078 2078 post :create, :project_id => 1,
2079 2079 :issue => {:tracker_id => 1,
2080 2080 :subject => 'This is a child issue',
2081 2081 :parent_issue_id => '#2'}
2082 2082 assert_response 302
2083 2083 end
2084 2084 issue = Issue.order('id DESC').first
2085 2085 assert_equal Issue.find(2), issue.parent
2086 2086 end
2087 2087
2088 2088 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
2089 2089 @request.session[:user_id] = 2
2090 2090
2091 2091 assert_no_difference 'Issue.count' do
2092 2092 post :create, :project_id => 1,
2093 2093 :issue => {:tracker_id => 1,
2094 2094 :subject => 'This is a child issue',
2095 2095 :parent_issue_id => '4'}
2096 2096
2097 2097 assert_response :success
2098 2098 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
2099 2099 assert_select_error /Parent task is invalid/i
2100 2100 end
2101 2101 end
2102 2102
2103 2103 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
2104 2104 @request.session[:user_id] = 2
2105 2105
2106 2106 assert_no_difference 'Issue.count' do
2107 2107 post :create, :project_id => 1,
2108 2108 :issue => {:tracker_id => 1,
2109 2109 :subject => 'This is a child issue',
2110 2110 :parent_issue_id => '01ABC'}
2111 2111
2112 2112 assert_response :success
2113 2113 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
2114 2114 assert_select_error /Parent task is invalid/i
2115 2115 end
2116 2116 end
2117 2117
2118 2118 def test_post_create_private
2119 2119 @request.session[:user_id] = 2
2120 2120
2121 2121 assert_difference 'Issue.count' do
2122 2122 post :create, :project_id => 1,
2123 2123 :issue => {:tracker_id => 1,
2124 2124 :subject => 'This is a private issue',
2125 2125 :is_private => '1'}
2126 2126 end
2127 2127 issue = Issue.order('id DESC').first
2128 2128 assert issue.is_private?
2129 2129 end
2130 2130
2131 2131 def test_post_create_private_with_set_own_issues_private_permission
2132 2132 role = Role.find(1)
2133 2133 role.remove_permission! :set_issues_private
2134 2134 role.add_permission! :set_own_issues_private
2135 2135
2136 2136 @request.session[:user_id] = 2
2137 2137
2138 2138 assert_difference 'Issue.count' do
2139 2139 post :create, :project_id => 1,
2140 2140 :issue => {:tracker_id => 1,
2141 2141 :subject => 'This is a private issue',
2142 2142 :is_private => '1'}
2143 2143 end
2144 2144 issue = Issue.order('id DESC').first
2145 2145 assert issue.is_private?
2146 2146 end
2147 2147
2148 2148 def test_post_create_should_send_a_notification
2149 2149 ActionMailer::Base.deliveries.clear
2150 2150 @request.session[:user_id] = 2
2151 2151 assert_difference 'Issue.count' do
2152 2152 post :create, :project_id => 1,
2153 2153 :issue => {:tracker_id => 3,
2154 2154 :subject => 'This is the test_new issue',
2155 2155 :description => 'This is the description',
2156 2156 :priority_id => 5,
2157 2157 :estimated_hours => '',
2158 2158 :custom_field_values => {'2' => 'Value for field 2'}}
2159 2159 end
2160 2160 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2161 2161
2162 2162 assert_equal 1, ActionMailer::Base.deliveries.size
2163 2163 end
2164 2164
2165 2165 def test_post_create_should_preserve_fields_values_on_validation_failure
2166 2166 @request.session[:user_id] = 2
2167 2167 post :create, :project_id => 1,
2168 2168 :issue => {:tracker_id => 1,
2169 2169 # empty subject
2170 2170 :subject => '',
2171 2171 :description => 'This is a description',
2172 2172 :priority_id => 6,
2173 2173 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2174 2174 assert_response :success
2175 2175 assert_template 'new'
2176 2176
2177 2177 assert_select 'textarea[name=?]', 'issue[description]', :text => 'This is a description'
2178 2178 assert_select 'select[name=?]', 'issue[priority_id]' do
2179 2179 assert_select 'option[value="6"][selected=selected]', :text => 'High'
2180 2180 end
2181 2181 # Custom fields
2182 2182 assert_select 'select[name=?]', 'issue[custom_field_values][1]' do
2183 2183 assert_select 'option[value=Oracle][selected=selected]', :text => 'Oracle'
2184 2184 end
2185 2185 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Value for field 2'
2186 2186 end
2187 2187
2188 2188 def test_post_create_with_failure_should_preserve_watchers
2189 2189 assert !User.find(8).member_of?(Project.find(1))
2190 2190
2191 2191 @request.session[:user_id] = 2
2192 2192 post :create, :project_id => 1,
2193 2193 :issue => {:tracker_id => 1,
2194 2194 :watcher_user_ids => ['3', '8']}
2195 2195 assert_response :success
2196 2196 assert_template 'new'
2197 2197
2198 2198 assert_select 'input[name=?][value="2"]:not(checked)', 'issue[watcher_user_ids][]'
2199 2199 assert_select 'input[name=?][value="3"][checked=checked]', 'issue[watcher_user_ids][]'
2200 2200 assert_select 'input[name=?][value="8"][checked=checked]', 'issue[watcher_user_ids][]'
2201 2201 end
2202 2202
2203 2203 def test_post_create_should_ignore_non_safe_attributes
2204 2204 @request.session[:user_id] = 2
2205 2205 assert_nothing_raised do
2206 2206 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2207 2207 end
2208 2208 end
2209 2209
2210 2210 def test_post_create_with_attachment
2211 2211 set_tmp_attachments_directory
2212 2212 @request.session[:user_id] = 2
2213 2213
2214 2214 assert_difference 'Issue.count' do
2215 2215 assert_difference 'Attachment.count' do
2216 2216 post :create, :project_id => 1,
2217 2217 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2218 2218 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2219 2219 end
2220 2220 end
2221 2221
2222 2222 issue = Issue.order('id DESC').first
2223 2223 attachment = Attachment.order('id DESC').first
2224 2224
2225 2225 assert_equal issue, attachment.container
2226 2226 assert_equal 2, attachment.author_id
2227 2227 assert_equal 'testfile.txt', attachment.filename
2228 2228 assert_equal 'text/plain', attachment.content_type
2229 2229 assert_equal 'test file', attachment.description
2230 2230 assert_equal 59, attachment.filesize
2231 2231 assert File.exists?(attachment.diskfile)
2232 2232 assert_equal 59, File.size(attachment.diskfile)
2233 2233 end
2234 2234
2235 2235 def test_post_create_with_attachment_should_notify_with_attachments
2236 2236 ActionMailer::Base.deliveries.clear
2237 2237 set_tmp_attachments_directory
2238 2238 @request.session[:user_id] = 2
2239 2239
2240 2240 with_settings :host_name => 'mydomain.foo', :protocol => 'http' do
2241 2241 assert_difference 'Issue.count' do
2242 2242 post :create, :project_id => 1,
2243 2243 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2244 2244 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2245 2245 end
2246 2246 end
2247 2247
2248 2248 assert_not_nil ActionMailer::Base.deliveries.last
2249 2249 assert_select_email do
2250 2250 assert_select 'a[href^=?]', 'http://mydomain.foo/attachments/download', 'testfile.txt'
2251 2251 end
2252 2252 end
2253 2253
2254 2254 def test_post_create_with_failure_should_save_attachments
2255 2255 set_tmp_attachments_directory
2256 2256 @request.session[:user_id] = 2
2257 2257
2258 2258 assert_no_difference 'Issue.count' do
2259 2259 assert_difference 'Attachment.count' do
2260 2260 post :create, :project_id => 1,
2261 2261 :issue => { :tracker_id => '1', :subject => '' },
2262 2262 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2263 2263 assert_response :success
2264 2264 assert_template 'new'
2265 2265 end
2266 2266 end
2267 2267
2268 2268 attachment = Attachment.order('id DESC').first
2269 2269 assert_equal 'testfile.txt', attachment.filename
2270 2270 assert File.exists?(attachment.diskfile)
2271 2271 assert_nil attachment.container
2272 2272
2273 2273 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2274 2274 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2275 2275 end
2276 2276
2277 2277 def test_post_create_with_failure_should_keep_saved_attachments
2278 2278 set_tmp_attachments_directory
2279 2279 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2280 2280 @request.session[:user_id] = 2
2281 2281
2282 2282 assert_no_difference 'Issue.count' do
2283 2283 assert_no_difference 'Attachment.count' do
2284 2284 post :create, :project_id => 1,
2285 2285 :issue => { :tracker_id => '1', :subject => '' },
2286 2286 :attachments => {'p0' => {'token' => attachment.token}}
2287 2287 assert_response :success
2288 2288 assert_template 'new'
2289 2289 end
2290 2290 end
2291 2291
2292 2292 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
2293 2293 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
2294 2294 end
2295 2295
2296 2296 def test_post_create_should_attach_saved_attachments
2297 2297 set_tmp_attachments_directory
2298 2298 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2299 2299 @request.session[:user_id] = 2
2300 2300
2301 2301 assert_difference 'Issue.count' do
2302 2302 assert_no_difference 'Attachment.count' do
2303 2303 post :create, :project_id => 1,
2304 2304 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2305 2305 :attachments => {'p0' => {'token' => attachment.token}}
2306 2306 assert_response 302
2307 2307 end
2308 2308 end
2309 2309
2310 2310 issue = Issue.order('id DESC').first
2311 2311 assert_equal 1, issue.attachments.count
2312 2312
2313 2313 attachment.reload
2314 2314 assert_equal issue, attachment.container
2315 2315 end
2316 2316
2317 2317 def setup_without_workflow_privilege
2318 2318 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2319 2319 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2320 2320 end
2321 2321 private :setup_without_workflow_privilege
2322 2322
2323 2323 test "without workflow privilege #new should propose default status only" do
2324 2324 setup_without_workflow_privilege
2325 2325 get :new, :project_id => 1
2326 2326 assert_response :success
2327 2327 assert_template 'new'
2328 2328
2329 2329 issue = assigns(:issue)
2330 2330 assert_not_nil issue.default_status
2331 2331
2332 2332 assert_select 'select[name=?]', 'issue[status_id]' do
2333 2333 assert_select 'option', 1
2334 2334 assert_select 'option[value=?]', issue.default_status.id.to_s
2335 2335 end
2336 2336 end
2337 2337
2338 2338 test "without workflow privilege #create should accept default status" do
2339 2339 setup_without_workflow_privilege
2340 2340 assert_difference 'Issue.count' do
2341 2341 post :create, :project_id => 1,
2342 2342 :issue => {:tracker_id => 1,
2343 2343 :subject => 'This is an issue',
2344 2344 :status_id => 1}
2345 2345 end
2346 2346 issue = Issue.order('id').last
2347 2347 assert_not_nil issue.default_status
2348 2348 assert_equal issue.default_status, issue.status
2349 2349 end
2350 2350
2351 2351 test "without workflow privilege #create should ignore unauthorized status" do
2352 2352 setup_without_workflow_privilege
2353 2353 assert_difference 'Issue.count' do
2354 2354 post :create, :project_id => 1,
2355 2355 :issue => {:tracker_id => 1,
2356 2356 :subject => 'This is an issue',
2357 2357 :status_id => 3}
2358 2358 end
2359 2359 issue = Issue.order('id').last
2360 2360 assert_not_nil issue.default_status
2361 2361 assert_equal issue.default_status, issue.status
2362 2362 end
2363 2363
2364 2364 test "without workflow privilege #update should ignore status change" do
2365 2365 setup_without_workflow_privilege
2366 2366 assert_difference 'Journal.count' do
2367 2367 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2368 2368 end
2369 2369 assert_equal 1, Issue.find(1).status_id
2370 2370 end
2371 2371
2372 2372 test "without workflow privilege #update ignore attributes changes" do
2373 2373 setup_without_workflow_privilege
2374 2374 assert_difference 'Journal.count' do
2375 2375 put :update, :id => 1,
2376 2376 :issue => {:subject => 'changed', :assigned_to_id => 2,
2377 2377 :notes => 'just trying'}
2378 2378 end
2379 2379 issue = Issue.find(1)
2380 2380 assert_equal "Can't print recipes", issue.subject
2381 2381 assert_nil issue.assigned_to
2382 2382 end
2383 2383
2384 2384 def setup_with_workflow_privilege
2385 2385 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2386 2386 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2387 2387 :old_status_id => 1, :new_status_id => 3)
2388 2388 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1,
2389 2389 :old_status_id => 1, :new_status_id => 4)
2390 2390 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2391 2391 end
2392 2392 private :setup_with_workflow_privilege
2393 2393
2394 2394 test "with workflow privilege #update should accept authorized status" do
2395 2395 setup_with_workflow_privilege
2396 2396 assert_difference 'Journal.count' do
2397 2397 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2398 2398 end
2399 2399 assert_equal 3, Issue.find(1).status_id
2400 2400 end
2401 2401
2402 2402 test "with workflow privilege #update should ignore unauthorized status" do
2403 2403 setup_with_workflow_privilege
2404 2404 assert_difference 'Journal.count' do
2405 2405 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2406 2406 end
2407 2407 assert_equal 1, Issue.find(1).status_id
2408 2408 end
2409 2409
2410 2410 test "with workflow privilege #update should accept authorized attributes changes" do
2411 2411 setup_with_workflow_privilege
2412 2412 assert_difference 'Journal.count' do
2413 2413 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2414 2414 end
2415 2415 issue = Issue.find(1)
2416 2416 assert_equal 2, issue.assigned_to_id
2417 2417 end
2418 2418
2419 2419 test "with workflow privilege #update should ignore unauthorized attributes changes" do
2420 2420 setup_with_workflow_privilege
2421 2421 assert_difference 'Journal.count' do
2422 2422 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2423 2423 end
2424 2424 issue = Issue.find(1)
2425 2425 assert_equal "Can't print recipes", issue.subject
2426 2426 end
2427 2427
2428 2428 def setup_with_workflow_privilege_and_edit_issues_permission
2429 2429 setup_with_workflow_privilege
2430 2430 Role.anonymous.add_permission! :add_issues, :edit_issues
2431 2431 end
2432 2432 private :setup_with_workflow_privilege_and_edit_issues_permission
2433 2433
2434 2434 test "with workflow privilege and :edit_issues permission should accept authorized status" do
2435 2435 setup_with_workflow_privilege_and_edit_issues_permission
2436 2436 assert_difference 'Journal.count' do
2437 2437 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2438 2438 end
2439 2439 assert_equal 3, Issue.find(1).status_id
2440 2440 end
2441 2441
2442 2442 test "with workflow privilege and :edit_issues permission should ignore unauthorized status" do
2443 2443 setup_with_workflow_privilege_and_edit_issues_permission
2444 2444 assert_difference 'Journal.count' do
2445 2445 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2446 2446 end
2447 2447 assert_equal 1, Issue.find(1).status_id
2448 2448 end
2449 2449
2450 2450 test "with workflow privilege and :edit_issues permission should accept authorized attributes changes" do
2451 2451 setup_with_workflow_privilege_and_edit_issues_permission
2452 2452 assert_difference 'Journal.count' do
2453 2453 put :update, :id => 1,
2454 2454 :issue => {:subject => 'changed', :assigned_to_id => 2,
2455 2455 :notes => 'just trying'}
2456 2456 end
2457 2457 issue = Issue.find(1)
2458 2458 assert_equal "changed", issue.subject
2459 2459 assert_equal 2, issue.assigned_to_id
2460 2460 end
2461 2461
2462 2462 def test_new_as_copy
2463 2463 @request.session[:user_id] = 2
2464 2464 get :new, :project_id => 1, :copy_from => 1
2465 2465
2466 2466 assert_response :success
2467 2467 assert_template 'new'
2468 2468
2469 2469 assert_not_nil assigns(:issue)
2470 2470 orig = Issue.find(1)
2471 2471 assert_equal 1, assigns(:issue).project_id
2472 2472 assert_equal orig.subject, assigns(:issue).subject
2473 2473 assert assigns(:issue).copy?
2474 2474
2475 2475 assert_select 'form[id=issue-form][action=/projects/ecookbook/issues]' do
2476 2476 assert_select 'select[name=?]', 'issue[project_id]' do
2477 2477 assert_select 'option[value="1"][selected=selected]', :text => 'eCookbook'
2478 2478 assert_select 'option[value="2"]:not([selected])', :text => 'OnlineStore'
2479 2479 end
2480 2480 assert_select 'input[name=copy_from][value="1"]'
2481 2481 end
2482 2482
2483 2483 # "New issue" menu item should not link to copy
2484 2484 assert_select '#main-menu a.new-issue[href="/projects/ecookbook/issues/new"]'
2485 2485 end
2486 2486
2487 2487 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2488 2488 @request.session[:user_id] = 2
2489 2489 issue = Issue.find(3)
2490 2490 assert issue.attachments.count > 0
2491 2491 get :new, :project_id => 1, :copy_from => 3
2492 2492
2493 2493 assert_select 'input[name=copy_attachments][type=checkbox][checked=checked][value="1"]'
2494 2494 end
2495 2495
2496 2496 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2497 2497 @request.session[:user_id] = 2
2498 2498 issue = Issue.find(3)
2499 2499 issue.attachments.delete_all
2500 2500 get :new, :project_id => 1, :copy_from => 3
2501 2501
2502 2502 assert_select 'input[name=copy_attachments]', 0
2503 2503 end
2504 2504
2505 2505 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2506 2506 @request.session[:user_id] = 2
2507 2507 issue = Issue.generate_with_descendants!
2508 2508 get :new, :project_id => 1, :copy_from => issue.id
2509 2509
2510 2510 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value="1"]'
2511 2511 end
2512 2512
2513 2513 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2514 2514 @request.session[:user_id] = 2
2515 2515 get :new, :project_id => 1, :copy_from => 99999
2516 2516 assert_response 404
2517 2517 end
2518 2518
2519 2519 def test_create_as_copy_on_different_project
2520 2520 @request.session[:user_id] = 2
2521 2521 assert_difference 'Issue.count' do
2522 2522 post :create, :project_id => 1, :copy_from => 1,
2523 2523 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2524 2524
2525 2525 assert_not_nil assigns(:issue)
2526 2526 assert assigns(:issue).copy?
2527 2527 end
2528 2528 issue = Issue.order('id DESC').first
2529 2529 assert_redirected_to "/issues/#{issue.id}"
2530 2530
2531 2531 assert_equal 2, issue.project_id
2532 2532 assert_equal 3, issue.tracker_id
2533 2533 assert_equal 'Copy', issue.subject
2534 2534 end
2535 2535
2536 2536 def test_create_as_copy_should_copy_attachments
2537 2537 @request.session[:user_id] = 2
2538 2538 issue = Issue.find(3)
2539 2539 count = issue.attachments.count
2540 2540 assert count > 0
2541 2541 assert_difference 'Issue.count' do
2542 2542 assert_difference 'Attachment.count', count do
2543 assert_difference 'Journal.count', 2 do
2544 post :create, :project_id => 1, :copy_from => 3,
2545 :issue => {:project_id => '1', :tracker_id => '3',
2546 :status_id => '1', :subject => 'Copy with attachments'},
2547 :copy_attachments => '1'
2548 end
2543 post :create, :project_id => 1, :copy_from => 3,
2544 :issue => {:project_id => '1', :tracker_id => '3',
2545 :status_id => '1', :subject => 'Copy with attachments'},
2546 :copy_attachments => '1'
2549 2547 end
2550 2548 end
2551 2549 copy = Issue.order('id DESC').first
2552 2550 assert_equal count, copy.attachments.count
2553 2551 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2554 2552 end
2555 2553
2556 2554 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2557 2555 @request.session[:user_id] = 2
2558 2556 issue = Issue.find(3)
2559 2557 count = issue.attachments.count
2560 2558 assert count > 0
2561 2559 assert_difference 'Issue.count' do
2562 2560 assert_no_difference 'Attachment.count' do
2563 assert_difference 'Journal.count', 2 do
2564 post :create, :project_id => 1, :copy_from => 3,
2565 :issue => {:project_id => '1', :tracker_id => '3',
2566 :status_id => '1', :subject => 'Copy with attachments'}
2567 end
2561 post :create, :project_id => 1, :copy_from => 3,
2562 :issue => {:project_id => '1', :tracker_id => '3',
2563 :status_id => '1', :subject => 'Copy with attachments'}
2568 2564 end
2569 2565 end
2570 2566 copy = Issue.order('id DESC').first
2571 2567 assert_equal 0, copy.attachments.count
2572 2568 end
2573 2569
2574 def test_create_as_copy_with_attachments_should_add_new_files
2570 def test_create_as_copy_with_attachments_should_also_add_new_files
2575 2571 @request.session[:user_id] = 2
2576 2572 issue = Issue.find(3)
2577 2573 count = issue.attachments.count
2578 2574 assert count > 0
2579 2575 assert_difference 'Issue.count' do
2580 2576 assert_difference 'Attachment.count', count + 1 do
2581 assert_difference 'Journal.count', 2 do
2582 post :create, :project_id => 1, :copy_from => 3,
2583 :issue => {:project_id => '1', :tracker_id => '3',
2584 :status_id => '1', :subject => 'Copy with attachments'},
2585 :copy_attachments => '1',
2586 :attachments => {'1' =>
2587 {'file' => uploaded_test_file('testfile.txt', 'text/plain'),
2588 'description' => 'test file'}}
2589 end
2577 post :create, :project_id => 1, :copy_from => 3,
2578 :issue => {:project_id => '1', :tracker_id => '3',
2579 :status_id => '1', :subject => 'Copy with attachments'},
2580 :copy_attachments => '1',
2581 :attachments => {'1' =>
2582 {'file' => uploaded_test_file('testfile.txt', 'text/plain'),
2583 'description' => 'test file'}}
2590 2584 end
2591 2585 end
2592 2586 copy = Issue.order('id DESC').first
2593 2587 assert_equal count + 1, copy.attachments.count
2594 2588 end
2595 2589
2596 2590 def test_create_as_copy_should_add_relation_with_copied_issue
2597 2591 @request.session[:user_id] = 2
2598 2592 assert_difference 'Issue.count' do
2599 2593 assert_difference 'IssueRelation.count' do
2600 post :create, :project_id => 1, :copy_from => 1,
2594 post :create, :project_id => 1, :copy_from => 1, :link_copy => '1',
2601 2595 :issue => {:project_id => '1', :tracker_id => '3',
2602 2596 :status_id => '1', :subject => 'Copy'}
2603 2597 end
2604 2598 end
2605 2599 copy = Issue.order('id DESC').first
2606 2600 assert_equal 1, copy.relations.size
2607 2601 end
2608 2602
2603 def test_create_as_copy_should_allow_not_to_add_relation_with_copied_issue
2604 @request.session[:user_id] = 2
2605 assert_difference 'Issue.count' do
2606 assert_no_difference 'IssueRelation.count' do
2607 post :create, :project_id => 1, :copy_from => 1,
2608 :issue => {:subject => 'Copy'}
2609 end
2610 end
2611 end
2612
2613 def test_create_as_copy_should_always_add_relation_with_copied_issue_by_setting
2614 with_settings :link_copied_issue => 'yes' do
2615 @request.session[:user_id] = 2
2616 assert_difference 'Issue.count' do
2617 assert_difference 'IssueRelation.count' do
2618 post :create, :project_id => 1, :copy_from => 1,
2619 :issue => {:subject => 'Copy'}
2620 end
2621 end
2622 end
2623 end
2624
2609 2625 def test_create_as_copy_should_copy_subtasks
2610 2626 @request.session[:user_id] = 2
2611 2627 issue = Issue.generate_with_descendants!
2612 2628 count = issue.descendants.count
2613 2629 assert_difference 'Issue.count', count + 1 do
2614 assert_difference 'Journal.count', (count + 1) * 2 do
2615 post :create, :project_id => 1, :copy_from => issue.id,
2616 :issue => {:project_id => '1', :tracker_id => '3',
2617 :status_id => '1', :subject => 'Copy with subtasks'},
2618 :copy_subtasks => '1'
2619 end
2630 post :create, :project_id => 1, :copy_from => issue.id,
2631 :issue => {:project_id => '1', :tracker_id => '3',
2632 :status_id => '1', :subject => 'Copy with subtasks'},
2633 :copy_subtasks => '1'
2620 2634 end
2621 2635 copy = Issue.where(:parent_id => nil).order('id DESC').first
2622 2636 assert_equal count, copy.descendants.count
2623 2637 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2624 2638 end
2625 2639
2626 2640 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2627 2641 @request.session[:user_id] = 2
2628 2642 issue = Issue.generate_with_descendants!
2629 2643 assert_difference 'Issue.count', 1 do
2630 assert_difference 'Journal.count', 2 do
2631 post :create, :project_id => 1, :copy_from => 3,
2632 :issue => {:project_id => '1', :tracker_id => '3',
2633 :status_id => '1', :subject => 'Copy with subtasks'}
2634 end
2644 post :create, :project_id => 1, :copy_from => 3,
2645 :issue => {:project_id => '1', :tracker_id => '3',
2646 :status_id => '1', :subject => 'Copy with subtasks'}
2635 2647 end
2636 2648 copy = Issue.where(:parent_id => nil).order('id DESC').first
2637 2649 assert_equal 0, copy.descendants.count
2638 2650 end
2639 2651
2640 2652 def test_create_as_copy_with_failure
2641 2653 @request.session[:user_id] = 2
2642 2654 post :create, :project_id => 1, :copy_from => 1,
2643 2655 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2644 2656
2645 2657 assert_response :success
2646 2658 assert_template 'new'
2647 2659
2648 2660 assert_not_nil assigns(:issue)
2649 2661 assert assigns(:issue).copy?
2650 2662
2651 2663 assert_select 'form#issue-form[action=/projects/ecookbook/issues]' do
2652 2664 assert_select 'select[name=?]', 'issue[project_id]' do
2653 2665 assert_select 'option[value="1"]:not([selected])', :text => 'eCookbook'
2654 2666 assert_select 'option[value="2"][selected=selected]', :text => 'OnlineStore'
2655 2667 end
2656 2668 assert_select 'input[name=copy_from][value="1"]'
2657 2669 end
2658 2670 end
2659 2671
2660 2672 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2661 2673 @request.session[:user_id] = 2
2662 2674 assert !User.find(2).member_of?(Project.find(4))
2663 2675
2664 2676 assert_difference 'Issue.count' do
2665 2677 post :create, :project_id => 1, :copy_from => 1,
2666 2678 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2667 2679 end
2668 2680 issue = Issue.order('id DESC').first
2669 2681 assert_equal 1, issue.project_id
2670 2682 end
2671 2683
2672 2684 def test_get_edit
2673 2685 @request.session[:user_id] = 2
2674 2686 get :edit, :id => 1
2675 2687 assert_response :success
2676 2688 assert_template 'edit'
2677 2689 assert_not_nil assigns(:issue)
2678 2690 assert_equal Issue.find(1), assigns(:issue)
2679 2691
2680 2692 # Be sure we don't display inactive IssuePriorities
2681 2693 assert ! IssuePriority.find(15).active?
2682 2694 assert_select 'select[name=?]', 'issue[priority_id]' do
2683 2695 assert_select 'option[value="15"]', 0
2684 2696 end
2685 2697 end
2686 2698
2687 2699 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2688 2700 @request.session[:user_id] = 2
2689 2701 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2690 2702
2691 2703 get :edit, :id => 1
2692 2704 assert_select 'input[name=?]', 'time_entry[hours]'
2693 2705 end
2694 2706
2695 2707 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2696 2708 @request.session[:user_id] = 2
2697 2709 Role.find_by_name('Manager').remove_permission! :log_time
2698 2710
2699 2711 get :edit, :id => 1
2700 2712 assert_select 'input[name=?]', 'time_entry[hours]', 0
2701 2713 end
2702 2714
2703 2715 def test_get_edit_with_params
2704 2716 @request.session[:user_id] = 2
2705 2717 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2706 2718 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => 10 }
2707 2719 assert_response :success
2708 2720 assert_template 'edit'
2709 2721
2710 2722 issue = assigns(:issue)
2711 2723 assert_not_nil issue
2712 2724
2713 2725 assert_equal 5, issue.status_id
2714 2726 assert_select 'select[name=?]', 'issue[status_id]' do
2715 2727 assert_select 'option[value="5"][selected=selected]', :text => 'Closed'
2716 2728 end
2717 2729
2718 2730 assert_equal 7, issue.priority_id
2719 2731 assert_select 'select[name=?]', 'issue[priority_id]' do
2720 2732 assert_select 'option[value="7"][selected=selected]', :text => 'Urgent'
2721 2733 end
2722 2734
2723 2735 assert_select 'input[name=?][value=2.5]', 'time_entry[hours]'
2724 2736 assert_select 'select[name=?]', 'time_entry[activity_id]' do
2725 2737 assert_select 'option[value="10"][selected=selected]', :text => 'Development'
2726 2738 end
2727 2739 assert_select 'input[name=?][value=test_get_edit_with_params]', 'time_entry[comments]'
2728 2740 end
2729 2741
2730 2742 def test_get_edit_with_multi_custom_field
2731 2743 field = CustomField.find(1)
2732 2744 field.update_attribute :multiple, true
2733 2745 issue = Issue.find(1)
2734 2746 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2735 2747 issue.save!
2736 2748
2737 2749 @request.session[:user_id] = 2
2738 2750 get :edit, :id => 1
2739 2751 assert_response :success
2740 2752 assert_template 'edit'
2741 2753
2742 2754 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
2743 2755 assert_select 'option', 3
2744 2756 assert_select 'option[value=MySQL][selected=selected]'
2745 2757 assert_select 'option[value=Oracle][selected=selected]'
2746 2758 assert_select 'option[value=PostgreSQL]:not([selected])'
2747 2759 end
2748 2760 end
2749 2761
2750 2762 def test_update_form_for_existing_issue
2751 2763 @request.session[:user_id] = 2
2752 2764 xhr :put, :update_form, :project_id => 1,
2753 2765 :id => 1,
2754 2766 :issue => {:tracker_id => 2,
2755 2767 :subject => 'This is the test_new issue',
2756 2768 :description => 'This is the description',
2757 2769 :priority_id => 5}
2758 2770 assert_response :success
2759 2771 assert_equal 'text/javascript', response.content_type
2760 2772 assert_template 'update_form'
2761 2773 assert_template :partial => '_form'
2762 2774
2763 2775 issue = assigns(:issue)
2764 2776 assert_kind_of Issue, issue
2765 2777 assert_equal 1, issue.id
2766 2778 assert_equal 1, issue.project_id
2767 2779 assert_equal 2, issue.tracker_id
2768 2780 assert_equal 'This is the test_new issue', issue.subject
2769 2781 end
2770 2782
2771 2783 def test_update_form_for_existing_issue_should_keep_issue_author
2772 2784 @request.session[:user_id] = 3
2773 2785 xhr :put, :update_form, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2774 2786 assert_response :success
2775 2787 assert_equal 'text/javascript', response.content_type
2776 2788
2777 2789 issue = assigns(:issue)
2778 2790 assert_equal User.find(2), issue.author
2779 2791 assert_equal 2, issue.author_id
2780 2792 assert_not_equal User.current, issue.author
2781 2793 end
2782 2794
2783 2795 def test_update_form_for_existing_issue_should_propose_transitions_based_on_initial_status
2784 2796 @request.session[:user_id] = 2
2785 2797 WorkflowTransition.delete_all
2786 2798 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2787 2799 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2788 2800 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2789 2801
2790 2802 xhr :put, :update_form, :project_id => 1,
2791 2803 :id => 2,
2792 2804 :issue => {:tracker_id => 2,
2793 2805 :status_id => 5,
2794 2806 :subject => 'This is an issue'}
2795 2807
2796 2808 assert_equal 5, assigns(:issue).status_id
2797 2809 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2798 2810 end
2799 2811
2800 2812 def test_update_form_for_existing_issue_with_project_change
2801 2813 @request.session[:user_id] = 2
2802 2814 xhr :put, :update_form, :project_id => 1,
2803 2815 :id => 1,
2804 2816 :issue => {:project_id => 2,
2805 2817 :tracker_id => 2,
2806 2818 :subject => 'This is the test_new issue',
2807 2819 :description => 'This is the description',
2808 2820 :priority_id => 5}
2809 2821 assert_response :success
2810 2822 assert_template :partial => '_form'
2811 2823
2812 2824 issue = assigns(:issue)
2813 2825 assert_kind_of Issue, issue
2814 2826 assert_equal 1, issue.id
2815 2827 assert_equal 2, issue.project_id
2816 2828 assert_equal 2, issue.tracker_id
2817 2829 assert_equal 'This is the test_new issue', issue.subject
2818 2830 end
2819 2831
2820 2832 def test_update_form_should_propose_default_status_for_existing_issue
2821 2833 @request.session[:user_id] = 2
2822 2834 WorkflowTransition.delete_all
2823 2835 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
2824 2836
2825 2837 xhr :put, :update_form, :project_id => 1, :id => 2
2826 2838 assert_response :success
2827 2839 assert_equal [2,3], assigns(:allowed_statuses).map(&:id).sort
2828 2840 end
2829 2841
2830 2842 def test_put_update_without_custom_fields_param
2831 2843 @request.session[:user_id] = 2
2832 2844 ActionMailer::Base.deliveries.clear
2833 2845
2834 2846 issue = Issue.find(1)
2835 2847 assert_equal '125', issue.custom_value_for(2).value
2836 2848 old_subject = issue.subject
2837 2849 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2838 2850
2839 2851 assert_difference('Journal.count') do
2840 2852 assert_difference('JournalDetail.count', 2) do
2841 2853 put :update, :id => 1, :issue => {:subject => new_subject,
2842 2854 :priority_id => '6',
2843 2855 :category_id => '1' # no change
2844 2856 }
2845 2857 end
2846 2858 end
2847 2859 assert_redirected_to :action => 'show', :id => '1'
2848 2860 issue.reload
2849 2861 assert_equal new_subject, issue.subject
2850 2862 # Make sure custom fields were not cleared
2851 2863 assert_equal '125', issue.custom_value_for(2).value
2852 2864
2853 2865 mail = ActionMailer::Base.deliveries.last
2854 2866 assert_not_nil mail
2855 2867 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2856 2868 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2857 2869 end
2858 2870
2859 2871 def test_put_update_with_project_change
2860 2872 @request.session[:user_id] = 2
2861 2873 ActionMailer::Base.deliveries.clear
2862 2874
2863 2875 assert_difference('Journal.count') do
2864 2876 assert_difference('JournalDetail.count', 3) do
2865 2877 put :update, :id => 1, :issue => {:project_id => '2',
2866 2878 :tracker_id => '1', # no change
2867 2879 :priority_id => '6',
2868 2880 :category_id => '3'
2869 2881 }
2870 2882 end
2871 2883 end
2872 2884 assert_redirected_to :action => 'show', :id => '1'
2873 2885 issue = Issue.find(1)
2874 2886 assert_equal 2, issue.project_id
2875 2887 assert_equal 1, issue.tracker_id
2876 2888 assert_equal 6, issue.priority_id
2877 2889 assert_equal 3, issue.category_id
2878 2890
2879 2891 mail = ActionMailer::Base.deliveries.last
2880 2892 assert_not_nil mail
2881 2893 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2882 2894 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2883 2895 end
2884 2896
2885 2897 def test_put_update_with_tracker_change
2886 2898 @request.session[:user_id] = 2
2887 2899 ActionMailer::Base.deliveries.clear
2888 2900
2889 2901 assert_difference('Journal.count') do
2890 2902 assert_difference('JournalDetail.count', 2) do
2891 2903 put :update, :id => 1, :issue => {:project_id => '1',
2892 2904 :tracker_id => '2',
2893 2905 :priority_id => '6'
2894 2906 }
2895 2907 end
2896 2908 end
2897 2909 assert_redirected_to :action => 'show', :id => '1'
2898 2910 issue = Issue.find(1)
2899 2911 assert_equal 1, issue.project_id
2900 2912 assert_equal 2, issue.tracker_id
2901 2913 assert_equal 6, issue.priority_id
2902 2914 assert_equal 1, issue.category_id
2903 2915
2904 2916 mail = ActionMailer::Base.deliveries.last
2905 2917 assert_not_nil mail
2906 2918 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2907 2919 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2908 2920 end
2909 2921
2910 2922 def test_put_update_with_custom_field_change
2911 2923 @request.session[:user_id] = 2
2912 2924 issue = Issue.find(1)
2913 2925 assert_equal '125', issue.custom_value_for(2).value
2914 2926
2915 2927 assert_difference('Journal.count') do
2916 2928 assert_difference('JournalDetail.count', 3) do
2917 2929 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2918 2930 :priority_id => '6',
2919 2931 :category_id => '1', # no change
2920 2932 :custom_field_values => { '2' => 'New custom value' }
2921 2933 }
2922 2934 end
2923 2935 end
2924 2936 assert_redirected_to :action => 'show', :id => '1'
2925 2937 issue.reload
2926 2938 assert_equal 'New custom value', issue.custom_value_for(2).value
2927 2939
2928 2940 mail = ActionMailer::Base.deliveries.last
2929 2941 assert_not_nil mail
2930 2942 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2931 2943 end
2932 2944
2933 2945 def test_put_update_with_multi_custom_field_change
2934 2946 field = CustomField.find(1)
2935 2947 field.update_attribute :multiple, true
2936 2948 issue = Issue.find(1)
2937 2949 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2938 2950 issue.save!
2939 2951
2940 2952 @request.session[:user_id] = 2
2941 2953 assert_difference('Journal.count') do
2942 2954 assert_difference('JournalDetail.count', 3) do
2943 2955 put :update, :id => 1,
2944 2956 :issue => {
2945 2957 :subject => 'Custom field change',
2946 2958 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2947 2959 }
2948 2960 end
2949 2961 end
2950 2962 assert_redirected_to :action => 'show', :id => '1'
2951 2963 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2952 2964 end
2953 2965
2954 2966 def test_put_update_with_status_and_assignee_change
2955 2967 issue = Issue.find(1)
2956 2968 assert_equal 1, issue.status_id
2957 2969 @request.session[:user_id] = 2
2958 2970 assert_difference('TimeEntry.count', 0) do
2959 2971 put :update,
2960 2972 :id => 1,
2961 2973 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2962 2974 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2963 2975 end
2964 2976 assert_redirected_to :action => 'show', :id => '1'
2965 2977 issue.reload
2966 2978 assert_equal 2, issue.status_id
2967 2979 j = Journal.order('id DESC').first
2968 2980 assert_equal 'Assigned to dlopper', j.notes
2969 2981 assert_equal 2, j.details.size
2970 2982
2971 2983 mail = ActionMailer::Base.deliveries.last
2972 2984 assert_mail_body_match "Status changed from New to Assigned", mail
2973 2985 # subject should contain the new status
2974 2986 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2975 2987 end
2976 2988
2977 2989 def test_put_update_with_note_only
2978 2990 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2979 2991 # anonymous user
2980 2992 put :update,
2981 2993 :id => 1,
2982 2994 :issue => { :notes => notes }
2983 2995 assert_redirected_to :action => 'show', :id => '1'
2984 2996 j = Journal.order('id DESC').first
2985 2997 assert_equal notes, j.notes
2986 2998 assert_equal 0, j.details.size
2987 2999 assert_equal User.anonymous, j.user
2988 3000
2989 3001 mail = ActionMailer::Base.deliveries.last
2990 3002 assert_mail_body_match notes, mail
2991 3003 end
2992 3004
2993 3005 def test_put_update_with_private_note_only
2994 3006 notes = 'Private note'
2995 3007 @request.session[:user_id] = 2
2996 3008
2997 3009 assert_difference 'Journal.count' do
2998 3010 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
2999 3011 assert_redirected_to :action => 'show', :id => '1'
3000 3012 end
3001 3013
3002 3014 j = Journal.order('id DESC').first
3003 3015 assert_equal notes, j.notes
3004 3016 assert_equal true, j.private_notes
3005 3017 end
3006 3018
3007 3019 def test_put_update_with_private_note_and_changes
3008 3020 notes = 'Private note'
3009 3021 @request.session[:user_id] = 2
3010 3022
3011 3023 assert_difference 'Journal.count', 2 do
3012 3024 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
3013 3025 assert_redirected_to :action => 'show', :id => '1'
3014 3026 end
3015 3027
3016 3028 j = Journal.order('id DESC').first
3017 3029 assert_equal notes, j.notes
3018 3030 assert_equal true, j.private_notes
3019 3031 assert_equal 0, j.details.count
3020 3032
3021 3033 j = Journal.order('id DESC').offset(1).first
3022 3034 assert_nil j.notes
3023 3035 assert_equal false, j.private_notes
3024 3036 assert_equal 1, j.details.count
3025 3037 end
3026 3038
3027 3039 def test_put_update_with_note_and_spent_time
3028 3040 @request.session[:user_id] = 2
3029 3041 spent_hours_before = Issue.find(1).spent_hours
3030 3042 assert_difference('TimeEntry.count') do
3031 3043 put :update,
3032 3044 :id => 1,
3033 3045 :issue => { :notes => '2.5 hours added' },
3034 3046 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
3035 3047 end
3036 3048 assert_redirected_to :action => 'show', :id => '1'
3037 3049
3038 3050 issue = Issue.find(1)
3039 3051
3040 3052 j = Journal.order('id DESC').first
3041 3053 assert_equal '2.5 hours added', j.notes
3042 3054 assert_equal 0, j.details.size
3043 3055
3044 3056 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
3045 3057 assert_not_nil t
3046 3058 assert_equal 2.5, t.hours
3047 3059 assert_equal spent_hours_before + 2.5, issue.spent_hours
3048 3060 end
3049 3061
3050 3062 def test_put_update_should_preserve_parent_issue_even_if_not_visible
3051 3063 parent = Issue.generate!(:project_id => 1, :is_private => true)
3052 3064 issue = Issue.generate!(:parent_issue_id => parent.id)
3053 3065 assert !parent.visible?(User.find(3))
3054 3066 @request.session[:user_id] = 3
3055 3067
3056 3068 get :edit, :id => issue.id
3057 3069 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', parent.id.to_s
3058 3070
3059 3071 put :update, :id => issue.id, :issue => {:subject => 'New subject', :parent_issue_id => parent.id.to_s}
3060 3072 assert_response 302
3061 3073 assert_equal parent, issue.parent
3062 3074 end
3063 3075
3064 3076 def test_put_update_with_attachment_only
3065 3077 set_tmp_attachments_directory
3066 3078
3067 3079 # Delete all fixtured journals, a race condition can occur causing the wrong
3068 3080 # journal to get fetched in the next find.
3069 3081 Journal.delete_all
3070 3082
3071 3083 # anonymous user
3072 3084 assert_difference 'Attachment.count' do
3073 3085 put :update, :id => 1,
3074 3086 :issue => {:notes => ''},
3075 3087 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3076 3088 end
3077 3089
3078 3090 assert_redirected_to :action => 'show', :id => '1'
3079 3091 j = Issue.find(1).journals.reorder('id DESC').first
3080 3092 assert j.notes.blank?
3081 3093 assert_equal 1, j.details.size
3082 3094 assert_equal 'testfile.txt', j.details.first.value
3083 3095 assert_equal User.anonymous, j.user
3084 3096
3085 3097 attachment = Attachment.order('id DESC').first
3086 3098 assert_equal Issue.find(1), attachment.container
3087 3099 assert_equal User.anonymous, attachment.author
3088 3100 assert_equal 'testfile.txt', attachment.filename
3089 3101 assert_equal 'text/plain', attachment.content_type
3090 3102 assert_equal 'test file', attachment.description
3091 3103 assert_equal 59, attachment.filesize
3092 3104 assert File.exists?(attachment.diskfile)
3093 3105 assert_equal 59, File.size(attachment.diskfile)
3094 3106
3095 3107 mail = ActionMailer::Base.deliveries.last
3096 3108 assert_mail_body_match 'testfile.txt', mail
3097 3109 end
3098 3110
3099 3111 def test_put_update_with_failure_should_save_attachments
3100 3112 set_tmp_attachments_directory
3101 3113 @request.session[:user_id] = 2
3102 3114
3103 3115 assert_no_difference 'Journal.count' do
3104 3116 assert_difference 'Attachment.count' do
3105 3117 put :update, :id => 1,
3106 3118 :issue => { :subject => '' },
3107 3119 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
3108 3120 assert_response :success
3109 3121 assert_template 'edit'
3110 3122 end
3111 3123 end
3112 3124
3113 3125 attachment = Attachment.order('id DESC').first
3114 3126 assert_equal 'testfile.txt', attachment.filename
3115 3127 assert File.exists?(attachment.diskfile)
3116 3128 assert_nil attachment.container
3117 3129
3118 3130 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3119 3131 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3120 3132 end
3121 3133
3122 3134 def test_put_update_with_failure_should_keep_saved_attachments
3123 3135 set_tmp_attachments_directory
3124 3136 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3125 3137 @request.session[:user_id] = 2
3126 3138
3127 3139 assert_no_difference 'Journal.count' do
3128 3140 assert_no_difference 'Attachment.count' do
3129 3141 put :update, :id => 1,
3130 3142 :issue => { :subject => '' },
3131 3143 :attachments => {'p0' => {'token' => attachment.token}}
3132 3144 assert_response :success
3133 3145 assert_template 'edit'
3134 3146 end
3135 3147 end
3136 3148
3137 3149 assert_select 'input[name=?][value=?]', 'attachments[p0][token]', attachment.token
3138 3150 assert_select 'input[name=?][value=?]', 'attachments[p0][filename]', 'testfile.txt'
3139 3151 end
3140 3152
3141 3153 def test_put_update_should_attach_saved_attachments
3142 3154 set_tmp_attachments_directory
3143 3155 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
3144 3156 @request.session[:user_id] = 2
3145 3157
3146 3158 assert_difference 'Journal.count' do
3147 3159 assert_difference 'JournalDetail.count' do
3148 3160 assert_no_difference 'Attachment.count' do
3149 3161 put :update, :id => 1,
3150 3162 :issue => {:notes => 'Attachment added'},
3151 3163 :attachments => {'p0' => {'token' => attachment.token}}
3152 3164 assert_redirected_to '/issues/1'
3153 3165 end
3154 3166 end
3155 3167 end
3156 3168
3157 3169 attachment.reload
3158 3170 assert_equal Issue.find(1), attachment.container
3159 3171
3160 3172 journal = Journal.order('id DESC').first
3161 3173 assert_equal 1, journal.details.size
3162 3174 assert_equal 'testfile.txt', journal.details.first.value
3163 3175 end
3164 3176
3165 3177 def test_put_update_with_attachment_that_fails_to_save
3166 3178 set_tmp_attachments_directory
3167 3179
3168 3180 # anonymous user
3169 3181 with_settings :attachment_max_size => 0 do
3170 3182 put :update,
3171 3183 :id => 1,
3172 3184 :issue => {:notes => ''},
3173 3185 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
3174 3186 assert_redirected_to :action => 'show', :id => '1'
3175 3187 assert_equal '1 file(s) could not be saved.', flash[:warning]
3176 3188 end
3177 3189 end
3178 3190
3179 3191 def test_put_update_with_no_change
3180 3192 issue = Issue.find(1)
3181 3193 issue.journals.clear
3182 3194 ActionMailer::Base.deliveries.clear
3183 3195
3184 3196 put :update,
3185 3197 :id => 1,
3186 3198 :issue => {:notes => ''}
3187 3199 assert_redirected_to :action => 'show', :id => '1'
3188 3200
3189 3201 issue.reload
3190 3202 assert issue.journals.empty?
3191 3203 # No email should be sent
3192 3204 assert ActionMailer::Base.deliveries.empty?
3193 3205 end
3194 3206
3195 3207 def test_put_update_should_send_a_notification
3196 3208 @request.session[:user_id] = 2
3197 3209 ActionMailer::Base.deliveries.clear
3198 3210 issue = Issue.find(1)
3199 3211 old_subject = issue.subject
3200 3212 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3201 3213
3202 3214 put :update, :id => 1, :issue => {:subject => new_subject,
3203 3215 :priority_id => '6',
3204 3216 :category_id => '1' # no change
3205 3217 }
3206 3218 assert_equal 1, ActionMailer::Base.deliveries.size
3207 3219 end
3208 3220
3209 3221 def test_put_update_with_invalid_spent_time_hours_only
3210 3222 @request.session[:user_id] = 2
3211 3223 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3212 3224
3213 3225 assert_no_difference('Journal.count') do
3214 3226 put :update,
3215 3227 :id => 1,
3216 3228 :issue => {:notes => notes},
3217 3229 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3218 3230 end
3219 3231 assert_response :success
3220 3232 assert_template 'edit'
3221 3233
3222 3234 assert_select_error /Activity #{ESCAPED_CANT} be blank/
3223 3235 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3224 3236 assert_select 'input[name=?][value=?]', 'time_entry[hours]', '2z'
3225 3237 end
3226 3238
3227 3239 def test_put_update_with_invalid_spent_time_comments_only
3228 3240 @request.session[:user_id] = 2
3229 3241 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3230 3242
3231 3243 assert_no_difference('Journal.count') do
3232 3244 put :update,
3233 3245 :id => 1,
3234 3246 :issue => {:notes => notes},
3235 3247 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3236 3248 end
3237 3249 assert_response :success
3238 3250 assert_template 'edit'
3239 3251
3240 3252 assert_select_error /Activity #{ESCAPED_CANT} be blank/
3241 3253 assert_select_error /Hours #{ESCAPED_CANT} be blank/
3242 3254 assert_select 'textarea[name=?]', 'issue[notes]', :text => notes
3243 3255 assert_select 'input[name=?][value=?]', 'time_entry[comments]', 'this is my comment'
3244 3256 end
3245 3257
3246 3258 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3247 3259 issue = Issue.find(2)
3248 3260 @request.session[:user_id] = 2
3249 3261
3250 3262 put :update,
3251 3263 :id => issue.id,
3252 3264 :issue => {
3253 3265 :fixed_version_id => 4
3254 3266 }
3255 3267
3256 3268 assert_response :redirect
3257 3269 issue.reload
3258 3270 assert_equal 4, issue.fixed_version_id
3259 3271 assert_not_equal issue.project_id, issue.fixed_version.project_id
3260 3272 end
3261 3273
3262 3274 def test_put_update_should_redirect_back_using_the_back_url_parameter
3263 3275 issue = Issue.find(2)
3264 3276 @request.session[:user_id] = 2
3265 3277
3266 3278 put :update,
3267 3279 :id => issue.id,
3268 3280 :issue => {
3269 3281 :fixed_version_id => 4
3270 3282 },
3271 3283 :back_url => '/issues'
3272 3284
3273 3285 assert_response :redirect
3274 3286 assert_redirected_to '/issues'
3275 3287 end
3276 3288
3277 3289 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3278 3290 issue = Issue.find(2)
3279 3291 @request.session[:user_id] = 2
3280 3292
3281 3293 put :update,
3282 3294 :id => issue.id,
3283 3295 :issue => {
3284 3296 :fixed_version_id => 4
3285 3297 },
3286 3298 :back_url => 'http://google.com'
3287 3299
3288 3300 assert_response :redirect
3289 3301 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3290 3302 end
3291 3303
3292 3304 def test_get_bulk_edit
3293 3305 @request.session[:user_id] = 2
3294 3306 get :bulk_edit, :ids => [1, 2]
3295 3307 assert_response :success
3296 3308 assert_template 'bulk_edit'
3297 3309
3298 3310 assert_select 'ul#bulk-selection' do
3299 3311 assert_select 'li', 2
3300 3312 assert_select 'li a', :text => 'Bug #1'
3301 3313 end
3302 3314
3303 3315 assert_select 'form#bulk_edit_form[action=?]', '/issues/bulk_update' do
3304 3316 assert_select 'input[name=?]', 'ids[]', 2
3305 3317 assert_select 'input[name=?][value="1"][type=hidden]', 'ids[]'
3306 3318
3307 3319 assert_select 'select[name=?]', 'issue[project_id]'
3308 3320 assert_select 'input[name=?]', 'issue[parent_issue_id]'
3309 3321
3310 3322 # Project specific custom field, date type
3311 3323 field = CustomField.find(9)
3312 3324 assert !field.is_for_all?
3313 3325 assert_equal 'date', field.field_format
3314 3326 assert_select 'input[name=?]', 'issue[custom_field_values][9]'
3315 3327
3316 3328 # System wide custom field
3317 3329 assert CustomField.find(1).is_for_all?
3318 3330 assert_select 'select[name=?]', 'issue[custom_field_values][1]'
3319 3331
3320 3332 # Be sure we don't display inactive IssuePriorities
3321 3333 assert ! IssuePriority.find(15).active?
3322 3334 assert_select 'select[name=?]', 'issue[priority_id]' do
3323 3335 assert_select 'option[value="15"]', 0
3324 3336 end
3325 3337 end
3326 3338 end
3327 3339
3328 3340 def test_get_bulk_edit_on_different_projects
3329 3341 @request.session[:user_id] = 2
3330 3342 get :bulk_edit, :ids => [1, 2, 6]
3331 3343 assert_response :success
3332 3344 assert_template 'bulk_edit'
3333 3345
3334 3346 # Can not set issues from different projects as children of an issue
3335 3347 assert_select 'input[name=?]', 'issue[parent_issue_id]', 0
3336 3348
3337 3349 # Project specific custom field, date type
3338 3350 field = CustomField.find(9)
3339 3351 assert !field.is_for_all?
3340 3352 assert !field.project_ids.include?(Issue.find(6).project_id)
3341 3353 assert_select 'input[name=?]', 'issue[custom_field_values][9]', 0
3342 3354 end
3343 3355
3344 3356 def test_get_bulk_edit_with_user_custom_field
3345 3357 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3346 3358
3347 3359 @request.session[:user_id] = 2
3348 3360 get :bulk_edit, :ids => [1, 2]
3349 3361 assert_response :success
3350 3362 assert_template 'bulk_edit'
3351 3363
3352 3364 assert_select 'select.user_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3353 3365 assert_select 'option', Project.find(1).users.count + 2 # "no change" + "none" options
3354 3366 end
3355 3367 end
3356 3368
3357 3369 def test_get_bulk_edit_with_version_custom_field
3358 3370 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3359 3371
3360 3372 @request.session[:user_id] = 2
3361 3373 get :bulk_edit, :ids => [1, 2]
3362 3374 assert_response :success
3363 3375 assert_template 'bulk_edit'
3364 3376
3365 3377 assert_select 'select.version_cf[name=?]', "issue[custom_field_values][#{field.id}]" do
3366 3378 assert_select 'option', Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3367 3379 end
3368 3380 end
3369 3381
3370 3382 def test_get_bulk_edit_with_multi_custom_field
3371 3383 field = CustomField.find(1)
3372 3384 field.update_attribute :multiple, true
3373 3385
3374 3386 @request.session[:user_id] = 2
3375 3387 get :bulk_edit, :ids => [1, 2]
3376 3388 assert_response :success
3377 3389 assert_template 'bulk_edit'
3378 3390
3379 3391 assert_select 'select[name=?]', 'issue[custom_field_values][1][]' do
3380 3392 assert_select 'option', field.possible_values.size + 1 # "none" options
3381 3393 end
3382 3394 end
3383 3395
3384 3396 def test_bulk_edit_should_propose_to_clear_text_custom_fields
3385 3397 @request.session[:user_id] = 2
3386 3398 get :bulk_edit, :ids => [1, 3]
3387 3399 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', '__none__'
3388 3400 end
3389 3401
3390 3402 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3391 3403 WorkflowTransition.delete_all
3392 3404 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3393 3405 :old_status_id => 1, :new_status_id => 1)
3394 3406 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3395 3407 :old_status_id => 1, :new_status_id => 3)
3396 3408 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
3397 3409 :old_status_id => 1, :new_status_id => 4)
3398 3410 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3399 3411 :old_status_id => 2, :new_status_id => 1)
3400 3412 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3401 3413 :old_status_id => 2, :new_status_id => 3)
3402 3414 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
3403 3415 :old_status_id => 2, :new_status_id => 5)
3404 3416 @request.session[:user_id] = 2
3405 3417 get :bulk_edit, :ids => [1, 2]
3406 3418
3407 3419 assert_response :success
3408 3420 statuses = assigns(:available_statuses)
3409 3421 assert_not_nil statuses
3410 3422 assert_equal [1, 3], statuses.map(&:id).sort
3411 3423
3412 3424 assert_select 'select[name=?]', 'issue[status_id]' do
3413 3425 assert_select 'option', 3 # 2 statuses + "no change" option
3414 3426 end
3415 3427 end
3416 3428
3417 3429 def test_bulk_edit_should_propose_target_project_open_shared_versions
3418 3430 @request.session[:user_id] = 2
3419 3431 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3420 3432 assert_response :success
3421 3433 assert_template 'bulk_edit'
3422 3434 assert_equal Project.find(1).shared_versions.open.to_a.sort, assigns(:versions).sort
3423 3435
3424 3436 assert_select 'select[name=?]', 'issue[fixed_version_id]' do
3425 3437 assert_select 'option', :text => '2.0'
3426 3438 end
3427 3439 end
3428 3440
3429 3441 def test_bulk_edit_should_propose_target_project_categories
3430 3442 @request.session[:user_id] = 2
3431 3443 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3432 3444 assert_response :success
3433 3445 assert_template 'bulk_edit'
3434 3446 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3435 3447
3436 3448 assert_select 'select[name=?]', 'issue[category_id]' do
3437 3449 assert_select 'option', :text => 'Recipes'
3438 3450 end
3439 3451 end
3440 3452
3441 3453 def test_bulk_update
3442 3454 @request.session[:user_id] = 2
3443 3455 # update issues priority
3444 3456 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3445 3457 :issue => {:priority_id => 7,
3446 3458 :assigned_to_id => '',
3447 3459 :custom_field_values => {'2' => ''}}
3448 3460
3449 3461 assert_response 302
3450 3462 # check that the issues were updated
3451 3463 assert_equal [7, 7], Issue.where(:id =>[1, 2]).collect {|i| i.priority.id}
3452 3464
3453 3465 issue = Issue.find(1)
3454 3466 journal = issue.journals.reorder('created_on DESC').first
3455 3467 assert_equal '125', issue.custom_value_for(2).value
3456 3468 assert_equal 'Bulk editing', journal.notes
3457 3469 assert_equal 1, journal.details.size
3458 3470 end
3459 3471
3460 3472 def test_bulk_update_with_group_assignee
3461 3473 group = Group.find(11)
3462 3474 project = Project.find(1)
3463 3475 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3464 3476
3465 3477 @request.session[:user_id] = 2
3466 3478 # update issues assignee
3467 3479 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3468 3480 :issue => {:priority_id => '',
3469 3481 :assigned_to_id => group.id,
3470 3482 :custom_field_values => {'2' => ''}}
3471 3483
3472 3484 assert_response 302
3473 3485 assert_equal [group, group], Issue.where(:id => [1, 2]).collect {|i| i.assigned_to}
3474 3486 end
3475 3487
3476 3488 def test_bulk_update_on_different_projects
3477 3489 @request.session[:user_id] = 2
3478 3490 # update issues priority
3479 3491 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3480 3492 :issue => {:priority_id => 7,
3481 3493 :assigned_to_id => '',
3482 3494 :custom_field_values => {'2' => ''}}
3483 3495
3484 3496 assert_response 302
3485 3497 # check that the issues were updated
3486 3498 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3487 3499
3488 3500 issue = Issue.find(1)
3489 3501 journal = issue.journals.reorder('created_on DESC').first
3490 3502 assert_equal '125', issue.custom_value_for(2).value
3491 3503 assert_equal 'Bulk editing', journal.notes
3492 3504 assert_equal 1, journal.details.size
3493 3505 end
3494 3506
3495 3507 def test_bulk_update_on_different_projects_without_rights
3496 3508 @request.session[:user_id] = 3
3497 3509 user = User.find(3)
3498 3510 action = { :controller => "issues", :action => "bulk_update" }
3499 3511 assert user.allowed_to?(action, Issue.find(1).project)
3500 3512 assert ! user.allowed_to?(action, Issue.find(6).project)
3501 3513 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3502 3514 :issue => {:priority_id => 7,
3503 3515 :assigned_to_id => '',
3504 3516 :custom_field_values => {'2' => ''}}
3505 3517 assert_response 403
3506 3518 assert_not_equal "Bulk should fail", Journal.last.notes
3507 3519 end
3508 3520
3509 3521 def test_bullk_update_should_send_a_notification
3510 3522 @request.session[:user_id] = 2
3511 3523 ActionMailer::Base.deliveries.clear
3512 3524 post(:bulk_update,
3513 3525 {
3514 3526 :ids => [1, 2],
3515 3527 :notes => 'Bulk editing',
3516 3528 :issue => {
3517 3529 :priority_id => 7,
3518 3530 :assigned_to_id => '',
3519 3531 :custom_field_values => {'2' => ''}
3520 3532 }
3521 3533 })
3522 3534
3523 3535 assert_response 302
3524 3536 assert_equal 2, ActionMailer::Base.deliveries.size
3525 3537 end
3526 3538
3527 3539 def test_bulk_update_project
3528 3540 @request.session[:user_id] = 2
3529 3541 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3530 3542 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3531 3543 # Issues moved to project 2
3532 3544 assert_equal 2, Issue.find(1).project_id
3533 3545 assert_equal 2, Issue.find(2).project_id
3534 3546 # No tracker change
3535 3547 assert_equal 1, Issue.find(1).tracker_id
3536 3548 assert_equal 2, Issue.find(2).tracker_id
3537 3549 end
3538 3550
3539 3551 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3540 3552 @request.session[:user_id] = 2
3541 3553 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3542 3554 assert_redirected_to '/issues/1'
3543 3555 end
3544 3556
3545 3557 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3546 3558 @request.session[:user_id] = 2
3547 3559 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3548 3560 assert_redirected_to '/projects/onlinestore/issues'
3549 3561 end
3550 3562
3551 3563 def test_bulk_update_tracker
3552 3564 @request.session[:user_id] = 2
3553 3565 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3554 3566 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3555 3567 assert_equal 2, Issue.find(1).tracker_id
3556 3568 assert_equal 2, Issue.find(2).tracker_id
3557 3569 end
3558 3570
3559 3571 def test_bulk_update_status
3560 3572 @request.session[:user_id] = 2
3561 3573 # update issues priority
3562 3574 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3563 3575 :issue => {:priority_id => '',
3564 3576 :assigned_to_id => '',
3565 3577 :status_id => '5'}
3566 3578
3567 3579 assert_response 302
3568 3580 issue = Issue.find(1)
3569 3581 assert issue.closed?
3570 3582 end
3571 3583
3572 3584 def test_bulk_update_priority
3573 3585 @request.session[:user_id] = 2
3574 3586 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3575 3587
3576 3588 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3577 3589 assert_equal 6, Issue.find(1).priority_id
3578 3590 assert_equal 6, Issue.find(2).priority_id
3579 3591 end
3580 3592
3581 3593 def test_bulk_update_with_notes
3582 3594 @request.session[:user_id] = 2
3583 3595 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3584 3596
3585 3597 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3586 3598 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3587 3599 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3588 3600 end
3589 3601
3590 3602 def test_bulk_update_parent_id
3591 3603 IssueRelation.delete_all
3592 3604 @request.session[:user_id] = 2
3593 3605 post :bulk_update, :ids => [1, 3],
3594 3606 :notes => 'Bulk editing parent',
3595 3607 :issue => {:priority_id => '', :assigned_to_id => '',
3596 3608 :status_id => '', :parent_issue_id => '2'}
3597 3609 assert_response 302
3598 3610 parent = Issue.find(2)
3599 3611 assert_equal parent.id, Issue.find(1).parent_id
3600 3612 assert_equal parent.id, Issue.find(3).parent_id
3601 3613 assert_equal [1, 3], parent.children.collect(&:id).sort
3602 3614 end
3603 3615
3604 3616 def test_bulk_update_custom_field
3605 3617 @request.session[:user_id] = 2
3606 3618 # update issues priority
3607 3619 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3608 3620 :issue => {:priority_id => '',
3609 3621 :assigned_to_id => '',
3610 3622 :custom_field_values => {'2' => '777'}}
3611 3623
3612 3624 assert_response 302
3613 3625
3614 3626 issue = Issue.find(1)
3615 3627 journal = issue.journals.reorder('created_on DESC').first
3616 3628 assert_equal '777', issue.custom_value_for(2).value
3617 3629 assert_equal 1, journal.details.size
3618 3630 assert_equal '125', journal.details.first.old_value
3619 3631 assert_equal '777', journal.details.first.value
3620 3632 end
3621 3633
3622 3634 def test_bulk_update_custom_field_to_blank
3623 3635 @request.session[:user_id] = 2
3624 3636 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3625 3637 :issue => {:priority_id => '',
3626 3638 :assigned_to_id => '',
3627 3639 :custom_field_values => {'1' => '__none__'}}
3628 3640 assert_response 302
3629 3641 assert_equal '', Issue.find(1).custom_field_value(1)
3630 3642 assert_equal '', Issue.find(3).custom_field_value(1)
3631 3643 end
3632 3644
3633 3645 def test_bulk_update_multi_custom_field
3634 3646 field = CustomField.find(1)
3635 3647 field.update_attribute :multiple, true
3636 3648
3637 3649 @request.session[:user_id] = 2
3638 3650 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3639 3651 :issue => {:priority_id => '',
3640 3652 :assigned_to_id => '',
3641 3653 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3642 3654
3643 3655 assert_response 302
3644 3656
3645 3657 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3646 3658 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3647 3659 # the custom field is not associated with the issue tracker
3648 3660 assert_nil Issue.find(2).custom_field_value(1)
3649 3661 end
3650 3662
3651 3663 def test_bulk_update_multi_custom_field_to_blank
3652 3664 field = CustomField.find(1)
3653 3665 field.update_attribute :multiple, true
3654 3666
3655 3667 @request.session[:user_id] = 2
3656 3668 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3657 3669 :issue => {:priority_id => '',
3658 3670 :assigned_to_id => '',
3659 3671 :custom_field_values => {'1' => ['__none__']}}
3660 3672 assert_response 302
3661 3673 assert_equal [''], Issue.find(1).custom_field_value(1)
3662 3674 assert_equal [''], Issue.find(3).custom_field_value(1)
3663 3675 end
3664 3676
3665 3677 def test_bulk_update_unassign
3666 3678 assert_not_nil Issue.find(2).assigned_to
3667 3679 @request.session[:user_id] = 2
3668 3680 # unassign issues
3669 3681 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3670 3682 assert_response 302
3671 3683 # check that the issues were updated
3672 3684 assert_nil Issue.find(2).assigned_to
3673 3685 end
3674 3686
3675 3687 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3676 3688 @request.session[:user_id] = 2
3677 3689
3678 3690 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3679 3691
3680 3692 assert_response :redirect
3681 3693 issues = Issue.find([1,2])
3682 3694 issues.each do |issue|
3683 3695 assert_equal 4, issue.fixed_version_id
3684 3696 assert_not_equal issue.project_id, issue.fixed_version.project_id
3685 3697 end
3686 3698 end
3687 3699
3688 3700 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3689 3701 @request.session[:user_id] = 2
3690 3702 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3691 3703
3692 3704 assert_response :redirect
3693 3705 assert_redirected_to '/issues'
3694 3706 end
3695 3707
3696 3708 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3697 3709 @request.session[:user_id] = 2
3698 3710 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3699 3711
3700 3712 assert_response :redirect
3701 3713 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3702 3714 end
3703 3715
3704 3716 def test_bulk_update_with_all_failures_should_show_errors
3705 3717 @request.session[:user_id] = 2
3706 3718 post :bulk_update, :ids => [1, 2], :issue => {:start_date => 'foo'}
3707 3719
3708 3720 assert_response :success
3709 3721 assert_template 'bulk_edit'
3710 3722 assert_select '#errorExplanation span', :text => 'Failed to save 2 issue(s) on 2 selected: #1, #2.'
3711 3723 assert_select '#errorExplanation ul li', :text => 'Start date is not a valid date: #1, #2'
3712 3724
3713 3725 assert_equal [1, 2], assigns[:issues].map(&:id)
3714 3726 end
3715 3727
3716 3728 def test_bulk_update_with_some_failures_should_show_errors
3717 3729 issue1 = Issue.generate!(:start_date => '2013-05-12')
3718 3730 issue2 = Issue.generate!(:start_date => '2013-05-15')
3719 3731 issue3 = Issue.generate!
3720 3732 @request.session[:user_id] = 2
3721 3733 post :bulk_update, :ids => [issue1.id, issue2.id, issue3.id],
3722 3734 :issue => {:due_date => '2013-05-01'}
3723 3735 assert_response :success
3724 3736 assert_template 'bulk_edit'
3725 3737 assert_select '#errorExplanation span',
3726 3738 :text => "Failed to save 2 issue(s) on 3 selected: ##{issue1.id}, ##{issue2.id}."
3727 3739 assert_select '#errorExplanation ul li',
3728 3740 :text => "Due date must be greater than start date: ##{issue1.id}, ##{issue2.id}"
3729 3741 assert_equal [issue1.id, issue2.id], assigns[:issues].map(&:id)
3730 3742 end
3731 3743
3732 3744 def test_bulk_update_with_failure_should_preserved_form_values
3733 3745 @request.session[:user_id] = 2
3734 3746 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2', :start_date => 'foo'}
3735 3747
3736 3748 assert_response :success
3737 3749 assert_template 'bulk_edit'
3738 3750 assert_select 'select[name=?]', 'issue[tracker_id]' do
3739 3751 assert_select 'option[value="2"][selected=selected]'
3740 3752 end
3741 3753 assert_select 'input[name=?][value=?]', 'issue[start_date]', 'foo'
3742 3754 end
3743 3755
3744 3756 def test_get_bulk_copy
3745 3757 @request.session[:user_id] = 2
3746 3758 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3747 3759 assert_response :success
3748 3760 assert_template 'bulk_edit'
3749 3761
3750 3762 issues = assigns(:issues)
3751 3763 assert_not_nil issues
3752 3764 assert_equal [1, 2, 3], issues.map(&:id).sort
3753 3765
3754 3766 assert_select 'input[name=copy_attachments]'
3755 3767 end
3756 3768
3757 3769 def test_bulk_copy_to_another_project
3758 3770 @request.session[:user_id] = 2
3759 3771 assert_difference 'Issue.count', 2 do
3760 3772 assert_no_difference 'Project.find(1).issues.count' do
3761 3773 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3762 3774 end
3763 3775 end
3764 3776 assert_redirected_to '/projects/ecookbook/issues'
3765 3777
3766 3778 copies = Issue.order('id DESC').limit(issues.size)
3767 3779 copies.each do |copy|
3768 3780 assert_equal 2, copy.project_id
3769 3781 end
3770 3782 end
3771 3783
3772 3784 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3773 3785 @request.session[:user_id] = 2
3774 3786 issues = [
3775 3787 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1,
3776 3788 :priority_id => 2, :subject => 'issue 1', :author_id => 1,
3777 3789 :assigned_to_id => nil),
3778 3790 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2,
3779 3791 :priority_id => 1, :subject => 'issue 2', :author_id => 2,
3780 3792 :assigned_to_id => 3)
3781 3793 ]
3782 3794 assert_difference 'Issue.count', issues.size do
3783 3795 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3784 3796 :issue => {
3785 3797 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3786 3798 :status_id => '', :start_date => '', :due_date => ''
3787 3799 }
3788 3800 end
3789 3801
3790 3802 copies = Issue.order('id DESC').limit(issues.size)
3791 3803 issues.each do |orig|
3792 3804 copy = copies.detect {|c| c.subject == orig.subject}
3793 3805 assert_not_nil copy
3794 3806 assert_equal orig.project_id, copy.project_id
3795 3807 assert_equal orig.tracker_id, copy.tracker_id
3796 3808 assert_equal orig.status_id, copy.status_id
3797 3809 assert_equal orig.assigned_to_id, copy.assigned_to_id
3798 3810 assert_equal orig.priority_id, copy.priority_id
3799 3811 end
3800 3812 end
3801 3813
3802 3814 def test_bulk_copy_should_allow_changing_the_issue_attributes
3803 3815 # Fixes random test failure with Mysql
3804 3816 # where Issue.where(:project_id => 2).limit(2).order('id desc')
3805 3817 # doesn't return the expected results
3806 3818 Issue.delete_all("project_id=2")
3807 3819
3808 3820 @request.session[:user_id] = 2
3809 3821 assert_difference 'Issue.count', 2 do
3810 3822 assert_no_difference 'Project.find(1).issues.count' do
3811 3823 post :bulk_update, :ids => [1, 2], :copy => '1',
3812 3824 :issue => {
3813 3825 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3814 3826 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3815 3827 }
3816 3828 end
3817 3829 end
3818 3830
3819 3831 copied_issues = Issue.where(:project_id => 2).limit(2).order('id desc').to_a
3820 3832 assert_equal 2, copied_issues.size
3821 3833 copied_issues.each do |issue|
3822 3834 assert_equal 2, issue.project_id, "Project is incorrect"
3823 3835 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3824 3836 assert_equal 1, issue.status_id, "Status is incorrect"
3825 3837 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3826 3838 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3827 3839 end
3828 3840 end
3829 3841
3830 3842 def test_bulk_copy_should_allow_adding_a_note
3831 3843 @request.session[:user_id] = 2
3832 3844 assert_difference 'Issue.count', 1 do
3833 3845 post :bulk_update, :ids => [1], :copy => '1',
3834 3846 :notes => 'Copying one issue',
3835 3847 :issue => {
3836 3848 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3837 3849 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3838 3850 }
3839 3851 end
3840 3852 issue = Issue.order('id DESC').first
3841 3853 assert_equal 1, issue.journals.size
3842 3854 journal = issue.journals.first
3843 assert_equal 1, journal.details.size
3844 3855 assert_equal 'Copying one issue', journal.notes
3845 3856 end
3846 3857
3847 3858 def test_bulk_copy_should_allow_not_copying_the_attachments
3848 3859 attachment_count = Issue.find(3).attachments.size
3849 3860 assert attachment_count > 0
3850 3861 @request.session[:user_id] = 2
3851 3862
3852 3863 assert_difference 'Issue.count', 1 do
3853 3864 assert_no_difference 'Attachment.count' do
3854 3865 post :bulk_update, :ids => [3], :copy => '1',
3855 3866 :issue => {
3856 3867 :project_id => ''
3857 3868 }
3858 3869 end
3859 3870 end
3860 3871 end
3861 3872
3862 3873 def test_bulk_copy_should_allow_copying_the_attachments
3863 3874 attachment_count = Issue.find(3).attachments.size
3864 3875 assert attachment_count > 0
3865 3876 @request.session[:user_id] = 2
3866 3877
3867 3878 assert_difference 'Issue.count', 1 do
3868 3879 assert_difference 'Attachment.count', attachment_count do
3869 3880 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3870 3881 :issue => {
3871 3882 :project_id => ''
3872 3883 }
3873 3884 end
3874 3885 end
3875 3886 end
3876 3887
3877 3888 def test_bulk_copy_should_add_relations_with_copied_issues
3878 3889 @request.session[:user_id] = 2
3879 3890
3880 3891 assert_difference 'Issue.count', 2 do
3881 3892 assert_difference 'IssueRelation.count', 2 do
3882 post :bulk_update, :ids => [1, 3], :copy => '1',
3893 post :bulk_update, :ids => [1, 3], :copy => '1', :link_copy => '1',
3883 3894 :issue => {
3884 3895 :project_id => '1'
3885 3896 }
3886 3897 end
3887 3898 end
3888 3899 end
3889 3900
3890 3901 def test_bulk_copy_should_allow_not_copying_the_subtasks
3891 3902 issue = Issue.generate_with_descendants!
3892 3903 @request.session[:user_id] = 2
3893 3904
3894 3905 assert_difference 'Issue.count', 1 do
3895 3906 post :bulk_update, :ids => [issue.id], :copy => '1',
3896 3907 :issue => {
3897 3908 :project_id => ''
3898 3909 }
3899 3910 end
3900 3911 end
3901 3912
3902 3913 def test_bulk_copy_should_allow_copying_the_subtasks
3903 3914 issue = Issue.generate_with_descendants!
3904 3915 count = issue.descendants.count
3905 3916 @request.session[:user_id] = 2
3906 3917
3907 3918 assert_difference 'Issue.count', count+1 do
3908 3919 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3909 3920 :issue => {
3910 3921 :project_id => ''
3911 3922 }
3912 3923 end
3913 3924 copy = Issue.where(:parent_id => nil).order("id DESC").first
3914 3925 assert_equal count, copy.descendants.count
3915 3926 end
3916 3927
3917 3928 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3918 3929 issue = Issue.generate_with_descendants!
3919 3930 count = issue.descendants.count
3920 3931 @request.session[:user_id] = 2
3921 3932
3922 3933 assert_difference 'Issue.count', count+1 do
3923 3934 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3924 3935 :issue => {
3925 3936 :project_id => ''
3926 3937 }
3927 3938 end
3928 3939 copy = Issue.where(:parent_id => nil).order("id DESC").first
3929 3940 assert_equal count, copy.descendants.count
3930 3941 end
3931 3942
3932 3943 def test_bulk_copy_to_another_project_should_follow_when_needed
3933 3944 @request.session[:user_id] = 2
3934 3945 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3935 3946 issue = Issue.order('id DESC').first
3936 3947 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3937 3948 end
3938 3949
3939 3950 def test_bulk_copy_with_all_failures_should_display_errors
3940 3951 @request.session[:user_id] = 2
3941 3952 post :bulk_update, :ids => [1, 2], :copy => '1', :issue => {:start_date => 'foo'}
3942 3953
3943 3954 assert_response :success
3944 3955 end
3945 3956
3946 3957 def test_destroy_issue_with_no_time_entries
3947 3958 assert_nil TimeEntry.find_by_issue_id(2)
3948 3959 @request.session[:user_id] = 2
3949 3960
3950 3961 assert_difference 'Issue.count', -1 do
3951 3962 delete :destroy, :id => 2
3952 3963 end
3953 3964 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3954 3965 assert_nil Issue.find_by_id(2)
3955 3966 end
3956 3967
3957 3968 def test_destroy_issues_with_time_entries
3958 3969 @request.session[:user_id] = 2
3959 3970
3960 3971 assert_no_difference 'Issue.count' do
3961 3972 delete :destroy, :ids => [1, 3]
3962 3973 end
3963 3974 assert_response :success
3964 3975 assert_template 'destroy'
3965 3976 assert_not_nil assigns(:hours)
3966 3977 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3967 3978
3968 3979 assert_select 'form' do
3969 3980 assert_select 'input[name=_method][value=delete]'
3970 3981 end
3971 3982 end
3972 3983
3973 3984 def test_destroy_issues_and_destroy_time_entries
3974 3985 @request.session[:user_id] = 2
3975 3986
3976 3987 assert_difference 'Issue.count', -2 do
3977 3988 assert_difference 'TimeEntry.count', -3 do
3978 3989 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3979 3990 end
3980 3991 end
3981 3992 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3982 3993 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3983 3994 assert_nil TimeEntry.find_by_id([1, 2])
3984 3995 end
3985 3996
3986 3997 def test_destroy_issues_and_assign_time_entries_to_project
3987 3998 @request.session[:user_id] = 2
3988 3999
3989 4000 assert_difference 'Issue.count', -2 do
3990 4001 assert_no_difference 'TimeEntry.count' do
3991 4002 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3992 4003 end
3993 4004 end
3994 4005 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3995 4006 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3996 4007 assert_nil TimeEntry.find(1).issue_id
3997 4008 assert_nil TimeEntry.find(2).issue_id
3998 4009 end
3999 4010
4000 4011 def test_destroy_issues_and_reassign_time_entries_to_another_issue
4001 4012 @request.session[:user_id] = 2
4002 4013
4003 4014 assert_difference 'Issue.count', -2 do
4004 4015 assert_no_difference 'TimeEntry.count' do
4005 4016 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
4006 4017 end
4007 4018 end
4008 4019 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
4009 4020 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
4010 4021 assert_equal 2, TimeEntry.find(1).issue_id
4011 4022 assert_equal 2, TimeEntry.find(2).issue_id
4012 4023 end
4013 4024
4014 4025 def test_destroy_issues_from_different_projects
4015 4026 @request.session[:user_id] = 2
4016 4027
4017 4028 assert_difference 'Issue.count', -3 do
4018 4029 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
4019 4030 end
4020 4031 assert_redirected_to :controller => 'issues', :action => 'index'
4021 4032 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
4022 4033 end
4023 4034
4024 4035 def test_destroy_parent_and_child_issues
4025 4036 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
4026 4037 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
4027 4038 assert child.is_descendant_of?(parent.reload)
4028 4039
4029 4040 @request.session[:user_id] = 2
4030 4041 assert_difference 'Issue.count', -2 do
4031 4042 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
4032 4043 end
4033 4044 assert_response 302
4034 4045 end
4035 4046
4036 4047 def test_destroy_invalid_should_respond_with_404
4037 4048 @request.session[:user_id] = 2
4038 4049 assert_no_difference 'Issue.count' do
4039 4050 delete :destroy, :id => 999
4040 4051 end
4041 4052 assert_response 404
4042 4053 end
4043 4054
4044 4055 def test_default_search_scope
4045 4056 get :index
4046 4057
4047 4058 assert_select 'div#quick-search form' do
4048 4059 assert_select 'input[name=issues][value="1"][type=hidden]'
4049 4060 end
4050 4061 end
4051 4062 end
General Comments 0
You need to be logged in to leave comments. Login now