##// END OF EJS Templates
Moved the project name after the item in the html title (#9593)....
Jean-Philippe Lang -
r7722:bf18ee107d08
parent child
Show More
@@ -1,1051 +1,1050
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2011 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 require 'forwardable'
21 21 require 'cgi'
22 22
23 23 module ApplicationHelper
24 24 include Redmine::WikiFormatting::Macros::Definitions
25 25 include Redmine::I18n
26 26 include GravatarHelper::PublicMethods
27 27
28 28 extend Forwardable
29 29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30 30
31 31 # Return true if user is authorized for controller/action, otherwise false
32 32 def authorize_for(controller, action)
33 33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 34 end
35 35
36 36 # Display a link if user is authorized
37 37 #
38 38 # @param [String] name Anchor text (passed to link_to)
39 39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 40 # @param [optional, Hash] html_options Options passed to link_to
41 41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 44 end
45 45
46 46 # Display a link to remote if user is authorized
47 47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 48 url = options[:url] || {}
49 49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 50 end
51 51
52 52 # Displays a link to user's account page if active
53 53 def link_to_user(user, options={})
54 54 if user.is_a?(User)
55 55 name = h(user.name(options[:format]))
56 56 if user.active?
57 57 link_to name, :controller => 'users', :action => 'show', :id => user
58 58 else
59 59 name
60 60 end
61 61 else
62 62 h(user.to_s)
63 63 end
64 64 end
65 65
66 66 # Displays a link to +issue+ with its subject.
67 67 # Examples:
68 68 #
69 69 # link_to_issue(issue) # => Defect #6: This is the subject
70 70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 71 # link_to_issue(issue, :subject => false) # => Defect #6
72 72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 73 #
74 74 def link_to_issue(issue, options={})
75 75 title = nil
76 76 subject = nil
77 77 if options[:subject] == false
78 78 title = truncate(issue.subject, :length => 60)
79 79 else
80 80 subject = issue.subject
81 81 if options[:truncate]
82 82 subject = truncate(subject, :length => options[:truncate])
83 83 end
84 84 end
85 85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 86 :class => issue.css_classes,
87 87 :title => title
88 88 s << ": #{h subject}" if subject
89 89 s = "#{h issue.project} - " + s if options[:project]
90 90 s
91 91 end
92 92
93 93 # Generates a link to an attachment.
94 94 # Options:
95 95 # * :text - Link text (default to attachment filename)
96 96 # * :download - Force download (default: false)
97 97 def link_to_attachment(attachment, options={})
98 98 text = options.delete(:text) || attachment.filename
99 99 action = options.delete(:download) ? 'download' : 'show'
100 100
101 101 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
102 102 end
103 103
104 104 # Generates a link to a SCM revision
105 105 # Options:
106 106 # * :text - Link text (default to the formatted revision)
107 107 def link_to_revision(revision, project, options={})
108 108 text = options.delete(:text) || format_revision(revision)
109 109 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
110 110
111 111 link_to(h(text), {:controller => 'repositories', :action => 'revision', :id => project, :rev => rev},
112 112 :title => l(:label_revision_id, format_revision(revision)))
113 113 end
114 114
115 115 # Generates a link to a message
116 116 def link_to_message(message, options={}, html_options = nil)
117 117 link_to(
118 118 h(truncate(message.subject, :length => 60)),
119 119 { :controller => 'messages', :action => 'show',
120 120 :board_id => message.board_id,
121 121 :id => message.root,
122 122 :r => (message.parent_id && message.id),
123 123 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
124 124 }.merge(options),
125 125 html_options
126 126 )
127 127 end
128 128
129 129 # Generates a link to a project if active
130 130 # Examples:
131 131 #
132 132 # link_to_project(project) # => link to the specified project overview
133 133 # link_to_project(project, :action=>'settings') # => link to project settings
134 134 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
135 135 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
136 136 #
137 137 def link_to_project(project, options={}, html_options = nil)
138 138 if project.active?
139 139 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
140 140 link_to(h(project), url, html_options)
141 141 else
142 142 h(project)
143 143 end
144 144 end
145 145
146 146 def toggle_link(name, id, options={})
147 147 onclick = "Element.toggle('#{id}'); "
148 148 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
149 149 onclick << "return false;"
150 150 link_to(name, "#", :onclick => onclick)
151 151 end
152 152
153 153 def image_to_function(name, function, html_options = {})
154 154 html_options.symbolize_keys!
155 155 tag(:input, html_options.merge({
156 156 :type => "image", :src => image_path(name),
157 157 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
158 158 }))
159 159 end
160 160
161 161 def prompt_to_remote(name, text, param, url, html_options = {})
162 162 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
163 163 link_to name, {}, html_options
164 164 end
165 165
166 166 def format_activity_title(text)
167 167 h(truncate_single_line(text, :length => 100))
168 168 end
169 169
170 170 def format_activity_day(date)
171 171 date == Date.today ? l(:label_today).titleize : format_date(date)
172 172 end
173 173
174 174 def format_activity_description(text)
175 175 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
176 176 end
177 177
178 178 def format_version_name(version)
179 179 if version.project == @project
180 180 h(version)
181 181 else
182 182 h("#{version.project} - #{version}")
183 183 end
184 184 end
185 185
186 186 def due_date_distance_in_words(date)
187 187 if date
188 188 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
189 189 end
190 190 end
191 191
192 192 def render_page_hierarchy(pages, node=nil, options={})
193 193 content = ''
194 194 if pages[node]
195 195 content << "<ul class=\"pages-hierarchy\">\n"
196 196 pages[node].each do |page|
197 197 content << "<li>"
198 198 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
199 199 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
200 200 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
201 201 content << "</li>\n"
202 202 end
203 203 content << "</ul>\n"
204 204 end
205 205 content.html_safe
206 206 end
207 207
208 208 # Renders flash messages
209 209 def render_flash_messages
210 210 s = ''
211 211 flash.each do |k,v|
212 212 s << content_tag('div', v, :class => "flash #{k}")
213 213 end
214 214 s.html_safe
215 215 end
216 216
217 217 # Renders tabs and their content
218 218 def render_tabs(tabs)
219 219 if tabs.any?
220 220 render :partial => 'common/tabs', :locals => {:tabs => tabs}
221 221 else
222 222 content_tag 'p', l(:label_no_data), :class => "nodata"
223 223 end
224 224 end
225 225
226 226 # Renders the project quick-jump box
227 227 def render_project_jump_box
228 228 return unless User.current.logged?
229 229 projects = User.current.memberships.collect(&:project).compact.uniq
230 230 if projects.any?
231 231 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
232 232 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
233 233 '<option value="" disabled="disabled">---</option>'
234 234 s << project_tree_options_for_select(projects, :selected => @project) do |p|
235 235 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
236 236 end
237 237 s << '</select>'
238 238 s.html_safe
239 239 end
240 240 end
241 241
242 242 def project_tree_options_for_select(projects, options = {})
243 243 s = ''
244 244 project_tree(projects) do |project, level|
245 245 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
246 246 tag_options = {:value => project.id}
247 247 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
248 248 tag_options[:selected] = 'selected'
249 249 else
250 250 tag_options[:selected] = nil
251 251 end
252 252 tag_options.merge!(yield(project)) if block_given?
253 253 s << content_tag('option', name_prefix + h(project), tag_options)
254 254 end
255 255 s.html_safe
256 256 end
257 257
258 258 # Yields the given block for each project with its level in the tree
259 259 #
260 260 # Wrapper for Project#project_tree
261 261 def project_tree(projects, &block)
262 262 Project.project_tree(projects, &block)
263 263 end
264 264
265 265 def project_nested_ul(projects, &block)
266 266 s = ''
267 267 if projects.any?
268 268 ancestors = []
269 269 projects.sort_by(&:lft).each do |project|
270 270 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
271 271 s << "<ul>\n"
272 272 else
273 273 ancestors.pop
274 274 s << "</li>"
275 275 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
276 276 ancestors.pop
277 277 s << "</ul></li>\n"
278 278 end
279 279 end
280 280 s << "<li>"
281 281 s << yield(project).to_s
282 282 ancestors << project
283 283 end
284 284 s << ("</li></ul>\n" * ancestors.size)
285 285 end
286 286 s.html_safe
287 287 end
288 288
289 289 def principals_check_box_tags(name, principals)
290 290 s = ''
291 291 principals.sort.each do |principal|
292 292 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
293 293 end
294 294 s.html_safe
295 295 end
296 296
297 297 # Returns a string for users/groups option tags
298 298 def principals_options_for_select(collection, selected=nil)
299 299 s = ''
300 300 groups = ''
301 301 collection.sort.each do |element|
302 302 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
303 303 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
304 304 end
305 305 unless groups.empty?
306 306 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
307 307 end
308 308 s
309 309 end
310 310
311 311 # Truncates and returns the string as a single line
312 312 def truncate_single_line(string, *args)
313 313 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
314 314 end
315 315
316 316 # Truncates at line break after 250 characters or options[:length]
317 317 def truncate_lines(string, options={})
318 318 length = options[:length] || 250
319 319 if string.to_s =~ /\A(.{#{length}}.*?)$/m
320 320 "#{$1}..."
321 321 else
322 322 string
323 323 end
324 324 end
325 325
326 326 def html_hours(text)
327 327 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
328 328 end
329 329
330 330 def authoring(created, author, options={})
331 331 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
332 332 end
333 333
334 334 def time_tag(time)
335 335 text = distance_of_time_in_words(Time.now, time)
336 336 if @project
337 337 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
338 338 else
339 339 content_tag('acronym', text, :title => format_time(time))
340 340 end
341 341 end
342 342
343 343 def syntax_highlight(name, content)
344 344 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
345 345 end
346 346
347 347 def to_path_param(path)
348 348 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
349 349 end
350 350
351 351 def pagination_links_full(paginator, count=nil, options={})
352 352 page_param = options.delete(:page_param) || :page
353 353 per_page_links = options.delete(:per_page_links)
354 354 url_param = params.dup
355 355
356 356 html = ''
357 357 if paginator.current.previous
358 358 # \xc2\xab(utf-8) = &#171;
359 359 html << link_to_content_update(
360 360 "\xc2\xab " + l(:label_previous),
361 361 url_param.merge(page_param => paginator.current.previous)) + ' '
362 362 end
363 363
364 364 html << (pagination_links_each(paginator, options) do |n|
365 365 link_to_content_update(n.to_s, url_param.merge(page_param => n))
366 366 end || '')
367 367
368 368 if paginator.current.next
369 369 # \xc2\xbb(utf-8) = &#187;
370 370 html << ' ' + link_to_content_update(
371 371 (l(:label_next) + " \xc2\xbb"),
372 372 url_param.merge(page_param => paginator.current.next))
373 373 end
374 374
375 375 unless count.nil?
376 376 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
377 377 if per_page_links != false && links = per_page_links(paginator.items_per_page)
378 378 html << " | #{links}"
379 379 end
380 380 end
381 381
382 382 html.html_safe
383 383 end
384 384
385 385 def per_page_links(selected=nil)
386 386 links = Setting.per_page_options_array.collect do |n|
387 387 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
388 388 end
389 389 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
390 390 end
391 391
392 392 def reorder_links(name, url)
393 393 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
394 394 url.merge({"#{name}[move_to]" => 'highest'}),
395 395 :method => :post, :title => l(:label_sort_highest)) +
396 396 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
397 397 url.merge({"#{name}[move_to]" => 'higher'}),
398 398 :method => :post, :title => l(:label_sort_higher)) +
399 399 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
400 400 url.merge({"#{name}[move_to]" => 'lower'}),
401 401 :method => :post, :title => l(:label_sort_lower)) +
402 402 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
403 403 url.merge({"#{name}[move_to]" => 'lowest'}),
404 404 :method => :post, :title => l(:label_sort_lowest))
405 405 end
406 406
407 407 def breadcrumb(*args)
408 408 elements = args.flatten
409 409 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
410 410 end
411 411
412 412 def other_formats_links(&block)
413 413 concat('<p class="other-formats">' + l(:label_export_to))
414 414 yield Redmine::Views::OtherFormatsBuilder.new(self)
415 415 concat('</p>')
416 416 end
417 417
418 418 def page_header_title
419 419 if @project.nil? || @project.new_record?
420 420 h(Setting.app_title)
421 421 else
422 422 b = []
423 423 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
424 424 if ancestors.any?
425 425 root = ancestors.shift
426 426 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
427 427 if ancestors.size > 2
428 428 b << "\xe2\x80\xa6"
429 429 ancestors = ancestors[-2, 2]
430 430 end
431 431 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
432 432 end
433 433 b << h(@project)
434 434 b.join(" \xc2\xbb ").html_safe
435 435 end
436 436 end
437 437
438 438 def html_title(*args)
439 439 if args.empty?
440 title = []
440 title = @html_title || []
441 441 title << @project.name if @project
442 title += @html_title if @html_title
443 title << Setting.app_title
442 title << Setting.app_title unless Setting.app_title == title.last
444 443 title.select {|t| !t.blank? }.join(' - ')
445 444 else
446 445 @html_title ||= []
447 446 @html_title += args
448 447 end
449 448 end
450 449
451 450 # Returns the theme, controller name, and action as css classes for the
452 451 # HTML body.
453 452 def body_css_classes
454 453 css = []
455 454 if theme = Redmine::Themes.theme(Setting.ui_theme)
456 455 css << 'theme-' + theme.name
457 456 end
458 457
459 458 css << 'controller-' + params[:controller]
460 459 css << 'action-' + params[:action]
461 460 css.join(' ')
462 461 end
463 462
464 463 def accesskey(s)
465 464 Redmine::AccessKeys.key_for s
466 465 end
467 466
468 467 # Formats text according to system settings.
469 468 # 2 ways to call this method:
470 469 # * with a String: textilizable(text, options)
471 470 # * with an object and one of its attribute: textilizable(issue, :description, options)
472 471 def textilizable(*args)
473 472 options = args.last.is_a?(Hash) ? args.pop : {}
474 473 case args.size
475 474 when 1
476 475 obj = options[:object]
477 476 text = args.shift
478 477 when 2
479 478 obj = args.shift
480 479 attr = args.shift
481 480 text = obj.send(attr).to_s
482 481 else
483 482 raise ArgumentError, 'invalid arguments to textilizable'
484 483 end
485 484 return '' if text.blank?
486 485 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
487 486 only_path = options.delete(:only_path) == false ? false : true
488 487
489 488 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
490 489
491 490 @parsed_headings = []
492 491 @current_section = 0 if options[:edit_section_links]
493 492 text = parse_non_pre_blocks(text) do |text|
494 493 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
495 494 send method_name, text, project, obj, attr, only_path, options
496 495 end
497 496 end
498 497
499 498 if @parsed_headings.any?
500 499 replace_toc(text, @parsed_headings)
501 500 end
502 501
503 502 text
504 503 end
505 504
506 505 def parse_non_pre_blocks(text)
507 506 s = StringScanner.new(text)
508 507 tags = []
509 508 parsed = ''
510 509 while !s.eos?
511 510 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
512 511 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
513 512 if tags.empty?
514 513 yield text
515 514 end
516 515 parsed << text
517 516 if tag
518 517 if closing
519 518 if tags.last == tag.downcase
520 519 tags.pop
521 520 end
522 521 else
523 522 tags << tag.downcase
524 523 end
525 524 parsed << full_tag
526 525 end
527 526 end
528 527 # Close any non closing tags
529 528 while tag = tags.pop
530 529 parsed << "</#{tag}>"
531 530 end
532 531 parsed.html_safe
533 532 end
534 533
535 534 def parse_inline_attachments(text, project, obj, attr, only_path, options)
536 535 # when using an image link, try to use an attachment, if possible
537 536 if options[:attachments] || (obj && obj.respond_to?(:attachments))
538 537 attachments = nil
539 538 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
540 539 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
541 540 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
542 541 # search for the picture in attachments
543 542 if found = attachments.detect { |att| att.filename.downcase == filename }
544 543 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
545 544 desc = found.description.to_s.gsub('"', '')
546 545 if !desc.blank? && alttext.blank?
547 546 alt = " title=\"#{desc}\" alt=\"#{desc}\""
548 547 end
549 548 "src=\"#{image_url}\"#{alt}".html_safe
550 549 else
551 550 m.html_safe
552 551 end
553 552 end
554 553 end
555 554 end
556 555
557 556 # Wiki links
558 557 #
559 558 # Examples:
560 559 # [[mypage]]
561 560 # [[mypage|mytext]]
562 561 # wiki links can refer other project wikis, using project name or identifier:
563 562 # [[project:]] -> wiki starting page
564 563 # [[project:|mytext]]
565 564 # [[project:mypage]]
566 565 # [[project:mypage|mytext]]
567 566 def parse_wiki_links(text, project, obj, attr, only_path, options)
568 567 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
569 568 link_project = project
570 569 esc, all, page, title = $1, $2, $3, $5
571 570 if esc.nil?
572 571 if page =~ /^([^\:]+)\:(.*)$/
573 572 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
574 573 page = $2
575 574 title ||= $1 if page.blank?
576 575 end
577 576
578 577 if link_project && link_project.wiki
579 578 # extract anchor
580 579 anchor = nil
581 580 if page =~ /^(.+?)\#(.+)$/
582 581 page, anchor = $1, $2
583 582 end
584 583 anchor = sanitize_anchor_name(anchor) if anchor.present?
585 584 # check if page exists
586 585 wiki_page = link_project.wiki.find_page(page)
587 586 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
588 587 "##{anchor}"
589 588 else
590 589 case options[:wiki_links]
591 590 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
592 591 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
593 592 else
594 593 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
595 594 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project, :id => wiki_page_id, :anchor => anchor)
596 595 end
597 596 end
598 597 link_to(title || h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
599 598 else
600 599 # project or wiki doesn't exist
601 600 all.html_safe
602 601 end
603 602 else
604 603 all.html_safe
605 604 end
606 605 end
607 606 end
608 607
609 608 # Redmine links
610 609 #
611 610 # Examples:
612 611 # Issues:
613 612 # #52 -> Link to issue #52
614 613 # Changesets:
615 614 # r52 -> Link to revision 52
616 615 # commit:a85130f -> Link to scmid starting with a85130f
617 616 # Documents:
618 617 # document#17 -> Link to document with id 17
619 618 # document:Greetings -> Link to the document with title "Greetings"
620 619 # document:"Some document" -> Link to the document with title "Some document"
621 620 # Versions:
622 621 # version#3 -> Link to version with id 3
623 622 # version:1.0.0 -> Link to version named "1.0.0"
624 623 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
625 624 # Attachments:
626 625 # attachment:file.zip -> Link to the attachment of the current object named file.zip
627 626 # Source files:
628 627 # source:some/file -> Link to the file located at /some/file in the project's repository
629 628 # source:some/file@52 -> Link to the file's revision 52
630 629 # source:some/file#L120 -> Link to line 120 of the file
631 630 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
632 631 # export:some/file -> Force the download of the file
633 632 # Forum messages:
634 633 # message#1218 -> Link to message with id 1218
635 634 #
636 635 # Links can refer other objects from other projects, using project identifier:
637 636 # identifier:r52
638 637 # identifier:document:"Some document"
639 638 # identifier:version:1.0.0
640 639 # identifier:source:some/file
641 640 def parse_redmine_links(text, project, obj, attr, only_path, options)
642 641 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
643 642 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
644 643 link = nil
645 644 if project_identifier
646 645 project = Project.visible.find_by_identifier(project_identifier)
647 646 end
648 647 if esc.nil?
649 648 if prefix.nil? && sep == 'r'
650 649 # project.changesets.visible raises an SQL error because of a double join on repositories
651 650 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
652 651 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
653 652 :class => 'changeset',
654 653 :title => truncate_single_line(changeset.comments, :length => 100))
655 654 end
656 655 elsif sep == '#'
657 656 oid = identifier.to_i
658 657 case prefix
659 658 when nil
660 659 if issue = Issue.visible.find_by_id(oid, :include => :status)
661 660 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
662 661 :class => issue.css_classes,
663 662 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
664 663 end
665 664 when 'document'
666 665 if document = Document.visible.find_by_id(oid)
667 666 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
668 667 :class => 'document'
669 668 end
670 669 when 'version'
671 670 if version = Version.visible.find_by_id(oid)
672 671 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
673 672 :class => 'version'
674 673 end
675 674 when 'message'
676 675 if message = Message.visible.find_by_id(oid, :include => :parent)
677 676 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
678 677 end
679 678 when 'forum'
680 679 if board = Board.visible.find_by_id(oid)
681 680 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
682 681 :class => 'board'
683 682 end
684 683 when 'news'
685 684 if news = News.visible.find_by_id(oid)
686 685 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
687 686 :class => 'news'
688 687 end
689 688 when 'project'
690 689 if p = Project.visible.find_by_id(oid)
691 690 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
692 691 end
693 692 end
694 693 elsif sep == ':'
695 694 # removes the double quotes if any
696 695 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
697 696 case prefix
698 697 when 'document'
699 698 if project && document = project.documents.visible.find_by_title(name)
700 699 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
701 700 :class => 'document'
702 701 end
703 702 when 'version'
704 703 if project && version = project.versions.visible.find_by_name(name)
705 704 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
706 705 :class => 'version'
707 706 end
708 707 when 'forum'
709 708 if project && board = project.boards.visible.find_by_name(name)
710 709 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
711 710 :class => 'board'
712 711 end
713 712 when 'news'
714 713 if project && news = project.news.visible.find_by_title(name)
715 714 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
716 715 :class => 'news'
717 716 end
718 717 when 'commit'
719 718 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
720 719 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
721 720 :class => 'changeset',
722 721 :title => truncate_single_line(h(changeset.comments), :length => 100)
723 722 end
724 723 when 'source', 'export'
725 724 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
726 725 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
727 726 path, rev, anchor = $1, $3, $5
728 727 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
729 728 :path => to_path_param(path),
730 729 :rev => rev,
731 730 :anchor => anchor,
732 731 :format => (prefix == 'export' ? 'raw' : nil)},
733 732 :class => (prefix == 'export' ? 'source download' : 'source')
734 733 end
735 734 when 'attachment'
736 735 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
737 736 if attachments && attachment = attachments.detect {|a| a.filename == name }
738 737 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
739 738 :class => 'attachment'
740 739 end
741 740 when 'project'
742 741 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
743 742 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
744 743 end
745 744 end
746 745 end
747 746 end
748 747 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
749 748 end
750 749 end
751 750
752 751 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
753 752
754 753 def parse_sections(text, project, obj, attr, only_path, options)
755 754 return unless options[:edit_section_links]
756 755 text.gsub!(HEADING_RE) do
757 756 @current_section += 1
758 757 if @current_section > 1
759 758 content_tag('div',
760 759 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
761 760 :class => 'contextual',
762 761 :title => l(:button_edit_section)) + $1
763 762 else
764 763 $1
765 764 end
766 765 end
767 766 end
768 767
769 768 # Headings and TOC
770 769 # Adds ids and links to headings unless options[:headings] is set to false
771 770 def parse_headings(text, project, obj, attr, only_path, options)
772 771 return if options[:headings] == false
773 772
774 773 text.gsub!(HEADING_RE) do
775 774 level, attrs, content = $2.to_i, $3, $4
776 775 item = strip_tags(content).strip
777 776 anchor = sanitize_anchor_name(item)
778 777 # used for single-file wiki export
779 778 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
780 779 @parsed_headings << [level, anchor, item]
781 780 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
782 781 end
783 782 end
784 783
785 784 MACROS_RE = /
786 785 (!)? # escaping
787 786 (
788 787 \{\{ # opening tag
789 788 ([\w]+) # macro name
790 789 (\(([^\}]*)\))? # optional arguments
791 790 \}\} # closing tag
792 791 )
793 792 /x unless const_defined?(:MACROS_RE)
794 793
795 794 # Macros substitution
796 795 def parse_macros(text, project, obj, attr, only_path, options)
797 796 text.gsub!(MACROS_RE) do
798 797 esc, all, macro = $1, $2, $3.downcase
799 798 args = ($5 || '').split(',').each(&:strip)
800 799 if esc.nil?
801 800 begin
802 801 exec_macro(macro, obj, args)
803 802 rescue => e
804 803 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
805 804 end || all
806 805 else
807 806 all
808 807 end
809 808 end
810 809 end
811 810
812 811 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
813 812
814 813 # Renders the TOC with given headings
815 814 def replace_toc(text, headings)
816 815 text.gsub!(TOC_RE) do
817 816 if headings.empty?
818 817 ''
819 818 else
820 819 div_class = 'toc'
821 820 div_class << ' right' if $1 == '>'
822 821 div_class << ' left' if $1 == '<'
823 822 out = "<ul class=\"#{div_class}\"><li>"
824 823 root = headings.map(&:first).min
825 824 current = root
826 825 started = false
827 826 headings.each do |level, anchor, item|
828 827 if level > current
829 828 out << '<ul><li>' * (level - current)
830 829 elsif level < current
831 830 out << "</li></ul>\n" * (current - level) + "</li><li>"
832 831 elsif started
833 832 out << '</li><li>'
834 833 end
835 834 out << "<a href=\"##{anchor}\">#{item}</a>"
836 835 current = level
837 836 started = true
838 837 end
839 838 out << '</li></ul>' * (current - root)
840 839 out << '</li></ul>'
841 840 end
842 841 end
843 842 end
844 843
845 844 # Same as Rails' simple_format helper without using paragraphs
846 845 def simple_format_without_paragraph(text)
847 846 text.to_s.
848 847 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
849 848 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
850 849 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
851 850 html_safe
852 851 end
853 852
854 853 def lang_options_for_select(blank=true)
855 854 (blank ? [["(auto)", ""]] : []) +
856 855 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
857 856 end
858 857
859 858 def label_tag_for(name, option_tags = nil, options = {})
860 859 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
861 860 content_tag("label", label_text)
862 861 end
863 862
864 863 def labelled_tabular_form_for(name, object, options, &proc)
865 864 options[:html] ||= {}
866 865 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
867 866 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
868 867 end
869 868
870 869 def back_url_hidden_field_tag
871 870 back_url = params[:back_url] || request.env['HTTP_REFERER']
872 871 back_url = CGI.unescape(back_url.to_s)
873 872 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
874 873 end
875 874
876 875 def check_all_links(form_name)
877 876 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
878 877 " | ".html_safe +
879 878 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
880 879 end
881 880
882 881 def progress_bar(pcts, options={})
883 882 pcts = [pcts, pcts] unless pcts.is_a?(Array)
884 883 pcts = pcts.collect(&:round)
885 884 pcts[1] = pcts[1] - pcts[0]
886 885 pcts << (100 - pcts[1] - pcts[0])
887 886 width = options[:width] || '100px;'
888 887 legend = options[:legend] || ''
889 888 content_tag('table',
890 889 content_tag('tr',
891 890 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
892 891 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
893 892 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
894 893 ), :class => 'progress', :style => "width: #{width};").html_safe +
895 894 content_tag('p', legend, :class => 'pourcent').html_safe
896 895 end
897 896
898 897 def checked_image(checked=true)
899 898 if checked
900 899 image_tag 'toggle_check.png'
901 900 end
902 901 end
903 902
904 903 def context_menu(url)
905 904 unless @context_menu_included
906 905 content_for :header_tags do
907 906 javascript_include_tag('context_menu') +
908 907 stylesheet_link_tag('context_menu')
909 908 end
910 909 if l(:direction) == 'rtl'
911 910 content_for :header_tags do
912 911 stylesheet_link_tag('context_menu_rtl')
913 912 end
914 913 end
915 914 @context_menu_included = true
916 915 end
917 916 javascript_tag "new ContextMenu('#{ url_for(url) }')"
918 917 end
919 918
920 919 def context_menu_link(name, url, options={})
921 920 options[:class] ||= ''
922 921 if options.delete(:selected)
923 922 options[:class] << ' icon-checked disabled'
924 923 options[:disabled] = true
925 924 end
926 925 if options.delete(:disabled)
927 926 options.delete(:method)
928 927 options.delete(:confirm)
929 928 options.delete(:onclick)
930 929 options[:class] << ' disabled'
931 930 url = '#'
932 931 end
933 932 link_to h(name), url, options
934 933 end
935 934
936 935 def calendar_for(field_id)
937 936 include_calendar_headers_tags
938 937 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
939 938 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
940 939 end
941 940
942 941 def include_calendar_headers_tags
943 942 unless @calendar_headers_tags_included
944 943 @calendar_headers_tags_included = true
945 944 content_for :header_tags do
946 945 start_of_week = case Setting.start_of_week.to_i
947 946 when 1
948 947 'Calendar._FD = 1;' # Monday
949 948 when 7
950 949 'Calendar._FD = 0;' # Sunday
951 950 when 6
952 951 'Calendar._FD = 6;' # Saturday
953 952 else
954 953 '' # use language
955 954 end
956 955
957 956 javascript_include_tag('calendar/calendar') +
958 957 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
959 958 javascript_tag(start_of_week) +
960 959 javascript_include_tag('calendar/calendar-setup') +
961 960 stylesheet_link_tag('calendar')
962 961 end
963 962 end
964 963 end
965 964
966 965 def content_for(name, content = nil, &block)
967 966 @has_content ||= {}
968 967 @has_content[name] = true
969 968 super(name, content, &block)
970 969 end
971 970
972 971 def has_content?(name)
973 972 (@has_content && @has_content[name]) || false
974 973 end
975 974
976 975 def email_delivery_enabled?
977 976 !!ActionMailer::Base.perform_deliveries
978 977 end
979 978
980 979 # Returns the avatar image tag for the given +user+ if avatars are enabled
981 980 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
982 981 def avatar(user, options = { })
983 982 if Setting.gravatar_enabled?
984 983 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
985 984 email = nil
986 985 if user.respond_to?(:mail)
987 986 email = user.mail
988 987 elsif user.to_s =~ %r{<(.+?)>}
989 988 email = $1
990 989 end
991 990 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
992 991 else
993 992 ''
994 993 end
995 994 end
996 995
997 996 def sanitize_anchor_name(anchor)
998 997 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
999 998 end
1000 999
1001 1000 # Returns the javascript tags that are included in the html layout head
1002 1001 def javascript_heads
1003 1002 tags = javascript_include_tag(:defaults)
1004 1003 unless User.current.pref.warn_on_leaving_unsaved == '0'
1005 1004 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1006 1005 end
1007 1006 tags
1008 1007 end
1009 1008
1010 1009 def favicon
1011 1010 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1012 1011 end
1013 1012
1014 1013 def robot_exclusion_tag
1015 1014 '<meta name="robots" content="noindex,follow,noarchive" />'
1016 1015 end
1017 1016
1018 1017 # Returns true if arg is expected in the API response
1019 1018 def include_in_api_response?(arg)
1020 1019 unless @included_in_api_response
1021 1020 param = params[:include]
1022 1021 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1023 1022 @included_in_api_response.collect!(&:strip)
1024 1023 end
1025 1024 @included_in_api_response.include?(arg.to_s)
1026 1025 end
1027 1026
1028 1027 # Returns options or nil if nometa param or X-Redmine-Nometa header
1029 1028 # was set in the request
1030 1029 def api_meta(options)
1031 1030 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1032 1031 # compatibility mode for activeresource clients that raise
1033 1032 # an error when unserializing an array with attributes
1034 1033 nil
1035 1034 else
1036 1035 options
1037 1036 end
1038 1037 end
1039 1038
1040 1039 private
1041 1040
1042 1041 def wiki_helper
1043 1042 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1044 1043 extend helper
1045 1044 return self
1046 1045 end
1047 1046
1048 1047 def link_to_content_update(text, url_params = {}, html_options = {})
1049 1048 link_to(text, url_params, html_options)
1050 1049 end
1051 1050 end
@@ -1,1816 +1,1818
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 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 require 'issues_controller'
20 20
21 21 class IssuesControllerTest < ActionController::TestCase
22 22 fixtures :projects,
23 23 :users,
24 24 :roles,
25 25 :members,
26 26 :member_roles,
27 27 :issues,
28 28 :issue_statuses,
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
46 46 def setup
47 47 @controller = IssuesController.new
48 48 @request = ActionController::TestRequest.new
49 49 @response = ActionController::TestResponse.new
50 50 User.current = nil
51 51 end
52 52
53 53 def test_index
54 54 Setting.default_language = 'en'
55 55
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 assert_tag :tag => 'a', :content => /Can't print recipes/
62 62 assert_tag :tag => 'a', :content => /Subproject issue/
63 63 # private projects hidden
64 64 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
65 65 assert_no_tag :tag => 'a', :content => /Issue on project 2/
66 66 # project column
67 67 assert_tag :tag => 'th', :content => /Project/
68 68 end
69 69
70 70 def test_index_should_not_list_issues_when_module_disabled
71 71 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
72 72 get :index
73 73 assert_response :success
74 74 assert_template 'index'
75 75 assert_not_nil assigns(:issues)
76 76 assert_nil assigns(:project)
77 77 assert_no_tag :tag => 'a', :content => /Can't print recipes/
78 78 assert_tag :tag => 'a', :content => /Subproject issue/
79 79 end
80 80
81 81 def test_index_should_list_visible_issues_only
82 82 get :index, :per_page => 100
83 83 assert_response :success
84 84 assert_not_nil assigns(:issues)
85 85 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
86 86 end
87 87
88 88 def test_index_with_project
89 89 Setting.display_subprojects_issues = 0
90 90 get :index, :project_id => 1
91 91 assert_response :success
92 92 assert_template 'index'
93 93 assert_not_nil assigns(:issues)
94 94 assert_tag :tag => 'a', :content => /Can't print recipes/
95 95 assert_no_tag :tag => 'a', :content => /Subproject issue/
96 96 end
97 97
98 98 def test_index_with_project_and_subprojects
99 99 Setting.display_subprojects_issues = 1
100 100 get :index, :project_id => 1
101 101 assert_response :success
102 102 assert_template 'index'
103 103 assert_not_nil assigns(:issues)
104 104 assert_tag :tag => 'a', :content => /Can't print recipes/
105 105 assert_tag :tag => 'a', :content => /Subproject issue/
106 106 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
107 107 end
108 108
109 109 def test_index_with_project_and_subprojects_should_show_private_subprojects
110 110 @request.session[:user_id] = 2
111 111 Setting.display_subprojects_issues = 1
112 112 get :index, :project_id => 1
113 113 assert_response :success
114 114 assert_template 'index'
115 115 assert_not_nil assigns(:issues)
116 116 assert_tag :tag => 'a', :content => /Can't print recipes/
117 117 assert_tag :tag => 'a', :content => /Subproject issue/
118 118 assert_tag :tag => 'a', :content => /Issue of a private subproject/
119 119 end
120 120
121 121 def test_index_with_project_and_default_filter
122 122 get :index, :project_id => 1, :set_filter => 1
123 123 assert_response :success
124 124 assert_template 'index'
125 125 assert_not_nil assigns(:issues)
126 126
127 127 query = assigns(:query)
128 128 assert_not_nil query
129 129 # default filter
130 130 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
131 131 end
132 132
133 133 def test_index_with_project_and_filter
134 134 get :index, :project_id => 1, :set_filter => 1,
135 135 :f => ['tracker_id'],
136 136 :op => {'tracker_id' => '='},
137 137 :v => {'tracker_id' => ['1']}
138 138 assert_response :success
139 139 assert_template 'index'
140 140 assert_not_nil assigns(:issues)
141 141
142 142 query = assigns(:query)
143 143 assert_not_nil query
144 144 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
145 145 end
146 146
147 147 def test_index_with_short_filters
148 148
149 149 to_test = {
150 150 'status_id' => {
151 151 'o' => { :op => 'o', :values => [''] },
152 152 'c' => { :op => 'c', :values => [''] },
153 153 '7' => { :op => '=', :values => ['7'] },
154 154 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
155 155 '=7' => { :op => '=', :values => ['7'] },
156 156 '!3' => { :op => '!', :values => ['3'] },
157 157 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
158 158 'subject' => {
159 159 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
160 160 'o' => { :op => '=', :values => ['o'] },
161 161 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
162 162 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
163 163 'tracker_id' => {
164 164 '3' => { :op => '=', :values => ['3'] },
165 165 '=3' => { :op => '=', :values => ['3'] }},
166 166 'start_date' => {
167 167 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
168 168 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
169 169 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
170 170 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
171 171 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
172 172 '<t+2' => { :op => '<t+', :values => ['2'] },
173 173 '>t+2' => { :op => '>t+', :values => ['2'] },
174 174 't+2' => { :op => 't+', :values => ['2'] },
175 175 't' => { :op => 't', :values => [''] },
176 176 'w' => { :op => 'w', :values => [''] },
177 177 '>t-2' => { :op => '>t-', :values => ['2'] },
178 178 '<t-2' => { :op => '<t-', :values => ['2'] },
179 179 't-2' => { :op => 't-', :values => ['2'] }},
180 180 'created_on' => {
181 181 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
182 182 '<t+2' => { :op => '=', :values => ['<t+2'] },
183 183 '>t+2' => { :op => '=', :values => ['>t+2'] },
184 184 't+2' => { :op => 't', :values => ['+2'] }},
185 185 'cf_1' => {
186 186 'c' => { :op => '=', :values => ['c'] },
187 187 '!c' => { :op => '!', :values => ['c'] },
188 188 '!*' => { :op => '!*', :values => [''] },
189 189 '*' => { :op => '*', :values => [''] }},
190 190 'estimated_hours' => {
191 191 '=13.4' => { :op => '=', :values => ['13.4'] },
192 192 '>=45' => { :op => '>=', :values => ['45'] },
193 193 '<=125' => { :op => '<=', :values => ['125'] },
194 194 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
195 195 '!*' => { :op => '!*', :values => [''] },
196 196 '*' => { :op => '*', :values => [''] }}
197 197 }
198 198
199 199 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
200 200
201 201 to_test.each do |field, expression_and_expected|
202 202 expression_and_expected.each do |filter_expression, expected|
203 203
204 204 get :index, :set_filter => 1, field => filter_expression
205 205
206 206 assert_response :success
207 207 assert_template 'index'
208 208 assert_not_nil assigns(:issues)
209 209
210 210 query = assigns(:query)
211 211 assert_not_nil query
212 212 assert query.has_filter?(field)
213 213 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
214 214 end
215 215 end
216 216
217 217 end
218 218
219 219 def test_index_with_project_and_empty_filters
220 220 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
221 221 assert_response :success
222 222 assert_template 'index'
223 223 assert_not_nil assigns(:issues)
224 224
225 225 query = assigns(:query)
226 226 assert_not_nil query
227 227 # no filter
228 228 assert_equal({}, query.filters)
229 229 end
230 230
231 231 def test_index_with_query
232 232 get :index, :project_id => 1, :query_id => 5
233 233 assert_response :success
234 234 assert_template 'index'
235 235 assert_not_nil assigns(:issues)
236 236 assert_nil assigns(:issue_count_by_group)
237 237 end
238 238
239 239 def test_index_with_query_grouped_by_tracker
240 240 get :index, :project_id => 1, :query_id => 6
241 241 assert_response :success
242 242 assert_template 'index'
243 243 assert_not_nil assigns(:issues)
244 244 assert_not_nil assigns(:issue_count_by_group)
245 245 end
246 246
247 247 def test_index_with_query_grouped_by_list_custom_field
248 248 get :index, :project_id => 1, :query_id => 9
249 249 assert_response :success
250 250 assert_template 'index'
251 251 assert_not_nil assigns(:issues)
252 252 assert_not_nil assigns(:issue_count_by_group)
253 253 end
254 254
255 255 def test_private_query_should_not_be_available_to_other_users
256 256 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
257 257 @request.session[:user_id] = 3
258 258
259 259 get :index, :query_id => q.id
260 260 assert_response 403
261 261 end
262 262
263 263 def test_private_query_should_be_available_to_its_user
264 264 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
265 265 @request.session[:user_id] = 2
266 266
267 267 get :index, :query_id => q.id
268 268 assert_response :success
269 269 end
270 270
271 271 def test_public_query_should_be_available_to_other_users
272 272 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
273 273 @request.session[:user_id] = 3
274 274
275 275 get :index, :query_id => q.id
276 276 assert_response :success
277 277 end
278 278
279 279 def test_index_sort_by_field_not_included_in_columns
280 280 Setting.issue_list_default_columns = %w(subject author)
281 281 get :index, :sort => 'tracker'
282 282 end
283 283
284 284 def test_index_csv_with_project
285 285 Setting.default_language = 'en'
286 286
287 287 get :index, :format => 'csv'
288 288 assert_response :success
289 289 assert_not_nil assigns(:issues)
290 290 assert_equal 'text/csv', @response.content_type
291 291 assert @response.body.starts_with?("#,")
292 292
293 293 get :index, :project_id => 1, :format => 'csv'
294 294 assert_response :success
295 295 assert_not_nil assigns(:issues)
296 296 assert_equal 'text/csv', @response.content_type
297 297 end
298 298
299 299 def test_index_csv_big_5
300 300 with_settings :default_language => "zh-TW" do
301 301 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
302 302 str_big5 = "\xa4@\xa4\xeb"
303 303 if str_utf8.respond_to?(:force_encoding)
304 304 str_utf8.force_encoding('UTF-8')
305 305 str_big5.force_encoding('Big5')
306 306 end
307 307 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
308 308 :status_id => 1, :priority => IssuePriority.all.first,
309 309 :subject => str_utf8)
310 310 assert issue.save
311 311
312 312 get :index, :project_id => 1,
313 313 :f => ['subject'],
314 314 :op => '=', :values => [str_utf8],
315 315 :format => 'csv'
316 316 assert_equal 'text/csv', @response.content_type
317 317 lines = @response.body.chomp.split("\n")
318 318 s1 = "\xaa\xac\xbaA"
319 319 if str_utf8.respond_to?(:force_encoding)
320 320 s1.force_encoding('Big5')
321 321 end
322 322 assert lines[0].include?(s1)
323 323 assert lines[1].include?(str_big5)
324 324 end
325 325 end
326 326
327 327 def test_index_csv_cannot_convert_should_be_replaced_big_5
328 328 with_settings :default_language => "zh-TW" do
329 329 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
330 330 if str_utf8.respond_to?(:force_encoding)
331 331 str_utf8.force_encoding('UTF-8')
332 332 end
333 333 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
334 334 :status_id => 1, :priority => IssuePriority.all.first,
335 335 :subject => str_utf8)
336 336 assert issue.save
337 337
338 338 get :index, :project_id => 1,
339 339 :f => ['subject'],
340 340 :op => '=', :values => [str_utf8],
341 341 :format => 'csv'
342 342 assert_equal 'text/csv', @response.content_type
343 343 lines = @response.body.chomp.split("\n")
344 344 s1 = "\xaa\xac\xbaA"
345 345 if str_utf8.respond_to?(:force_encoding)
346 346 s1.force_encoding('Big5')
347 347 end
348 348 assert lines[0].include?(s1)
349 349 s2 = lines[1].split(",")[5]
350 350 if s1.respond_to?(:force_encoding)
351 351 s3 = "\xa5H?"
352 352 s3.force_encoding('Big5')
353 353 assert_equal s3, s2
354 354 elsif RUBY_PLATFORM == 'java'
355 355 assert_equal "??", s2
356 356 else
357 357 assert_equal "\xa5H???", s2
358 358 end
359 359 end
360 360 end
361 361
362 362 def test_index_pdf
363 363 get :index, :format => 'pdf'
364 364 assert_response :success
365 365 assert_not_nil assigns(:issues)
366 366 assert_equal 'application/pdf', @response.content_type
367 367
368 368 get :index, :project_id => 1, :format => 'pdf'
369 369 assert_response :success
370 370 assert_not_nil assigns(:issues)
371 371 assert_equal 'application/pdf', @response.content_type
372 372
373 373 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
374 374 assert_response :success
375 375 assert_not_nil assigns(:issues)
376 376 assert_equal 'application/pdf', @response.content_type
377 377 end
378 378
379 379 def test_index_pdf_with_query_grouped_by_list_custom_field
380 380 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
381 381 assert_response :success
382 382 assert_not_nil assigns(:issues)
383 383 assert_not_nil assigns(:issue_count_by_group)
384 384 assert_equal 'application/pdf', @response.content_type
385 385 end
386 386
387 387 def test_index_sort
388 388 get :index, :sort => 'tracker,id:desc'
389 389 assert_response :success
390 390
391 391 sort_params = @request.session['issues_index_sort']
392 392 assert sort_params.is_a?(String)
393 393 assert_equal 'tracker,id:desc', sort_params
394 394
395 395 issues = assigns(:issues)
396 396 assert_not_nil issues
397 397 assert !issues.empty?
398 398 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
399 399 end
400 400
401 401 def test_index_with_columns
402 402 columns = ['tracker', 'subject', 'assigned_to']
403 403 get :index, :set_filter => 1, :c => columns
404 404 assert_response :success
405 405
406 406 # query should use specified columns
407 407 query = assigns(:query)
408 408 assert_kind_of Query, query
409 409 assert_equal columns, query.column_names.map(&:to_s)
410 410
411 411 # columns should be stored in session
412 412 assert_kind_of Hash, session[:query]
413 413 assert_kind_of Array, session[:query][:column_names]
414 414 assert_equal columns, session[:query][:column_names].map(&:to_s)
415 415
416 416 # ensure only these columns are kept in the selected columns list
417 417 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
418 418 :children => { :count => 3 }
419 419 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
420 420 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
421 421 end
422 422
423 423 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
424 424 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
425 425 get :index, :set_filter => 1
426 426
427 427 # query should use specified columns
428 428 query = assigns(:query)
429 429 assert_kind_of Query, query
430 430 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
431 431 end
432 432
433 433 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
434 434 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
435 435 columns = ['tracker', 'subject', 'assigned_to']
436 436 get :index, :set_filter => 1, :c => columns
437 437
438 438 # query should use specified columns
439 439 query = assigns(:query)
440 440 assert_kind_of Query, query
441 441 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
442 442 end
443 443
444 444 def test_index_with_custom_field_column
445 445 columns = %w(tracker subject cf_2)
446 446 get :index, :set_filter => 1, :c => columns
447 447 assert_response :success
448 448
449 449 # query should use specified columns
450 450 query = assigns(:query)
451 451 assert_kind_of Query, query
452 452 assert_equal columns, query.column_names.map(&:to_s)
453 453
454 454 assert_tag :td,
455 455 :attributes => {:class => 'cf_2 string'},
456 456 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
457 457 end
458 458
459 459 def test_index_send_html_if_query_is_invalid
460 460 get :index, :f => ['start_date'], :op => {:start_date => '='}
461 461 assert_equal 'text/html', @response.content_type
462 462 assert_template 'index'
463 463 end
464 464
465 465 def test_index_send_nothing_if_query_is_invalid
466 466 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
467 467 assert_equal 'text/csv', @response.content_type
468 468 assert @response.body.blank?
469 469 end
470 470
471 471 def test_show_by_anonymous
472 472 get :show, :id => 1
473 473 assert_response :success
474 474 assert_template 'show'
475 475 assert_not_nil assigns(:issue)
476 476 assert_equal Issue.find(1), assigns(:issue)
477 477
478 478 # anonymous role is allowed to add a note
479 479 assert_tag :tag => 'form',
480 480 :descendant => { :tag => 'fieldset',
481 481 :child => { :tag => 'legend',
482 482 :content => /Notes/ } }
483 assert_tag :tag => 'title',
484 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
483 485 end
484 486
485 487 def test_show_by_manager
486 488 @request.session[:user_id] = 2
487 489 get :show, :id => 1
488 490 assert_response :success
489 491
490 492 assert_tag :tag => 'a',
491 493 :content => /Quote/
492 494
493 495 assert_tag :tag => 'form',
494 496 :descendant => { :tag => 'fieldset',
495 497 :child => { :tag => 'legend',
496 498 :content => /Change properties/ } },
497 499 :descendant => { :tag => 'fieldset',
498 500 :child => { :tag => 'legend',
499 501 :content => /Log time/ } },
500 502 :descendant => { :tag => 'fieldset',
501 503 :child => { :tag => 'legend',
502 504 :content => /Notes/ } }
503 505 end
504 506
505 507 def test_update_form_should_not_display_inactive_enumerations
506 508 @request.session[:user_id] = 2
507 509 get :show, :id => 1
508 510 assert_response :success
509 511
510 512 assert ! IssuePriority.find(15).active?
511 513 assert_no_tag :option, :attributes => {:value => '15'},
512 514 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
513 515 end
514 516
515 517 def test_update_form_should_allow_attachment_upload
516 518 @request.session[:user_id] = 2
517 519 get :show, :id => 1
518 520
519 521 assert_tag :tag => 'form',
520 522 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
521 523 :descendant => {
522 524 :tag => 'input',
523 525 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
524 526 }
525 527 end
526 528
527 529 def test_show_should_deny_anonymous_access_without_permission
528 530 Role.anonymous.remove_permission!(:view_issues)
529 531 get :show, :id => 1
530 532 assert_response :redirect
531 533 end
532 534
533 535 def test_show_should_deny_anonymous_access_to_private_issue
534 536 Issue.update_all(["is_private = ?", true], "id = 1")
535 537 get :show, :id => 1
536 538 assert_response :redirect
537 539 end
538 540
539 541 def test_show_should_deny_non_member_access_without_permission
540 542 Role.non_member.remove_permission!(:view_issues)
541 543 @request.session[:user_id] = 9
542 544 get :show, :id => 1
543 545 assert_response 403
544 546 end
545 547
546 548 def test_show_should_deny_non_member_access_to_private_issue
547 549 Issue.update_all(["is_private = ?", true], "id = 1")
548 550 @request.session[:user_id] = 9
549 551 get :show, :id => 1
550 552 assert_response 403
551 553 end
552 554
553 555 def test_show_should_deny_member_access_without_permission
554 556 Role.find(1).remove_permission!(:view_issues)
555 557 @request.session[:user_id] = 2
556 558 get :show, :id => 1
557 559 assert_response 403
558 560 end
559 561
560 562 def test_show_should_deny_member_access_to_private_issue_without_permission
561 563 Issue.update_all(["is_private = ?", true], "id = 1")
562 564 @request.session[:user_id] = 3
563 565 get :show, :id => 1
564 566 assert_response 403
565 567 end
566 568
567 569 def test_show_should_allow_author_access_to_private_issue
568 570 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
569 571 @request.session[:user_id] = 3
570 572 get :show, :id => 1
571 573 assert_response :success
572 574 end
573 575
574 576 def test_show_should_allow_assignee_access_to_private_issue
575 577 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
576 578 @request.session[:user_id] = 3
577 579 get :show, :id => 1
578 580 assert_response :success
579 581 end
580 582
581 583 def test_show_should_allow_member_access_to_private_issue_with_permission
582 584 Issue.update_all(["is_private = ?", true], "id = 1")
583 585 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
584 586 @request.session[:user_id] = 3
585 587 get :show, :id => 1
586 588 assert_response :success
587 589 end
588 590
589 591 def test_show_should_not_disclose_relations_to_invisible_issues
590 592 Setting.cross_project_issue_relations = '1'
591 593 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
592 594 # Relation to a private project issue
593 595 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
594 596
595 597 get :show, :id => 1
596 598 assert_response :success
597 599
598 600 assert_tag :div, :attributes => { :id => 'relations' },
599 601 :descendant => { :tag => 'a', :content => /#2$/ }
600 602 assert_no_tag :div, :attributes => { :id => 'relations' },
601 603 :descendant => { :tag => 'a', :content => /#4$/ }
602 604 end
603 605
604 606 def test_show_atom
605 607 get :show, :id => 2, :format => 'atom'
606 608 assert_response :success
607 609 assert_template 'journals/index'
608 610 # Inline image
609 611 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
610 612 end
611 613
612 614 def test_show_export_to_pdf
613 615 get :show, :id => 3, :format => 'pdf'
614 616 assert_response :success
615 617 assert_equal 'application/pdf', @response.content_type
616 618 assert @response.body.starts_with?('%PDF')
617 619 assert_not_nil assigns(:issue)
618 620 end
619 621
620 622 def test_get_new
621 623 @request.session[:user_id] = 2
622 624 get :new, :project_id => 1, :tracker_id => 1
623 625 assert_response :success
624 626 assert_template 'new'
625 627
626 628 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
627 629 :value => 'Default string' }
628 630
629 631 # Be sure we don't display inactive IssuePriorities
630 632 assert ! IssuePriority.find(15).active?
631 633 assert_no_tag :option, :attributes => {:value => '15'},
632 634 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
633 635 end
634 636
635 637 def test_get_new_without_default_start_date_is_creation_date
636 638 Setting.default_issue_start_date_to_creation_date = 0
637 639
638 640 @request.session[:user_id] = 2
639 641 get :new, :project_id => 1, :tracker_id => 1
640 642 assert_response :success
641 643 assert_template 'new'
642 644
643 645 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
644 646 :value => nil }
645 647 end
646 648
647 649 def test_get_new_with_default_start_date_is_creation_date
648 650 Setting.default_issue_start_date_to_creation_date = 1
649 651
650 652 @request.session[:user_id] = 2
651 653 get :new, :project_id => 1, :tracker_id => 1
652 654 assert_response :success
653 655 assert_template 'new'
654 656
655 657 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
656 658 :value => Date.today.to_s }
657 659 end
658 660
659 661 def test_get_new_form_should_allow_attachment_upload
660 662 @request.session[:user_id] = 2
661 663 get :new, :project_id => 1, :tracker_id => 1
662 664
663 665 assert_tag :tag => 'form',
664 666 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
665 667 :descendant => {
666 668 :tag => 'input',
667 669 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
668 670 }
669 671 end
670 672
671 673 def test_get_new_without_tracker_id
672 674 @request.session[:user_id] = 2
673 675 get :new, :project_id => 1
674 676 assert_response :success
675 677 assert_template 'new'
676 678
677 679 issue = assigns(:issue)
678 680 assert_not_nil issue
679 681 assert_equal Project.find(1).trackers.first, issue.tracker
680 682 end
681 683
682 684 def test_get_new_with_no_default_status_should_display_an_error
683 685 @request.session[:user_id] = 2
684 686 IssueStatus.delete_all
685 687
686 688 get :new, :project_id => 1
687 689 assert_response 500
688 690 assert_error_tag :content => /No default issue/
689 691 end
690 692
691 693 def test_get_new_with_no_tracker_should_display_an_error
692 694 @request.session[:user_id] = 2
693 695 Tracker.delete_all
694 696
695 697 get :new, :project_id => 1
696 698 assert_response 500
697 699 assert_error_tag :content => /No tracker/
698 700 end
699 701
700 702 def test_update_new_form
701 703 @request.session[:user_id] = 2
702 704 xhr :post, :new, :project_id => 1,
703 705 :issue => {:tracker_id => 2,
704 706 :subject => 'This is the test_new issue',
705 707 :description => 'This is the description',
706 708 :priority_id => 5}
707 709 assert_response :success
708 710 assert_template 'attributes'
709 711
710 712 issue = assigns(:issue)
711 713 assert_kind_of Issue, issue
712 714 assert_equal 1, issue.project_id
713 715 assert_equal 2, issue.tracker_id
714 716 assert_equal 'This is the test_new issue', issue.subject
715 717 end
716 718
717 719 def test_post_create
718 720 @request.session[:user_id] = 2
719 721 assert_difference 'Issue.count' do
720 722 post :create, :project_id => 1,
721 723 :issue => {:tracker_id => 3,
722 724 :status_id => 2,
723 725 :subject => 'This is the test_new issue',
724 726 :description => 'This is the description',
725 727 :priority_id => 5,
726 728 :start_date => '2010-11-07',
727 729 :estimated_hours => '',
728 730 :custom_field_values => {'2' => 'Value for field 2'}}
729 731 end
730 732 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
731 733
732 734 issue = Issue.find_by_subject('This is the test_new issue')
733 735 assert_not_nil issue
734 736 assert_equal 2, issue.author_id
735 737 assert_equal 3, issue.tracker_id
736 738 assert_equal 2, issue.status_id
737 739 assert_equal Date.parse('2010-11-07'), issue.start_date
738 740 assert_nil issue.estimated_hours
739 741 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
740 742 assert_not_nil v
741 743 assert_equal 'Value for field 2', v.value
742 744 end
743 745
744 746 def test_post_new_with_group_assignment
745 747 group = Group.find(11)
746 748 project = Project.find(1)
747 749 project.members << Member.new(:principal => group, :roles => [Role.first])
748 750
749 751 with_settings :issue_group_assignment => '1' do
750 752 @request.session[:user_id] = 2
751 753 assert_difference 'Issue.count' do
752 754 post :create, :project_id => project.id,
753 755 :issue => {:tracker_id => 3,
754 756 :status_id => 1,
755 757 :subject => 'This is the test_new_with_group_assignment issue',
756 758 :assigned_to_id => group.id}
757 759 end
758 760 end
759 761 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
760 762
761 763 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
762 764 assert_not_nil issue
763 765 assert_equal group, issue.assigned_to
764 766 end
765 767
766 768 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
767 769 Setting.default_issue_start_date_to_creation_date = 0
768 770
769 771 @request.session[:user_id] = 2
770 772 assert_difference 'Issue.count' do
771 773 post :create, :project_id => 1,
772 774 :issue => {:tracker_id => 3,
773 775 :status_id => 2,
774 776 :subject => 'This is the test_new issue',
775 777 :description => 'This is the description',
776 778 :priority_id => 5,
777 779 :estimated_hours => '',
778 780 :custom_field_values => {'2' => 'Value for field 2'}}
779 781 end
780 782 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
781 783
782 784 issue = Issue.find_by_subject('This is the test_new issue')
783 785 assert_not_nil issue
784 786 assert_nil issue.start_date
785 787 end
786 788
787 789 def test_post_create_without_start_date_and_default_start_date_is_creation_date
788 790 Setting.default_issue_start_date_to_creation_date = 1
789 791
790 792 @request.session[:user_id] = 2
791 793 assert_difference 'Issue.count' do
792 794 post :create, :project_id => 1,
793 795 :issue => {:tracker_id => 3,
794 796 :status_id => 2,
795 797 :subject => 'This is the test_new issue',
796 798 :description => 'This is the description',
797 799 :priority_id => 5,
798 800 :estimated_hours => '',
799 801 :custom_field_values => {'2' => 'Value for field 2'}}
800 802 end
801 803 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
802 804
803 805 issue = Issue.find_by_subject('This is the test_new issue')
804 806 assert_not_nil issue
805 807 assert_equal Date.today, issue.start_date
806 808 end
807 809
808 810 def test_post_create_and_continue
809 811 @request.session[:user_id] = 2
810 812 assert_difference 'Issue.count' do
811 813 post :create, :project_id => 1,
812 814 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
813 815 :continue => ''
814 816 end
815 817
816 818 issue = Issue.first(:order => 'id DESC')
817 819 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
818 820 assert_not_nil flash[:notice], "flash was not set"
819 821 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
820 822 end
821 823
822 824 def test_post_create_without_custom_fields_param
823 825 @request.session[:user_id] = 2
824 826 assert_difference 'Issue.count' do
825 827 post :create, :project_id => 1,
826 828 :issue => {:tracker_id => 1,
827 829 :subject => 'This is the test_new issue',
828 830 :description => 'This is the description',
829 831 :priority_id => 5}
830 832 end
831 833 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
832 834 end
833 835
834 836 def test_post_create_with_required_custom_field_and_without_custom_fields_param
835 837 field = IssueCustomField.find_by_name('Database')
836 838 field.update_attribute(:is_required, true)
837 839
838 840 @request.session[:user_id] = 2
839 841 post :create, :project_id => 1,
840 842 :issue => {:tracker_id => 1,
841 843 :subject => 'This is the test_new issue',
842 844 :description => 'This is the description',
843 845 :priority_id => 5}
844 846 assert_response :success
845 847 assert_template 'new'
846 848 issue = assigns(:issue)
847 849 assert_not_nil issue
848 850 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
849 851 end
850 852
851 853 def test_post_create_with_watchers
852 854 @request.session[:user_id] = 2
853 855 ActionMailer::Base.deliveries.clear
854 856
855 857 assert_difference 'Watcher.count', 2 do
856 858 post :create, :project_id => 1,
857 859 :issue => {:tracker_id => 1,
858 860 :subject => 'This is a new issue with watchers',
859 861 :description => 'This is the description',
860 862 :priority_id => 5,
861 863 :watcher_user_ids => ['2', '3']}
862 864 end
863 865 issue = Issue.find_by_subject('This is a new issue with watchers')
864 866 assert_not_nil issue
865 867 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
866 868
867 869 # Watchers added
868 870 assert_equal [2, 3], issue.watcher_user_ids.sort
869 871 assert issue.watched_by?(User.find(3))
870 872 # Watchers notified
871 873 mail = ActionMailer::Base.deliveries.last
872 874 assert_kind_of TMail::Mail, mail
873 875 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
874 876 end
875 877
876 878 def test_post_create_subissue
877 879 @request.session[:user_id] = 2
878 880
879 881 assert_difference 'Issue.count' do
880 882 post :create, :project_id => 1,
881 883 :issue => {:tracker_id => 1,
882 884 :subject => 'This is a child issue',
883 885 :parent_issue_id => 2}
884 886 end
885 887 issue = Issue.find_by_subject('This is a child issue')
886 888 assert_not_nil issue
887 889 assert_equal Issue.find(2), issue.parent
888 890 end
889 891
890 892 def test_post_create_subissue_with_non_numeric_parent_id
891 893 @request.session[:user_id] = 2
892 894
893 895 assert_difference 'Issue.count' do
894 896 post :create, :project_id => 1,
895 897 :issue => {:tracker_id => 1,
896 898 :subject => 'This is a child issue',
897 899 :parent_issue_id => 'ABC'}
898 900 end
899 901 issue = Issue.find_by_subject('This is a child issue')
900 902 assert_not_nil issue
901 903 assert_nil issue.parent
902 904 end
903 905
904 906 def test_post_create_private
905 907 @request.session[:user_id] = 2
906 908
907 909 assert_difference 'Issue.count' do
908 910 post :create, :project_id => 1,
909 911 :issue => {:tracker_id => 1,
910 912 :subject => 'This is a private issue',
911 913 :is_private => '1'}
912 914 end
913 915 issue = Issue.first(:order => 'id DESC')
914 916 assert issue.is_private?
915 917 end
916 918
917 919 def test_post_create_private_with_set_own_issues_private_permission
918 920 role = Role.find(1)
919 921 role.remove_permission! :set_issues_private
920 922 role.add_permission! :set_own_issues_private
921 923
922 924 @request.session[:user_id] = 2
923 925
924 926 assert_difference 'Issue.count' do
925 927 post :create, :project_id => 1,
926 928 :issue => {:tracker_id => 1,
927 929 :subject => 'This is a private issue',
928 930 :is_private => '1'}
929 931 end
930 932 issue = Issue.first(:order => 'id DESC')
931 933 assert issue.is_private?
932 934 end
933 935
934 936 def test_post_create_should_send_a_notification
935 937 ActionMailer::Base.deliveries.clear
936 938 @request.session[:user_id] = 2
937 939 assert_difference 'Issue.count' do
938 940 post :create, :project_id => 1,
939 941 :issue => {:tracker_id => 3,
940 942 :subject => 'This is the test_new issue',
941 943 :description => 'This is the description',
942 944 :priority_id => 5,
943 945 :estimated_hours => '',
944 946 :custom_field_values => {'2' => 'Value for field 2'}}
945 947 end
946 948 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
947 949
948 950 assert_equal 1, ActionMailer::Base.deliveries.size
949 951 end
950 952
951 953 def test_post_create_should_preserve_fields_values_on_validation_failure
952 954 @request.session[:user_id] = 2
953 955 post :create, :project_id => 1,
954 956 :issue => {:tracker_id => 1,
955 957 # empty subject
956 958 :subject => '',
957 959 :description => 'This is a description',
958 960 :priority_id => 6,
959 961 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
960 962 assert_response :success
961 963 assert_template 'new'
962 964
963 965 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
964 966 :content => 'This is a description'
965 967 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
966 968 :child => { :tag => 'option', :attributes => { :selected => 'selected',
967 969 :value => '6' },
968 970 :content => 'High' }
969 971 # Custom fields
970 972 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
971 973 :child => { :tag => 'option', :attributes => { :selected => 'selected',
972 974 :value => 'Oracle' },
973 975 :content => 'Oracle' }
974 976 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
975 977 :value => 'Value for field 2'}
976 978 end
977 979
978 980 def test_post_create_should_ignore_non_safe_attributes
979 981 @request.session[:user_id] = 2
980 982 assert_nothing_raised do
981 983 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
982 984 end
983 985 end
984 986
985 987 def test_post_create_with_attachment
986 988 set_tmp_attachments_directory
987 989 @request.session[:user_id] = 2
988 990
989 991 assert_difference 'Issue.count' do
990 992 assert_difference 'Attachment.count' do
991 993 post :create, :project_id => 1,
992 994 :issue => { :tracker_id => '1', :subject => 'With attachment' },
993 995 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
994 996 end
995 997 end
996 998
997 999 issue = Issue.first(:order => 'id DESC')
998 1000 attachment = Attachment.first(:order => 'id DESC')
999 1001
1000 1002 assert_equal issue, attachment.container
1001 1003 assert_equal 2, attachment.author_id
1002 1004 assert_equal 'testfile.txt', attachment.filename
1003 1005 assert_equal 'text/plain', attachment.content_type
1004 1006 assert_equal 'test file', attachment.description
1005 1007 assert_equal 59, attachment.filesize
1006 1008 assert File.exists?(attachment.diskfile)
1007 1009 assert_equal 59, File.size(attachment.diskfile)
1008 1010 end
1009 1011
1010 1012 context "without workflow privilege" do
1011 1013 setup do
1012 1014 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1013 1015 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1014 1016 end
1015 1017
1016 1018 context "#new" do
1017 1019 should "propose default status only" do
1018 1020 get :new, :project_id => 1
1019 1021 assert_response :success
1020 1022 assert_template 'new'
1021 1023 assert_tag :tag => 'select',
1022 1024 :attributes => {:name => 'issue[status_id]'},
1023 1025 :children => {:count => 1},
1024 1026 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1025 1027 end
1026 1028
1027 1029 should "accept default status" do
1028 1030 assert_difference 'Issue.count' do
1029 1031 post :create, :project_id => 1,
1030 1032 :issue => {:tracker_id => 1,
1031 1033 :subject => 'This is an issue',
1032 1034 :status_id => 1}
1033 1035 end
1034 1036 issue = Issue.last(:order => 'id')
1035 1037 assert_equal IssueStatus.default, issue.status
1036 1038 end
1037 1039
1038 1040 should "ignore unauthorized status" do
1039 1041 assert_difference 'Issue.count' do
1040 1042 post :create, :project_id => 1,
1041 1043 :issue => {:tracker_id => 1,
1042 1044 :subject => 'This is an issue',
1043 1045 :status_id => 3}
1044 1046 end
1045 1047 issue = Issue.last(:order => 'id')
1046 1048 assert_equal IssueStatus.default, issue.status
1047 1049 end
1048 1050 end
1049 1051
1050 1052 context "#update" do
1051 1053 should "ignore status change" do
1052 1054 assert_difference 'Journal.count' do
1053 1055 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1054 1056 end
1055 1057 assert_equal 1, Issue.find(1).status_id
1056 1058 end
1057 1059
1058 1060 should "ignore attributes changes" do
1059 1061 assert_difference 'Journal.count' do
1060 1062 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1061 1063 end
1062 1064 issue = Issue.find(1)
1063 1065 assert_equal "Can't print recipes", issue.subject
1064 1066 assert_nil issue.assigned_to
1065 1067 end
1066 1068 end
1067 1069 end
1068 1070
1069 1071 context "with workflow privilege" do
1070 1072 setup do
1071 1073 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1072 1074 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1073 1075 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1074 1076 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1075 1077 end
1076 1078
1077 1079 context "#update" do
1078 1080 should "accept authorized status" do
1079 1081 assert_difference 'Journal.count' do
1080 1082 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1081 1083 end
1082 1084 assert_equal 3, Issue.find(1).status_id
1083 1085 end
1084 1086
1085 1087 should "ignore unauthorized status" do
1086 1088 assert_difference 'Journal.count' do
1087 1089 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1088 1090 end
1089 1091 assert_equal 1, Issue.find(1).status_id
1090 1092 end
1091 1093
1092 1094 should "accept authorized attributes changes" do
1093 1095 assert_difference 'Journal.count' do
1094 1096 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1095 1097 end
1096 1098 issue = Issue.find(1)
1097 1099 assert_equal 2, issue.assigned_to_id
1098 1100 end
1099 1101
1100 1102 should "ignore unauthorized attributes changes" do
1101 1103 assert_difference 'Journal.count' do
1102 1104 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1103 1105 end
1104 1106 issue = Issue.find(1)
1105 1107 assert_equal "Can't print recipes", issue.subject
1106 1108 end
1107 1109 end
1108 1110
1109 1111 context "and :edit_issues permission" do
1110 1112 setup do
1111 1113 Role.anonymous.add_permission! :add_issues, :edit_issues
1112 1114 end
1113 1115
1114 1116 should "accept authorized status" do
1115 1117 assert_difference 'Journal.count' do
1116 1118 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1117 1119 end
1118 1120 assert_equal 3, Issue.find(1).status_id
1119 1121 end
1120 1122
1121 1123 should "ignore unauthorized status" do
1122 1124 assert_difference 'Journal.count' do
1123 1125 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1124 1126 end
1125 1127 assert_equal 1, Issue.find(1).status_id
1126 1128 end
1127 1129
1128 1130 should "accept authorized attributes changes" do
1129 1131 assert_difference 'Journal.count' do
1130 1132 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1131 1133 end
1132 1134 issue = Issue.find(1)
1133 1135 assert_equal "changed", issue.subject
1134 1136 assert_equal 2, issue.assigned_to_id
1135 1137 end
1136 1138 end
1137 1139 end
1138 1140
1139 1141 def test_copy_issue
1140 1142 @request.session[:user_id] = 2
1141 1143 get :new, :project_id => 1, :copy_from => 1
1142 1144 assert_template 'new'
1143 1145 assert_not_nil assigns(:issue)
1144 1146 orig = Issue.find(1)
1145 1147 assert_equal orig.subject, assigns(:issue).subject
1146 1148 end
1147 1149
1148 1150 def test_get_edit
1149 1151 @request.session[:user_id] = 2
1150 1152 get :edit, :id => 1
1151 1153 assert_response :success
1152 1154 assert_template 'edit'
1153 1155 assert_not_nil assigns(:issue)
1154 1156 assert_equal Issue.find(1), assigns(:issue)
1155 1157
1156 1158 # Be sure we don't display inactive IssuePriorities
1157 1159 assert ! IssuePriority.find(15).active?
1158 1160 assert_no_tag :option, :attributes => {:value => '15'},
1159 1161 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1160 1162 end
1161 1163
1162 1164 def test_get_edit_with_params
1163 1165 @request.session[:user_id] = 2
1164 1166 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1165 1167 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1166 1168 assert_response :success
1167 1169 assert_template 'edit'
1168 1170
1169 1171 issue = assigns(:issue)
1170 1172 assert_not_nil issue
1171 1173
1172 1174 assert_equal 5, issue.status_id
1173 1175 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1174 1176 :child => { :tag => 'option',
1175 1177 :content => 'Closed',
1176 1178 :attributes => { :selected => 'selected' } }
1177 1179
1178 1180 assert_equal 7, issue.priority_id
1179 1181 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1180 1182 :child => { :tag => 'option',
1181 1183 :content => 'Urgent',
1182 1184 :attributes => { :selected => 'selected' } }
1183 1185
1184 1186 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1185 1187 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1186 1188 :child => { :tag => 'option',
1187 1189 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1188 1190 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1189 1191 end
1190 1192
1191 1193 def test_update_edit_form
1192 1194 @request.session[:user_id] = 2
1193 1195 xhr :post, :new, :project_id => 1,
1194 1196 :id => 1,
1195 1197 :issue => {:tracker_id => 2,
1196 1198 :subject => 'This is the test_new issue',
1197 1199 :description => 'This is the description',
1198 1200 :priority_id => 5}
1199 1201 assert_response :success
1200 1202 assert_template 'attributes'
1201 1203
1202 1204 issue = assigns(:issue)
1203 1205 assert_kind_of Issue, issue
1204 1206 assert_equal 1, issue.id
1205 1207 assert_equal 1, issue.project_id
1206 1208 assert_equal 2, issue.tracker_id
1207 1209 assert_equal 'This is the test_new issue', issue.subject
1208 1210 end
1209 1211
1210 1212 def test_update_using_invalid_http_verbs
1211 1213 @request.session[:user_id] = 2
1212 1214 subject = 'Updated by an invalid http verb'
1213 1215
1214 1216 get :update, :id => 1, :issue => {:subject => subject}
1215 1217 assert_not_equal subject, Issue.find(1).subject
1216 1218
1217 1219 post :update, :id => 1, :issue => {:subject => subject}
1218 1220 assert_not_equal subject, Issue.find(1).subject
1219 1221
1220 1222 delete :update, :id => 1, :issue => {:subject => subject}
1221 1223 assert_not_equal subject, Issue.find(1).subject
1222 1224 end
1223 1225
1224 1226 def test_put_update_without_custom_fields_param
1225 1227 @request.session[:user_id] = 2
1226 1228 ActionMailer::Base.deliveries.clear
1227 1229
1228 1230 issue = Issue.find(1)
1229 1231 assert_equal '125', issue.custom_value_for(2).value
1230 1232 old_subject = issue.subject
1231 1233 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1232 1234
1233 1235 assert_difference('Journal.count') do
1234 1236 assert_difference('JournalDetail.count', 2) do
1235 1237 put :update, :id => 1, :issue => {:subject => new_subject,
1236 1238 :priority_id => '6',
1237 1239 :category_id => '1' # no change
1238 1240 }
1239 1241 end
1240 1242 end
1241 1243 assert_redirected_to :action => 'show', :id => '1'
1242 1244 issue.reload
1243 1245 assert_equal new_subject, issue.subject
1244 1246 # Make sure custom fields were not cleared
1245 1247 assert_equal '125', issue.custom_value_for(2).value
1246 1248
1247 1249 mail = ActionMailer::Base.deliveries.last
1248 1250 assert_kind_of TMail::Mail, mail
1249 1251 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1250 1252 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1251 1253 end
1252 1254
1253 1255 def test_put_update_with_custom_field_change
1254 1256 @request.session[:user_id] = 2
1255 1257 issue = Issue.find(1)
1256 1258 assert_equal '125', issue.custom_value_for(2).value
1257 1259
1258 1260 assert_difference('Journal.count') do
1259 1261 assert_difference('JournalDetail.count', 3) do
1260 1262 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1261 1263 :priority_id => '6',
1262 1264 :category_id => '1', # no change
1263 1265 :custom_field_values => { '2' => 'New custom value' }
1264 1266 }
1265 1267 end
1266 1268 end
1267 1269 assert_redirected_to :action => 'show', :id => '1'
1268 1270 issue.reload
1269 1271 assert_equal 'New custom value', issue.custom_value_for(2).value
1270 1272
1271 1273 mail = ActionMailer::Base.deliveries.last
1272 1274 assert_kind_of TMail::Mail, mail
1273 1275 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1274 1276 end
1275 1277
1276 1278 def test_put_update_with_status_and_assignee_change
1277 1279 issue = Issue.find(1)
1278 1280 assert_equal 1, issue.status_id
1279 1281 @request.session[:user_id] = 2
1280 1282 assert_difference('TimeEntry.count', 0) do
1281 1283 put :update,
1282 1284 :id => 1,
1283 1285 :issue => { :status_id => 2, :assigned_to_id => 3 },
1284 1286 :notes => 'Assigned to dlopper',
1285 1287 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1286 1288 end
1287 1289 assert_redirected_to :action => 'show', :id => '1'
1288 1290 issue.reload
1289 1291 assert_equal 2, issue.status_id
1290 1292 j = Journal.find(:first, :order => 'id DESC')
1291 1293 assert_equal 'Assigned to dlopper', j.notes
1292 1294 assert_equal 2, j.details.size
1293 1295
1294 1296 mail = ActionMailer::Base.deliveries.last
1295 1297 assert mail.body.include?("Status changed from New to Assigned")
1296 1298 # subject should contain the new status
1297 1299 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1298 1300 end
1299 1301
1300 1302 def test_put_update_with_note_only
1301 1303 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1302 1304 # anonymous user
1303 1305 put :update,
1304 1306 :id => 1,
1305 1307 :notes => notes
1306 1308 assert_redirected_to :action => 'show', :id => '1'
1307 1309 j = Journal.find(:first, :order => 'id DESC')
1308 1310 assert_equal notes, j.notes
1309 1311 assert_equal 0, j.details.size
1310 1312 assert_equal User.anonymous, j.user
1311 1313
1312 1314 mail = ActionMailer::Base.deliveries.last
1313 1315 assert mail.body.include?(notes)
1314 1316 end
1315 1317
1316 1318 def test_put_update_with_note_and_spent_time
1317 1319 @request.session[:user_id] = 2
1318 1320 spent_hours_before = Issue.find(1).spent_hours
1319 1321 assert_difference('TimeEntry.count') do
1320 1322 put :update,
1321 1323 :id => 1,
1322 1324 :notes => '2.5 hours added',
1323 1325 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1324 1326 end
1325 1327 assert_redirected_to :action => 'show', :id => '1'
1326 1328
1327 1329 issue = Issue.find(1)
1328 1330
1329 1331 j = Journal.find(:first, :order => 'id DESC')
1330 1332 assert_equal '2.5 hours added', j.notes
1331 1333 assert_equal 0, j.details.size
1332 1334
1333 1335 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1334 1336 assert_not_nil t
1335 1337 assert_equal 2.5, t.hours
1336 1338 assert_equal spent_hours_before + 2.5, issue.spent_hours
1337 1339 end
1338 1340
1339 1341 def test_put_update_with_attachment_only
1340 1342 set_tmp_attachments_directory
1341 1343
1342 1344 # Delete all fixtured journals, a race condition can occur causing the wrong
1343 1345 # journal to get fetched in the next find.
1344 1346 Journal.delete_all
1345 1347
1346 1348 # anonymous user
1347 1349 assert_difference 'Attachment.count' do
1348 1350 put :update, :id => 1,
1349 1351 :notes => '',
1350 1352 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1351 1353 end
1352 1354
1353 1355 assert_redirected_to :action => 'show', :id => '1'
1354 1356 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1355 1357 assert j.notes.blank?
1356 1358 assert_equal 1, j.details.size
1357 1359 assert_equal 'testfile.txt', j.details.first.value
1358 1360 assert_equal User.anonymous, j.user
1359 1361
1360 1362 attachment = Attachment.first(:order => 'id DESC')
1361 1363 assert_equal Issue.find(1), attachment.container
1362 1364 assert_equal User.anonymous, attachment.author
1363 1365 assert_equal 'testfile.txt', attachment.filename
1364 1366 assert_equal 'text/plain', attachment.content_type
1365 1367 assert_equal 'test file', attachment.description
1366 1368 assert_equal 59, attachment.filesize
1367 1369 assert File.exists?(attachment.diskfile)
1368 1370 assert_equal 59, File.size(attachment.diskfile)
1369 1371
1370 1372 mail = ActionMailer::Base.deliveries.last
1371 1373 assert mail.body.include?('testfile.txt')
1372 1374 end
1373 1375
1374 1376 def test_put_update_with_attachment_that_fails_to_save
1375 1377 set_tmp_attachments_directory
1376 1378
1377 1379 # Delete all fixtured journals, a race condition can occur causing the wrong
1378 1380 # journal to get fetched in the next find.
1379 1381 Journal.delete_all
1380 1382
1381 1383 # Mock out the unsaved attachment
1382 1384 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1383 1385
1384 1386 # anonymous user
1385 1387 put :update,
1386 1388 :id => 1,
1387 1389 :notes => '',
1388 1390 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1389 1391 assert_redirected_to :action => 'show', :id => '1'
1390 1392 assert_equal '1 file(s) could not be saved.', flash[:warning]
1391 1393
1392 1394 end if Object.const_defined?(:Mocha)
1393 1395
1394 1396 def test_put_update_with_no_change
1395 1397 issue = Issue.find(1)
1396 1398 issue.journals.clear
1397 1399 ActionMailer::Base.deliveries.clear
1398 1400
1399 1401 put :update,
1400 1402 :id => 1,
1401 1403 :notes => ''
1402 1404 assert_redirected_to :action => 'show', :id => '1'
1403 1405
1404 1406 issue.reload
1405 1407 assert issue.journals.empty?
1406 1408 # No email should be sent
1407 1409 assert ActionMailer::Base.deliveries.empty?
1408 1410 end
1409 1411
1410 1412 def test_put_update_should_send_a_notification
1411 1413 @request.session[:user_id] = 2
1412 1414 ActionMailer::Base.deliveries.clear
1413 1415 issue = Issue.find(1)
1414 1416 old_subject = issue.subject
1415 1417 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1416 1418
1417 1419 put :update, :id => 1, :issue => {:subject => new_subject,
1418 1420 :priority_id => '6',
1419 1421 :category_id => '1' # no change
1420 1422 }
1421 1423 assert_equal 1, ActionMailer::Base.deliveries.size
1422 1424 end
1423 1425
1424 1426 def test_put_update_with_invalid_spent_time_hours_only
1425 1427 @request.session[:user_id] = 2
1426 1428 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1427 1429
1428 1430 assert_no_difference('Journal.count') do
1429 1431 put :update,
1430 1432 :id => 1,
1431 1433 :notes => notes,
1432 1434 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1433 1435 end
1434 1436 assert_response :success
1435 1437 assert_template 'edit'
1436 1438
1437 1439 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1438 1440 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1439 1441 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1440 1442 end
1441 1443
1442 1444 def test_put_update_with_invalid_spent_time_comments_only
1443 1445 @request.session[:user_id] = 2
1444 1446 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1445 1447
1446 1448 assert_no_difference('Journal.count') do
1447 1449 put :update,
1448 1450 :id => 1,
1449 1451 :notes => notes,
1450 1452 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1451 1453 end
1452 1454 assert_response :success
1453 1455 assert_template 'edit'
1454 1456
1455 1457 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1456 1458 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1457 1459 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1458 1460 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1459 1461 end
1460 1462
1461 1463 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1462 1464 issue = Issue.find(2)
1463 1465 @request.session[:user_id] = 2
1464 1466
1465 1467 put :update,
1466 1468 :id => issue.id,
1467 1469 :issue => {
1468 1470 :fixed_version_id => 4
1469 1471 }
1470 1472
1471 1473 assert_response :redirect
1472 1474 issue.reload
1473 1475 assert_equal 4, issue.fixed_version_id
1474 1476 assert_not_equal issue.project_id, issue.fixed_version.project_id
1475 1477 end
1476 1478
1477 1479 def test_put_update_should_redirect_back_using_the_back_url_parameter
1478 1480 issue = Issue.find(2)
1479 1481 @request.session[:user_id] = 2
1480 1482
1481 1483 put :update,
1482 1484 :id => issue.id,
1483 1485 :issue => {
1484 1486 :fixed_version_id => 4
1485 1487 },
1486 1488 :back_url => '/issues'
1487 1489
1488 1490 assert_response :redirect
1489 1491 assert_redirected_to '/issues'
1490 1492 end
1491 1493
1492 1494 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1493 1495 issue = Issue.find(2)
1494 1496 @request.session[:user_id] = 2
1495 1497
1496 1498 put :update,
1497 1499 :id => issue.id,
1498 1500 :issue => {
1499 1501 :fixed_version_id => 4
1500 1502 },
1501 1503 :back_url => 'http://google.com'
1502 1504
1503 1505 assert_response :redirect
1504 1506 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1505 1507 end
1506 1508
1507 1509 def test_get_bulk_edit
1508 1510 @request.session[:user_id] = 2
1509 1511 get :bulk_edit, :ids => [1, 2]
1510 1512 assert_response :success
1511 1513 assert_template 'bulk_edit'
1512 1514
1513 1515 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1514 1516
1515 1517 # Project specific custom field, date type
1516 1518 field = CustomField.find(9)
1517 1519 assert !field.is_for_all?
1518 1520 assert_equal 'date', field.field_format
1519 1521 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1520 1522
1521 1523 # System wide custom field
1522 1524 assert CustomField.find(1).is_for_all?
1523 1525 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1524 1526
1525 1527 # Be sure we don't display inactive IssuePriorities
1526 1528 assert ! IssuePriority.find(15).active?
1527 1529 assert_no_tag :option, :attributes => {:value => '15'},
1528 1530 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1529 1531 end
1530 1532
1531 1533 def test_get_bulk_edit_on_different_projects
1532 1534 @request.session[:user_id] = 2
1533 1535 get :bulk_edit, :ids => [1, 2, 6]
1534 1536 assert_response :success
1535 1537 assert_template 'bulk_edit'
1536 1538
1537 1539 # Can not set issues from different projects as children of an issue
1538 1540 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1539 1541
1540 1542 # Project specific custom field, date type
1541 1543 field = CustomField.find(9)
1542 1544 assert !field.is_for_all?
1543 1545 assert !field.project_ids.include?(Issue.find(6).project_id)
1544 1546 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1545 1547 end
1546 1548
1547 1549 def test_get_bulk_edit_with_user_custom_field
1548 1550 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1549 1551
1550 1552 @request.session[:user_id] = 2
1551 1553 get :bulk_edit, :ids => [1, 2]
1552 1554 assert_response :success
1553 1555 assert_template 'bulk_edit'
1554 1556
1555 1557 assert_tag :select,
1556 1558 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1557 1559 :children => {
1558 1560 :only => {:tag => 'option'},
1559 1561 :count => Project.find(1).users.count + 1
1560 1562 }
1561 1563 end
1562 1564
1563 1565 def test_get_bulk_edit_with_version_custom_field
1564 1566 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1565 1567
1566 1568 @request.session[:user_id] = 2
1567 1569 get :bulk_edit, :ids => [1, 2]
1568 1570 assert_response :success
1569 1571 assert_template 'bulk_edit'
1570 1572
1571 1573 assert_tag :select,
1572 1574 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1573 1575 :children => {
1574 1576 :only => {:tag => 'option'},
1575 1577 :count => Project.find(1).shared_versions.count + 1
1576 1578 }
1577 1579 end
1578 1580
1579 1581 def test_bulk_update
1580 1582 @request.session[:user_id] = 2
1581 1583 # update issues priority
1582 1584 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1583 1585 :issue => {:priority_id => 7,
1584 1586 :assigned_to_id => '',
1585 1587 :custom_field_values => {'2' => ''}}
1586 1588
1587 1589 assert_response 302
1588 1590 # check that the issues were updated
1589 1591 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1590 1592
1591 1593 issue = Issue.find(1)
1592 1594 journal = issue.journals.find(:first, :order => 'created_on DESC')
1593 1595 assert_equal '125', issue.custom_value_for(2).value
1594 1596 assert_equal 'Bulk editing', journal.notes
1595 1597 assert_equal 1, journal.details.size
1596 1598 end
1597 1599
1598 1600 def test_bulk_update_with_group_assignee
1599 1601 group = Group.find(11)
1600 1602 project = Project.find(1)
1601 1603 project.members << Member.new(:principal => group, :roles => [Role.first])
1602 1604
1603 1605 @request.session[:user_id] = 2
1604 1606 # update issues assignee
1605 1607 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1606 1608 :issue => {:priority_id => '',
1607 1609 :assigned_to_id => group.id,
1608 1610 :custom_field_values => {'2' => ''}}
1609 1611
1610 1612 assert_response 302
1611 1613 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1612 1614 end
1613 1615
1614 1616 def test_bulk_update_on_different_projects
1615 1617 @request.session[:user_id] = 2
1616 1618 # update issues priority
1617 1619 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1618 1620 :issue => {:priority_id => 7,
1619 1621 :assigned_to_id => '',
1620 1622 :custom_field_values => {'2' => ''}}
1621 1623
1622 1624 assert_response 302
1623 1625 # check that the issues were updated
1624 1626 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1625 1627
1626 1628 issue = Issue.find(1)
1627 1629 journal = issue.journals.find(:first, :order => 'created_on DESC')
1628 1630 assert_equal '125', issue.custom_value_for(2).value
1629 1631 assert_equal 'Bulk editing', journal.notes
1630 1632 assert_equal 1, journal.details.size
1631 1633 end
1632 1634
1633 1635 def test_bulk_update_on_different_projects_without_rights
1634 1636 @request.session[:user_id] = 3
1635 1637 user = User.find(3)
1636 1638 action = { :controller => "issues", :action => "bulk_update" }
1637 1639 assert user.allowed_to?(action, Issue.find(1).project)
1638 1640 assert ! user.allowed_to?(action, Issue.find(6).project)
1639 1641 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1640 1642 :issue => {:priority_id => 7,
1641 1643 :assigned_to_id => '',
1642 1644 :custom_field_values => {'2' => ''}}
1643 1645 assert_response 403
1644 1646 assert_not_equal "Bulk should fail", Journal.last.notes
1645 1647 end
1646 1648
1647 1649 def test_bullk_update_should_send_a_notification
1648 1650 @request.session[:user_id] = 2
1649 1651 ActionMailer::Base.deliveries.clear
1650 1652 post(:bulk_update,
1651 1653 {
1652 1654 :ids => [1, 2],
1653 1655 :notes => 'Bulk editing',
1654 1656 :issue => {
1655 1657 :priority_id => 7,
1656 1658 :assigned_to_id => '',
1657 1659 :custom_field_values => {'2' => ''}
1658 1660 }
1659 1661 })
1660 1662
1661 1663 assert_response 302
1662 1664 assert_equal 2, ActionMailer::Base.deliveries.size
1663 1665 end
1664 1666
1665 1667 def test_bulk_update_status
1666 1668 @request.session[:user_id] = 2
1667 1669 # update issues priority
1668 1670 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1669 1671 :issue => {:priority_id => '',
1670 1672 :assigned_to_id => '',
1671 1673 :status_id => '5'}
1672 1674
1673 1675 assert_response 302
1674 1676 issue = Issue.find(1)
1675 1677 assert issue.closed?
1676 1678 end
1677 1679
1678 1680 def test_bulk_update_parent_id
1679 1681 @request.session[:user_id] = 2
1680 1682 post :bulk_update, :ids => [1, 3],
1681 1683 :notes => 'Bulk editing parent',
1682 1684 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1683 1685
1684 1686 assert_response 302
1685 1687 parent = Issue.find(2)
1686 1688 assert_equal parent.id, Issue.find(1).parent_id
1687 1689 assert_equal parent.id, Issue.find(3).parent_id
1688 1690 assert_equal [1, 3], parent.children.collect(&:id).sort
1689 1691 end
1690 1692
1691 1693 def test_bulk_update_custom_field
1692 1694 @request.session[:user_id] = 2
1693 1695 # update issues priority
1694 1696 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1695 1697 :issue => {:priority_id => '',
1696 1698 :assigned_to_id => '',
1697 1699 :custom_field_values => {'2' => '777'}}
1698 1700
1699 1701 assert_response 302
1700 1702
1701 1703 issue = Issue.find(1)
1702 1704 journal = issue.journals.find(:first, :order => 'created_on DESC')
1703 1705 assert_equal '777', issue.custom_value_for(2).value
1704 1706 assert_equal 1, journal.details.size
1705 1707 assert_equal '125', journal.details.first.old_value
1706 1708 assert_equal '777', journal.details.first.value
1707 1709 end
1708 1710
1709 1711 def test_bulk_update_unassign
1710 1712 assert_not_nil Issue.find(2).assigned_to
1711 1713 @request.session[:user_id] = 2
1712 1714 # unassign issues
1713 1715 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1714 1716 assert_response 302
1715 1717 # check that the issues were updated
1716 1718 assert_nil Issue.find(2).assigned_to
1717 1719 end
1718 1720
1719 1721 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1720 1722 @request.session[:user_id] = 2
1721 1723
1722 1724 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1723 1725
1724 1726 assert_response :redirect
1725 1727 issues = Issue.find([1,2])
1726 1728 issues.each do |issue|
1727 1729 assert_equal 4, issue.fixed_version_id
1728 1730 assert_not_equal issue.project_id, issue.fixed_version.project_id
1729 1731 end
1730 1732 end
1731 1733
1732 1734 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1733 1735 @request.session[:user_id] = 2
1734 1736 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1735 1737
1736 1738 assert_response :redirect
1737 1739 assert_redirected_to '/issues'
1738 1740 end
1739 1741
1740 1742 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1741 1743 @request.session[:user_id] = 2
1742 1744 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1743 1745
1744 1746 assert_response :redirect
1745 1747 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1746 1748 end
1747 1749
1748 1750 def test_destroy_issue_with_no_time_entries
1749 1751 assert_nil TimeEntry.find_by_issue_id(2)
1750 1752 @request.session[:user_id] = 2
1751 1753 post :destroy, :id => 2
1752 1754 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1753 1755 assert_nil Issue.find_by_id(2)
1754 1756 end
1755 1757
1756 1758 def test_destroy_issues_with_time_entries
1757 1759 @request.session[:user_id] = 2
1758 1760 post :destroy, :ids => [1, 3]
1759 1761 assert_response :success
1760 1762 assert_template 'destroy'
1761 1763 assert_not_nil assigns(:hours)
1762 1764 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1763 1765 end
1764 1766
1765 1767 def test_destroy_issues_and_destroy_time_entries
1766 1768 @request.session[:user_id] = 2
1767 1769 post :destroy, :ids => [1, 3], :todo => 'destroy'
1768 1770 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1769 1771 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1770 1772 assert_nil TimeEntry.find_by_id([1, 2])
1771 1773 end
1772 1774
1773 1775 def test_destroy_issues_and_assign_time_entries_to_project
1774 1776 @request.session[:user_id] = 2
1775 1777 post :destroy, :ids => [1, 3], :todo => 'nullify'
1776 1778 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1777 1779 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1778 1780 assert_nil TimeEntry.find(1).issue_id
1779 1781 assert_nil TimeEntry.find(2).issue_id
1780 1782 end
1781 1783
1782 1784 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1783 1785 @request.session[:user_id] = 2
1784 1786 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1785 1787 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1786 1788 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1787 1789 assert_equal 2, TimeEntry.find(1).issue_id
1788 1790 assert_equal 2, TimeEntry.find(2).issue_id
1789 1791 end
1790 1792
1791 1793 def test_destroy_issues_from_different_projects
1792 1794 @request.session[:user_id] = 2
1793 1795 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1794 1796 assert_redirected_to :controller => 'issues', :action => 'index'
1795 1797 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1796 1798 end
1797 1799
1798 1800 def test_destroy_parent_and_child_issues
1799 1801 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
1800 1802 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
1801 1803 assert child.is_descendant_of?(parent.reload)
1802 1804
1803 1805 @request.session[:user_id] = 2
1804 1806 assert_difference 'Issue.count', -2 do
1805 1807 post :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
1806 1808 end
1807 1809 assert_response 302
1808 1810 end
1809 1811
1810 1812 def test_default_search_scope
1811 1813 get :index
1812 1814 assert_tag :div, :attributes => {:id => 'quick-search'},
1813 1815 :child => {:tag => 'form',
1814 1816 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1815 1817 end
1816 1818 end
General Comments 0
You need to be logged in to leave comments. Login now