##// END OF EJS Templates
Add view_issues permission (#3187)....
Jean-Philippe Lang -
r2925:dfd02040521b
parent child
Show More
@@ -0,0 +1,13
1 class AddViewIssuesPermission < ActiveRecord::Migration
2 def self.up
3 Role.find(:all).each do |r|
4 r.add_permission!(:view_issues)
5 end
6 end
7
8 def self.down
9 Role.find(:all).each do |r|
10 r.remove_permission!(:view_issues)
11 end
12 end
13 end
@@ -1,116 +1,116
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class SearchController < ApplicationController
18 class SearchController < ApplicationController
19 before_filter :find_optional_project
19 before_filter :find_optional_project
20
20
21 helper :messages
21 helper :messages
22 include MessagesHelper
22 include MessagesHelper
23
23
24 def index
24 def index
25 @question = params[:q] || ""
25 @question = params[:q] || ""
26 @question.strip!
26 @question.strip!
27 @all_words = params[:all_words] || (params[:submit] ? false : true)
27 @all_words = params[:all_words] || (params[:submit] ? false : true)
28 @titles_only = !params[:titles_only].nil?
28 @titles_only = !params[:titles_only].nil?
29
29
30 projects_to_search =
30 projects_to_search =
31 case params[:scope]
31 case params[:scope]
32 when 'all'
32 when 'all'
33 nil
33 nil
34 when 'my_projects'
34 when 'my_projects'
35 User.current.memberships.collect(&:project)
35 User.current.memberships.collect(&:project)
36 when 'subprojects'
36 when 'subprojects'
37 @project ? (@project.self_and_descendants.active) : nil
37 @project ? (@project.self_and_descendants.active) : nil
38 else
38 else
39 @project
39 @project
40 end
40 end
41
41
42 offset = nil
42 offset = nil
43 begin; offset = params[:offset].to_time if params[:offset]; rescue; end
43 begin; offset = params[:offset].to_time if params[:offset]; rescue; end
44
44
45 # quick jump to an issue
45 # quick jump to an issue
46 if @question.match(/^#?(\d+)$/) && Issue.find_by_id($1, :include => :project, :conditions => Project.visible_by(User.current))
46 if @question.match(/^#?(\d+)$/) && Issue.visible.find_by_id($1)
47 redirect_to :controller => "issues", :action => "show", :id => $1
47 redirect_to :controller => "issues", :action => "show", :id => $1
48 return
48 return
49 end
49 end
50
50
51 @object_types = %w(issues news documents changesets wiki_pages messages projects)
51 @object_types = %w(issues news documents changesets wiki_pages messages projects)
52 if projects_to_search.is_a? Project
52 if projects_to_search.is_a? Project
53 # don't search projects
53 # don't search projects
54 @object_types.delete('projects')
54 @object_types.delete('projects')
55 # only show what the user is allowed to view
55 # only show what the user is allowed to view
56 @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)}
56 @object_types = @object_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, projects_to_search)}
57 end
57 end
58
58
59 @scope = @object_types.select {|t| params[t]}
59 @scope = @object_types.select {|t| params[t]}
60 @scope = @object_types if @scope.empty?
60 @scope = @object_types if @scope.empty?
61
61
62 # extract tokens from the question
62 # extract tokens from the question
63 # eg. hello "bye bye" => ["hello", "bye bye"]
63 # eg. hello "bye bye" => ["hello", "bye bye"]
64 @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
64 @tokens = @question.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
65 # tokens must be at least 3 character long
65 # tokens must be at least 3 character long
66 @tokens = @tokens.uniq.select {|w| w.length > 2 }
66 @tokens = @tokens.uniq.select {|w| w.length > 2 }
67
67
68 if !@tokens.empty?
68 if !@tokens.empty?
69 # no more than 5 tokens to search for
69 # no more than 5 tokens to search for
70 @tokens.slice! 5..-1 if @tokens.size > 5
70 @tokens.slice! 5..-1 if @tokens.size > 5
71 # strings used in sql like statement
71 # strings used in sql like statement
72 like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
72 like_tokens = @tokens.collect {|w| "%#{w.downcase}%"}
73
73
74 @results = []
74 @results = []
75 @results_by_type = Hash.new {|h,k| h[k] = 0}
75 @results_by_type = Hash.new {|h,k| h[k] = 0}
76
76
77 limit = 10
77 limit = 10
78 @scope.each do |s|
78 @scope.each do |s|
79 r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search,
79 r, c = s.singularize.camelcase.constantize.search(like_tokens, projects_to_search,
80 :all_words => @all_words,
80 :all_words => @all_words,
81 :titles_only => @titles_only,
81 :titles_only => @titles_only,
82 :limit => (limit+1),
82 :limit => (limit+1),
83 :offset => offset,
83 :offset => offset,
84 :before => params[:previous].nil?)
84 :before => params[:previous].nil?)
85 @results += r
85 @results += r
86 @results_by_type[s] += c
86 @results_by_type[s] += c
87 end
87 end
88 @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
88 @results = @results.sort {|a,b| b.event_datetime <=> a.event_datetime}
89 if params[:previous].nil?
89 if params[:previous].nil?
90 @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
90 @pagination_previous_date = @results[0].event_datetime if offset && @results[0]
91 if @results.size > limit
91 if @results.size > limit
92 @pagination_next_date = @results[limit-1].event_datetime
92 @pagination_next_date = @results[limit-1].event_datetime
93 @results = @results[0, limit]
93 @results = @results[0, limit]
94 end
94 end
95 else
95 else
96 @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
96 @pagination_next_date = @results[-1].event_datetime if offset && @results[-1]
97 if @results.size > limit
97 if @results.size > limit
98 @pagination_previous_date = @results[-(limit)].event_datetime
98 @pagination_previous_date = @results[-(limit)].event_datetime
99 @results = @results[-(limit), limit]
99 @results = @results[-(limit), limit]
100 end
100 end
101 end
101 end
102 else
102 else
103 @question = ""
103 @question = ""
104 end
104 end
105 render :layout => false if request.xhr?
105 render :layout => false if request.xhr?
106 end
106 end
107
107
108 private
108 private
109 def find_optional_project
109 def find_optional_project
110 return true unless params[:id]
110 return true unless params[:id]
111 @project = Project.find(params[:id])
111 @project = Project.find(params[:id])
112 check_project_privacy
112 check_project_privacy
113 rescue ActiveRecord::RecordNotFound
113 rescue ActiveRecord::RecordNotFound
114 render_404
114 render_404
115 end
115 end
116 end
116 end
@@ -1,680 +1,680
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'coderay'
18 require 'coderay'
19 require 'coderay/helpers/file_type'
19 require 'coderay/helpers/file_type'
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
37 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
38 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
38 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
39 end
39 end
40
40
41 # Display a link to remote if user is authorized
41 # Display a link to remote if user is authorized
42 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
42 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
43 url = options[:url] || {}
43 url = options[:url] || {}
44 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
44 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
45 end
45 end
46
46
47 # Displays a link to user's account page if active
47 # Displays a link to user's account page if active
48 def link_to_user(user, options={})
48 def link_to_user(user, options={})
49 if user.is_a?(User)
49 if user.is_a?(User)
50 name = h(user.name(options[:format]))
50 name = h(user.name(options[:format]))
51 if user.active?
51 if user.active?
52 link_to name, :controller => 'users', :action => 'show', :id => user
52 link_to name, :controller => 'users', :action => 'show', :id => user
53 else
53 else
54 name
54 name
55 end
55 end
56 else
56 else
57 h(user.to_s)
57 h(user.to_s)
58 end
58 end
59 end
59 end
60
60
61 def link_to_issue(issue, options={})
61 def link_to_issue(issue, options={})
62 options[:class] ||= issue.css_classes
62 options[:class] ||= issue.css_classes
63 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
63 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
64 end
64 end
65
65
66 # Generates a link to an attachment.
66 # Generates a link to an attachment.
67 # Options:
67 # Options:
68 # * :text - Link text (default to attachment filename)
68 # * :text - Link text (default to attachment filename)
69 # * :download - Force download (default: false)
69 # * :download - Force download (default: false)
70 def link_to_attachment(attachment, options={})
70 def link_to_attachment(attachment, options={})
71 text = options.delete(:text) || attachment.filename
71 text = options.delete(:text) || attachment.filename
72 action = options.delete(:download) ? 'download' : 'show'
72 action = options.delete(:download) ? 'download' : 'show'
73
73
74 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
74 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
75 end
75 end
76
76
77 def toggle_link(name, id, options={})
77 def toggle_link(name, id, options={})
78 onclick = "Element.toggle('#{id}'); "
78 onclick = "Element.toggle('#{id}'); "
79 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
79 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
80 onclick << "return false;"
80 onclick << "return false;"
81 link_to(name, "#", :onclick => onclick)
81 link_to(name, "#", :onclick => onclick)
82 end
82 end
83
83
84 def image_to_function(name, function, html_options = {})
84 def image_to_function(name, function, html_options = {})
85 html_options.symbolize_keys!
85 html_options.symbolize_keys!
86 tag(:input, html_options.merge({
86 tag(:input, html_options.merge({
87 :type => "image", :src => image_path(name),
87 :type => "image", :src => image_path(name),
88 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
88 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
89 }))
89 }))
90 end
90 end
91
91
92 def prompt_to_remote(name, text, param, url, html_options = {})
92 def prompt_to_remote(name, text, param, url, html_options = {})
93 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
93 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
94 link_to name, {}, html_options
94 link_to name, {}, html_options
95 end
95 end
96
96
97 def format_activity_title(text)
97 def format_activity_title(text)
98 h(truncate_single_line(text, :length => 100))
98 h(truncate_single_line(text, :length => 100))
99 end
99 end
100
100
101 def format_activity_day(date)
101 def format_activity_day(date)
102 date == Date.today ? l(:label_today).titleize : format_date(date)
102 date == Date.today ? l(:label_today).titleize : format_date(date)
103 end
103 end
104
104
105 def format_activity_description(text)
105 def format_activity_description(text)
106 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
106 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
107 end
107 end
108
108
109 def due_date_distance_in_words(date)
109 def due_date_distance_in_words(date)
110 if date
110 if date
111 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
111 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
112 end
112 end
113 end
113 end
114
114
115 def render_page_hierarchy(pages, node=nil)
115 def render_page_hierarchy(pages, node=nil)
116 content = ''
116 content = ''
117 if pages[node]
117 if pages[node]
118 content << "<ul class=\"pages-hierarchy\">\n"
118 content << "<ul class=\"pages-hierarchy\">\n"
119 pages[node].each do |page|
119 pages[node].each do |page|
120 content << "<li>"
120 content << "<li>"
121 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
121 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
122 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
122 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
123 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
123 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
124 content << "</li>\n"
124 content << "</li>\n"
125 end
125 end
126 content << "</ul>\n"
126 content << "</ul>\n"
127 end
127 end
128 content
128 content
129 end
129 end
130
130
131 # Renders flash messages
131 # Renders flash messages
132 def render_flash_messages
132 def render_flash_messages
133 s = ''
133 s = ''
134 flash.each do |k,v|
134 flash.each do |k,v|
135 s << content_tag('div', v, :class => "flash #{k}")
135 s << content_tag('div', v, :class => "flash #{k}")
136 end
136 end
137 s
137 s
138 end
138 end
139
139
140 # Renders tabs and their content
140 # Renders tabs and their content
141 def render_tabs(tabs)
141 def render_tabs(tabs)
142 if tabs.any?
142 if tabs.any?
143 render :partial => 'common/tabs', :locals => {:tabs => tabs}
143 render :partial => 'common/tabs', :locals => {:tabs => tabs}
144 else
144 else
145 content_tag 'p', l(:label_no_data), :class => "nodata"
145 content_tag 'p', l(:label_no_data), :class => "nodata"
146 end
146 end
147 end
147 end
148
148
149 # Renders the project quick-jump box
149 # Renders the project quick-jump box
150 def render_project_jump_box
150 def render_project_jump_box
151 # Retrieve them now to avoid a COUNT query
151 # Retrieve them now to avoid a COUNT query
152 projects = User.current.projects.all
152 projects = User.current.projects.all
153 if projects.any?
153 if projects.any?
154 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
154 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
155 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
155 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
156 '<option value="" disabled="disabled">---</option>'
156 '<option value="" disabled="disabled">---</option>'
157 s << project_tree_options_for_select(projects, :selected => @project) do |p|
157 s << project_tree_options_for_select(projects, :selected => @project) do |p|
158 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
158 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
159 end
159 end
160 s << '</select>'
160 s << '</select>'
161 s
161 s
162 end
162 end
163 end
163 end
164
164
165 def project_tree_options_for_select(projects, options = {})
165 def project_tree_options_for_select(projects, options = {})
166 s = ''
166 s = ''
167 project_tree(projects) do |project, level|
167 project_tree(projects) do |project, level|
168 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
168 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
169 tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
169 tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
170 tag_options.merge!(yield(project)) if block_given?
170 tag_options.merge!(yield(project)) if block_given?
171 s << content_tag('option', name_prefix + h(project), tag_options)
171 s << content_tag('option', name_prefix + h(project), tag_options)
172 end
172 end
173 s
173 s
174 end
174 end
175
175
176 # Yields the given block for each project with its level in the tree
176 # Yields the given block for each project with its level in the tree
177 def project_tree(projects, &block)
177 def project_tree(projects, &block)
178 ancestors = []
178 ancestors = []
179 projects.sort_by(&:lft).each do |project|
179 projects.sort_by(&:lft).each do |project|
180 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
180 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
181 ancestors.pop
181 ancestors.pop
182 end
182 end
183 yield project, ancestors.size
183 yield project, ancestors.size
184 ancestors << project
184 ancestors << project
185 end
185 end
186 end
186 end
187
187
188 def project_nested_ul(projects, &block)
188 def project_nested_ul(projects, &block)
189 s = ''
189 s = ''
190 if projects.any?
190 if projects.any?
191 ancestors = []
191 ancestors = []
192 projects.sort_by(&:lft).each do |project|
192 projects.sort_by(&:lft).each do |project|
193 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
193 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
194 s << "<ul>\n"
194 s << "<ul>\n"
195 else
195 else
196 ancestors.pop
196 ancestors.pop
197 s << "</li>"
197 s << "</li>"
198 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
198 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
199 ancestors.pop
199 ancestors.pop
200 s << "</ul></li>\n"
200 s << "</ul></li>\n"
201 end
201 end
202 end
202 end
203 s << "<li>"
203 s << "<li>"
204 s << yield(project).to_s
204 s << yield(project).to_s
205 ancestors << project
205 ancestors << project
206 end
206 end
207 s << ("</li></ul>\n" * ancestors.size)
207 s << ("</li></ul>\n" * ancestors.size)
208 end
208 end
209 s
209 s
210 end
210 end
211
211
212 def principals_check_box_tags(name, principals)
212 def principals_check_box_tags(name, principals)
213 s = ''
213 s = ''
214 principals.sort.each do |principal|
214 principals.sort.each do |principal|
215 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
215 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
216 end
216 end
217 s
217 s
218 end
218 end
219
219
220 # Truncates and returns the string as a single line
220 # Truncates and returns the string as a single line
221 def truncate_single_line(string, *args)
221 def truncate_single_line(string, *args)
222 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
222 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
223 end
223 end
224
224
225 def html_hours(text)
225 def html_hours(text)
226 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
226 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
227 end
227 end
228
228
229 def authoring(created, author, options={})
229 def authoring(created, author, options={})
230 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
230 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
231 end
231 end
232
232
233 def time_tag(time)
233 def time_tag(time)
234 text = distance_of_time_in_words(Time.now, time)
234 text = distance_of_time_in_words(Time.now, time)
235 if @project
235 if @project
236 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
236 link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
237 else
237 else
238 content_tag('acronym', text, :title => format_time(time))
238 content_tag('acronym', text, :title => format_time(time))
239 end
239 end
240 end
240 end
241
241
242 def syntax_highlight(name, content)
242 def syntax_highlight(name, content)
243 type = CodeRay::FileType[name]
243 type = CodeRay::FileType[name]
244 type ? CodeRay.scan(content, type).html : h(content)
244 type ? CodeRay.scan(content, type).html : h(content)
245 end
245 end
246
246
247 def to_path_param(path)
247 def to_path_param(path)
248 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
248 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
249 end
249 end
250
250
251 def pagination_links_full(paginator, count=nil, options={})
251 def pagination_links_full(paginator, count=nil, options={})
252 page_param = options.delete(:page_param) || :page
252 page_param = options.delete(:page_param) || :page
253 url_param = params.dup
253 url_param = params.dup
254 # don't reuse query params if filters are present
254 # don't reuse query params if filters are present
255 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
255 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
256
256
257 html = ''
257 html = ''
258 if paginator.current.previous
258 if paginator.current.previous
259 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
259 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
260 end
260 end
261
261
262 html << (pagination_links_each(paginator, options) do |n|
262 html << (pagination_links_each(paginator, options) do |n|
263 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
263 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
264 end || '')
264 end || '')
265
265
266 if paginator.current.next
266 if paginator.current.next
267 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
267 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
268 end
268 end
269
269
270 unless count.nil?
270 unless count.nil?
271 html << [
271 html << [
272 " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
272 " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
273 per_page_links(paginator.items_per_page)
273 per_page_links(paginator.items_per_page)
274 ].compact.join(' | ')
274 ].compact.join(' | ')
275 end
275 end
276
276
277 html
277 html
278 end
278 end
279
279
280 def per_page_links(selected=nil)
280 def per_page_links(selected=nil)
281 url_param = params.dup
281 url_param = params.dup
282 url_param.clear if url_param.has_key?(:set_filter)
282 url_param.clear if url_param.has_key?(:set_filter)
283
283
284 links = Setting.per_page_options_array.collect do |n|
284 links = Setting.per_page_options_array.collect do |n|
285 n == selected ? n : link_to_remote(n, {:update => "content",
285 n == selected ? n : link_to_remote(n, {:update => "content",
286 :url => params.dup.merge(:per_page => n),
286 :url => params.dup.merge(:per_page => n),
287 :method => :get},
287 :method => :get},
288 {:href => url_for(url_param.merge(:per_page => n))})
288 {:href => url_for(url_param.merge(:per_page => n))})
289 end
289 end
290 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
290 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
291 end
291 end
292
292
293 def reorder_links(name, url)
293 def reorder_links(name, url)
294 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
294 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
295 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
295 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
296 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
296 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
297 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
297 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
298 end
298 end
299
299
300 def breadcrumb(*args)
300 def breadcrumb(*args)
301 elements = args.flatten
301 elements = args.flatten
302 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
302 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
303 end
303 end
304
304
305 def other_formats_links(&block)
305 def other_formats_links(&block)
306 concat('<p class="other-formats">' + l(:label_export_to))
306 concat('<p class="other-formats">' + l(:label_export_to))
307 yield Redmine::Views::OtherFormatsBuilder.new(self)
307 yield Redmine::Views::OtherFormatsBuilder.new(self)
308 concat('</p>')
308 concat('</p>')
309 end
309 end
310
310
311 def page_header_title
311 def page_header_title
312 if @project.nil? || @project.new_record?
312 if @project.nil? || @project.new_record?
313 h(Setting.app_title)
313 h(Setting.app_title)
314 else
314 else
315 b = []
315 b = []
316 ancestors = (@project.root? ? [] : @project.ancestors.visible)
316 ancestors = (@project.root? ? [] : @project.ancestors.visible)
317 if ancestors.any?
317 if ancestors.any?
318 root = ancestors.shift
318 root = ancestors.shift
319 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
319 b << link_to(h(root), {:controller => 'projects', :action => 'show', :id => root, :jump => current_menu_item}, :class => 'root')
320 if ancestors.size > 2
320 if ancestors.size > 2
321 b << '&#8230;'
321 b << '&#8230;'
322 ancestors = ancestors[-2, 2]
322 ancestors = ancestors[-2, 2]
323 end
323 end
324 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
324 b += ancestors.collect {|p| link_to(h(p), {:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item}, :class => 'ancestor') }
325 end
325 end
326 b << h(@project)
326 b << h(@project)
327 b.join(' &#187; ')
327 b.join(' &#187; ')
328 end
328 end
329 end
329 end
330
330
331 def html_title(*args)
331 def html_title(*args)
332 if args.empty?
332 if args.empty?
333 title = []
333 title = []
334 title << @project.name if @project
334 title << @project.name if @project
335 title += @html_title if @html_title
335 title += @html_title if @html_title
336 title << Setting.app_title
336 title << Setting.app_title
337 title.select {|t| !t.blank? }.join(' - ')
337 title.select {|t| !t.blank? }.join(' - ')
338 else
338 else
339 @html_title ||= []
339 @html_title ||= []
340 @html_title += args
340 @html_title += args
341 end
341 end
342 end
342 end
343
343
344 def accesskey(s)
344 def accesskey(s)
345 Redmine::AccessKeys.key_for s
345 Redmine::AccessKeys.key_for s
346 end
346 end
347
347
348 # Formats text according to system settings.
348 # Formats text according to system settings.
349 # 2 ways to call this method:
349 # 2 ways to call this method:
350 # * with a String: textilizable(text, options)
350 # * with a String: textilizable(text, options)
351 # * with an object and one of its attribute: textilizable(issue, :description, options)
351 # * with an object and one of its attribute: textilizable(issue, :description, options)
352 def textilizable(*args)
352 def textilizable(*args)
353 options = args.last.is_a?(Hash) ? args.pop : {}
353 options = args.last.is_a?(Hash) ? args.pop : {}
354 case args.size
354 case args.size
355 when 1
355 when 1
356 obj = options[:object]
356 obj = options[:object]
357 text = args.shift
357 text = args.shift
358 when 2
358 when 2
359 obj = args.shift
359 obj = args.shift
360 text = obj.send(args.shift).to_s
360 text = obj.send(args.shift).to_s
361 else
361 else
362 raise ArgumentError, 'invalid arguments to textilizable'
362 raise ArgumentError, 'invalid arguments to textilizable'
363 end
363 end
364 return '' if text.blank?
364 return '' if text.blank?
365
365
366 only_path = options.delete(:only_path) == false ? false : true
366 only_path = options.delete(:only_path) == false ? false : true
367
367
368 # when using an image link, try to use an attachment, if possible
368 # when using an image link, try to use an attachment, if possible
369 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
369 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
370
370
371 if attachments
371 if attachments
372 attachments = attachments.sort_by(&:created_on).reverse
372 attachments = attachments.sort_by(&:created_on).reverse
373 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
373 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
374 style = $1
374 style = $1
375 filename = $6.downcase
375 filename = $6.downcase
376 # search for the picture in attachments
376 # search for the picture in attachments
377 if found = attachments.detect { |att| att.filename.downcase == filename }
377 if found = attachments.detect { |att| att.filename.downcase == filename }
378 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
378 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
379 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
379 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
380 alt = desc.blank? ? nil : "(#{desc})"
380 alt = desc.blank? ? nil : "(#{desc})"
381 "!#{style}#{image_url}#{alt}!"
381 "!#{style}#{image_url}#{alt}!"
382 else
382 else
383 m
383 m
384 end
384 end
385 end
385 end
386 end
386 end
387
387
388 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
388 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
389
389
390 # different methods for formatting wiki links
390 # different methods for formatting wiki links
391 case options[:wiki_links]
391 case options[:wiki_links]
392 when :local
392 when :local
393 # used for local links to html files
393 # used for local links to html files
394 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
394 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
395 when :anchor
395 when :anchor
396 # used for single-file wiki export
396 # used for single-file wiki export
397 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
397 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
398 else
398 else
399 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
399 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
400 end
400 end
401
401
402 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
402 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
403
403
404 # Wiki links
404 # Wiki links
405 #
405 #
406 # Examples:
406 # Examples:
407 # [[mypage]]
407 # [[mypage]]
408 # [[mypage|mytext]]
408 # [[mypage|mytext]]
409 # wiki links can refer other project wikis, using project name or identifier:
409 # wiki links can refer other project wikis, using project name or identifier:
410 # [[project:]] -> wiki starting page
410 # [[project:]] -> wiki starting page
411 # [[project:|mytext]]
411 # [[project:|mytext]]
412 # [[project:mypage]]
412 # [[project:mypage]]
413 # [[project:mypage|mytext]]
413 # [[project:mypage|mytext]]
414 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
414 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
415 link_project = project
415 link_project = project
416 esc, all, page, title = $1, $2, $3, $5
416 esc, all, page, title = $1, $2, $3, $5
417 if esc.nil?
417 if esc.nil?
418 if page =~ /^([^\:]+)\:(.*)$/
418 if page =~ /^([^\:]+)\:(.*)$/
419 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
419 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
420 page = $2
420 page = $2
421 title ||= $1 if page.blank?
421 title ||= $1 if page.blank?
422 end
422 end
423
423
424 if link_project && link_project.wiki
424 if link_project && link_project.wiki
425 # extract anchor
425 # extract anchor
426 anchor = nil
426 anchor = nil
427 if page =~ /^(.+?)\#(.+)$/
427 if page =~ /^(.+?)\#(.+)$/
428 page, anchor = $1, $2
428 page, anchor = $1, $2
429 end
429 end
430 # check if page exists
430 # check if page exists
431 wiki_page = link_project.wiki.find_page(page)
431 wiki_page = link_project.wiki.find_page(page)
432 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
432 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
433 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
433 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
434 else
434 else
435 # project or wiki doesn't exist
435 # project or wiki doesn't exist
436 all
436 all
437 end
437 end
438 else
438 else
439 all
439 all
440 end
440 end
441 end
441 end
442
442
443 # Redmine links
443 # Redmine links
444 #
444 #
445 # Examples:
445 # Examples:
446 # Issues:
446 # Issues:
447 # #52 -> Link to issue #52
447 # #52 -> Link to issue #52
448 # Changesets:
448 # Changesets:
449 # r52 -> Link to revision 52
449 # r52 -> Link to revision 52
450 # commit:a85130f -> Link to scmid starting with a85130f
450 # commit:a85130f -> Link to scmid starting with a85130f
451 # Documents:
451 # Documents:
452 # document#17 -> Link to document with id 17
452 # document#17 -> Link to document with id 17
453 # document:Greetings -> Link to the document with title "Greetings"
453 # document:Greetings -> Link to the document with title "Greetings"
454 # document:"Some document" -> Link to the document with title "Some document"
454 # document:"Some document" -> Link to the document with title "Some document"
455 # Versions:
455 # Versions:
456 # version#3 -> Link to version with id 3
456 # version#3 -> Link to version with id 3
457 # version:1.0.0 -> Link to version named "1.0.0"
457 # version:1.0.0 -> Link to version named "1.0.0"
458 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
458 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
459 # Attachments:
459 # Attachments:
460 # attachment:file.zip -> Link to the attachment of the current object named file.zip
460 # attachment:file.zip -> Link to the attachment of the current object named file.zip
461 # Source files:
461 # Source files:
462 # source:some/file -> Link to the file located at /some/file in the project's repository
462 # source:some/file -> Link to the file located at /some/file in the project's repository
463 # source:some/file@52 -> Link to the file's revision 52
463 # source:some/file@52 -> Link to the file's revision 52
464 # source:some/file#L120 -> Link to line 120 of the file
464 # source:some/file#L120 -> Link to line 120 of the file
465 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
465 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
466 # export:some/file -> Force the download of the file
466 # export:some/file -> Force the download of the file
467 # Forum messages:
467 # Forum messages:
468 # message#1218 -> Link to message with id 1218
468 # message#1218 -> Link to message with id 1218
469 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
469 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|<|$)}) do |m|
470 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
470 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
471 link = nil
471 link = nil
472 if esc.nil?
472 if esc.nil?
473 if prefix.nil? && sep == 'r'
473 if prefix.nil? && sep == 'r'
474 if project && (changeset = project.changesets.find_by_revision(oid))
474 if project && (changeset = project.changesets.find_by_revision(oid))
475 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
475 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
476 :class => 'changeset',
476 :class => 'changeset',
477 :title => truncate_single_line(changeset.comments, :length => 100))
477 :title => truncate_single_line(changeset.comments, :length => 100))
478 end
478 end
479 elsif sep == '#'
479 elsif sep == '#'
480 oid = oid.to_i
480 oid = oid.to_i
481 case prefix
481 case prefix
482 when nil
482 when nil
483 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
483 if issue = Issue.visible.find_by_id(oid, :include => :status)
484 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
484 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
485 :class => (issue.closed? ? 'issue closed' : 'issue'),
485 :class => (issue.closed? ? 'issue closed' : 'issue'),
486 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
486 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
487 link = content_tag('del', link) if issue.closed?
487 link = content_tag('del', link) if issue.closed?
488 end
488 end
489 when 'document'
489 when 'document'
490 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
490 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
491 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
491 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
492 :class => 'document'
492 :class => 'document'
493 end
493 end
494 when 'version'
494 when 'version'
495 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
495 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
496 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
496 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
497 :class => 'version'
497 :class => 'version'
498 end
498 end
499 when 'message'
499 when 'message'
500 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
500 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
501 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
501 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
502 :controller => 'messages',
502 :controller => 'messages',
503 :action => 'show',
503 :action => 'show',
504 :board_id => message.board,
504 :board_id => message.board,
505 :id => message.root,
505 :id => message.root,
506 :anchor => (message.parent ? "message-#{message.id}" : nil)},
506 :anchor => (message.parent ? "message-#{message.id}" : nil)},
507 :class => 'message'
507 :class => 'message'
508 end
508 end
509 end
509 end
510 elsif sep == ':'
510 elsif sep == ':'
511 # removes the double quotes if any
511 # removes the double quotes if any
512 name = oid.gsub(%r{^"(.*)"$}, "\\1")
512 name = oid.gsub(%r{^"(.*)"$}, "\\1")
513 case prefix
513 case prefix
514 when 'document'
514 when 'document'
515 if project && document = project.documents.find_by_title(name)
515 if project && document = project.documents.find_by_title(name)
516 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
516 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
517 :class => 'document'
517 :class => 'document'
518 end
518 end
519 when 'version'
519 when 'version'
520 if project && version = project.versions.find_by_name(name)
520 if project && version = project.versions.find_by_name(name)
521 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
521 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
522 :class => 'version'
522 :class => 'version'
523 end
523 end
524 when 'commit'
524 when 'commit'
525 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
525 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
526 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
526 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
527 :class => 'changeset',
527 :class => 'changeset',
528 :title => truncate_single_line(changeset.comments, :length => 100)
528 :title => truncate_single_line(changeset.comments, :length => 100)
529 end
529 end
530 when 'source', 'export'
530 when 'source', 'export'
531 if project && project.repository
531 if project && project.repository
532 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
532 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
533 path, rev, anchor = $1, $3, $5
533 path, rev, anchor = $1, $3, $5
534 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
534 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
535 :path => to_path_param(path),
535 :path => to_path_param(path),
536 :rev => rev,
536 :rev => rev,
537 :anchor => anchor,
537 :anchor => anchor,
538 :format => (prefix == 'export' ? 'raw' : nil)},
538 :format => (prefix == 'export' ? 'raw' : nil)},
539 :class => (prefix == 'export' ? 'source download' : 'source')
539 :class => (prefix == 'export' ? 'source download' : 'source')
540 end
540 end
541 when 'attachment'
541 when 'attachment'
542 if attachments && attachment = attachments.detect {|a| a.filename == name }
542 if attachments && attachment = attachments.detect {|a| a.filename == name }
543 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
543 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
544 :class => 'attachment'
544 :class => 'attachment'
545 end
545 end
546 end
546 end
547 end
547 end
548 end
548 end
549 leading + (link || "#{prefix}#{sep}#{oid}")
549 leading + (link || "#{prefix}#{sep}#{oid}")
550 end
550 end
551
551
552 text
552 text
553 end
553 end
554
554
555 # Same as Rails' simple_format helper without using paragraphs
555 # Same as Rails' simple_format helper without using paragraphs
556 def simple_format_without_paragraph(text)
556 def simple_format_without_paragraph(text)
557 text.to_s.
557 text.to_s.
558 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
558 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
559 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
559 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
560 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
560 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
561 end
561 end
562
562
563 def lang_options_for_select(blank=true)
563 def lang_options_for_select(blank=true)
564 (blank ? [["(auto)", ""]] : []) +
564 (blank ? [["(auto)", ""]] : []) +
565 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
565 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
566 end
566 end
567
567
568 def label_tag_for(name, option_tags = nil, options = {})
568 def label_tag_for(name, option_tags = nil, options = {})
569 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
569 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
570 content_tag("label", label_text)
570 content_tag("label", label_text)
571 end
571 end
572
572
573 def labelled_tabular_form_for(name, object, options, &proc)
573 def labelled_tabular_form_for(name, object, options, &proc)
574 options[:html] ||= {}
574 options[:html] ||= {}
575 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
575 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
576 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
576 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
577 end
577 end
578
578
579 def back_url_hidden_field_tag
579 def back_url_hidden_field_tag
580 back_url = params[:back_url] || request.env['HTTP_REFERER']
580 back_url = params[:back_url] || request.env['HTTP_REFERER']
581 back_url = CGI.unescape(back_url.to_s)
581 back_url = CGI.unescape(back_url.to_s)
582 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
582 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
583 end
583 end
584
584
585 def check_all_links(form_name)
585 def check_all_links(form_name)
586 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
586 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
587 " | " +
587 " | " +
588 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
588 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
589 end
589 end
590
590
591 def progress_bar(pcts, options={})
591 def progress_bar(pcts, options={})
592 pcts = [pcts, pcts] unless pcts.is_a?(Array)
592 pcts = [pcts, pcts] unless pcts.is_a?(Array)
593 pcts[1] = pcts[1] - pcts[0]
593 pcts[1] = pcts[1] - pcts[0]
594 pcts << (100 - pcts[1] - pcts[0])
594 pcts << (100 - pcts[1] - pcts[0])
595 width = options[:width] || '100px;'
595 width = options[:width] || '100px;'
596 legend = options[:legend] || ''
596 legend = options[:legend] || ''
597 content_tag('table',
597 content_tag('table',
598 content_tag('tr',
598 content_tag('tr',
599 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
599 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
600 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
600 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
601 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
601 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
602 ), :class => 'progress', :style => "width: #{width};") +
602 ), :class => 'progress', :style => "width: #{width};") +
603 content_tag('p', legend, :class => 'pourcent')
603 content_tag('p', legend, :class => 'pourcent')
604 end
604 end
605
605
606 def context_menu_link(name, url, options={})
606 def context_menu_link(name, url, options={})
607 options[:class] ||= ''
607 options[:class] ||= ''
608 if options.delete(:selected)
608 if options.delete(:selected)
609 options[:class] << ' icon-checked disabled'
609 options[:class] << ' icon-checked disabled'
610 options[:disabled] = true
610 options[:disabled] = true
611 end
611 end
612 if options.delete(:disabled)
612 if options.delete(:disabled)
613 options.delete(:method)
613 options.delete(:method)
614 options.delete(:confirm)
614 options.delete(:confirm)
615 options.delete(:onclick)
615 options.delete(:onclick)
616 options[:class] << ' disabled'
616 options[:class] << ' disabled'
617 url = '#'
617 url = '#'
618 end
618 end
619 link_to name, url, options
619 link_to name, url, options
620 end
620 end
621
621
622 def calendar_for(field_id)
622 def calendar_for(field_id)
623 include_calendar_headers_tags
623 include_calendar_headers_tags
624 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
624 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
625 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
625 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
626 end
626 end
627
627
628 def include_calendar_headers_tags
628 def include_calendar_headers_tags
629 unless @calendar_headers_tags_included
629 unless @calendar_headers_tags_included
630 @calendar_headers_tags_included = true
630 @calendar_headers_tags_included = true
631 content_for :header_tags do
631 content_for :header_tags do
632 javascript_include_tag('calendar/calendar') +
632 javascript_include_tag('calendar/calendar') +
633 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
633 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
634 javascript_include_tag('calendar/calendar-setup') +
634 javascript_include_tag('calendar/calendar-setup') +
635 stylesheet_link_tag('calendar')
635 stylesheet_link_tag('calendar')
636 end
636 end
637 end
637 end
638 end
638 end
639
639
640 def content_for(name, content = nil, &block)
640 def content_for(name, content = nil, &block)
641 @has_content ||= {}
641 @has_content ||= {}
642 @has_content[name] = true
642 @has_content[name] = true
643 super(name, content, &block)
643 super(name, content, &block)
644 end
644 end
645
645
646 def has_content?(name)
646 def has_content?(name)
647 (@has_content && @has_content[name]) || false
647 (@has_content && @has_content[name]) || false
648 end
648 end
649
649
650 # Returns the avatar image tag for the given +user+ if avatars are enabled
650 # Returns the avatar image tag for the given +user+ if avatars are enabled
651 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
651 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
652 def avatar(user, options = { })
652 def avatar(user, options = { })
653 if Setting.gravatar_enabled?
653 if Setting.gravatar_enabled?
654 options.merge!({:ssl => Setting.protocol == 'https'})
654 options.merge!({:ssl => Setting.protocol == 'https'})
655 email = nil
655 email = nil
656 if user.respond_to?(:mail)
656 if user.respond_to?(:mail)
657 email = user.mail
657 email = user.mail
658 elsif user.to_s =~ %r{<(.+?)>}
658 elsif user.to_s =~ %r{<(.+?)>}
659 email = $1
659 email = $1
660 end
660 end
661 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
661 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
662 end
662 end
663 end
663 end
664
664
665 private
665 private
666
666
667 def wiki_helper
667 def wiki_helper
668 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
668 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
669 extend helper
669 extend helper
670 return self
670 return self
671 end
671 end
672
672
673 def link_to_remote_content_update(text, url_params)
673 def link_to_remote_content_update(text, url_params)
674 link_to_remote(text,
674 link_to_remote(text,
675 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
675 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
676 {:href => url_for(:params => url_params)}
676 {:href => url_for(:params => url_params)}
677 )
677 )
678 end
678 end
679
679
680 end
680 end
@@ -1,21 +1,21
1 <div class="contextual">
1 <div class="contextual">
2 <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %>
2 <%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %>
3 <%= link_to l(:label_issue_view_all), { :controller => 'issues' } %> |
3 <%= link_to(l(:label_issue_view_all), { :controller => 'issues' }) + ' |' if User.current.allowed_to?(:view_issues, nil, :global => true) %>
4 <%= link_to l(:label_overall_activity), { :controller => 'projects', :action => 'activity' }%>
4 <%= link_to l(:label_overall_activity), { :controller => 'projects', :action => 'activity' }%>
5 </div>
5 </div>
6
6
7 <h2><%=l(:label_project_plural)%></h2>
7 <h2><%=l(:label_project_plural)%></h2>
8
8
9 <%= render_project_hierarchy(@projects)%>
9 <%= render_project_hierarchy(@projects)%>
10
10
11 <% if User.current.logged? %>
11 <% if User.current.logged? %>
12 <p style="text-align:right;">
12 <p style="text-align:right;">
13 <span class="my-project"><%= l(:label_my_projects) %></span>
13 <span class="my-project"><%= l(:label_my_projects) %></span>
14 </p>
14 </p>
15 <% end %>
15 <% end %>
16
16
17 <% other_formats_links do |f| %>
17 <% other_formats_links do |f| %>
18 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
18 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
19 <% end %>
19 <% end %>
20
20
21 <% html_title(l(:label_project_plural)) -%>
21 <% html_title(l(:label_project_plural)) -%>
@@ -1,59 +1,59
1 <div class="contextual">
1 <div class="contextual">
2 &#171;
2 &#171;
3 <% unless @changeset.previous.nil? -%>
3 <% unless @changeset.previous.nil? -%>
4 <%= link_to l(:label_previous), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.previous.revision %>
4 <%= link_to l(:label_previous), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.previous.revision %>
5 <% else -%>
5 <% else -%>
6 <%= l(:label_previous) %>
6 <%= l(:label_previous) %>
7 <% end -%>
7 <% end -%>
8 |
8 |
9 <% unless @changeset.next.nil? -%>
9 <% unless @changeset.next.nil? -%>
10 <%= link_to l(:label_next), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.next.revision %>
10 <%= link_to l(:label_next), :controller => 'repositories', :action => 'revision', :id => @project, :rev => @changeset.next.revision %>
11 <% else -%>
11 <% else -%>
12 <%= l(:label_next) %>
12 <%= l(:label_next) %>
13 <% end -%>
13 <% end -%>
14 &#187;&nbsp;
14 &#187;&nbsp;
15
15
16 <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %>
16 <% form_tag({:controller => 'repositories', :action => 'revision', :id => @project, :rev => nil}, :method => :get) do %>
17 <%= text_field_tag 'rev', @rev[0,8], :size => 8 %>
17 <%= text_field_tag 'rev', @rev[0,8], :size => 8 %>
18 <%= submit_tag 'OK', :name => nil %>
18 <%= submit_tag 'OK', :name => nil %>
19 <% end %>
19 <% end %>
20 </div>
20 </div>
21
21
22 <h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
22 <h2><%= l(:label_revision) %> <%= format_revision(@changeset.revision) %></h2>
23
23
24 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
24 <p><% if @changeset.scmid %>ID: <%= @changeset.scmid %><br /><% end %>
25 <span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p>
25 <span class="author"><%= authoring(@changeset.committed_on, @changeset.author) %></span></p>
26
26
27 <%= textilizable @changeset.comments %>
27 <%= textilizable @changeset.comments %>
28
28
29 <% if @changeset.issues.any? %>
29 <% if @changeset.issues.visible.any? %>
30 <h3><%= l(:label_related_issues) %></h3>
30 <h3><%= l(:label_related_issues) %></h3>
31 <ul>
31 <ul>
32 <% @changeset.issues.each do |issue| %>
32 <% @changeset.issues.visible.each do |issue| %>
33 <li><%= link_to_issue issue %>: <%=h issue.subject %></li>
33 <li><%= link_to_issue issue %>: <%=h issue.subject %></li>
34 <% end %>
34 <% end %>
35 </ul>
35 </ul>
36 <% end %>
36 <% end %>
37
37
38 <% if User.current.allowed_to?(:browse_repository, @project) %>
38 <% if User.current.allowed_to?(:browse_repository, @project) %>
39 <h3><%= l(:label_attachment_plural) %></h3>
39 <h3><%= l(:label_attachment_plural) %></h3>
40 <ul id="changes-legend">
40 <ul id="changes-legend">
41 <li class="change change-A"><%= l(:label_added) %></li>
41 <li class="change change-A"><%= l(:label_added) %></li>
42 <li class="change change-M"><%= l(:label_modified) %></li>
42 <li class="change change-M"><%= l(:label_modified) %></li>
43 <li class="change change-C"><%= l(:label_copied) %></li>
43 <li class="change change-C"><%= l(:label_copied) %></li>
44 <li class="change change-R"><%= l(:label_renamed) %></li>
44 <li class="change change-R"><%= l(:label_renamed) %></li>
45 <li class="change change-D"><%= l(:label_deleted) %></li>
45 <li class="change change-D"><%= l(:label_deleted) %></li>
46 </ul>
46 </ul>
47
47
48 <p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
48 <p><%= link_to(l(:label_view_diff), :action => 'diff', :id => @project, :path => "", :rev => @changeset.revision) if @changeset.changes.any? %></p>
49
49
50 <div class="changeset-changes">
50 <div class="changeset-changes">
51 <%= render_changeset_changes %>
51 <%= render_changeset_changes %>
52 </div>
52 </div>
53 <% end %>
53 <% end %>
54
54
55 <% content_for :header_tags do %>
55 <% content_for :header_tags do %>
56 <%= stylesheet_link_tag "scm" %>
56 <%= stylesheet_link_tag "scm" %>
57 <% end %>
57 <% end %>
58
58
59 <% html_title("#{l(:label_revision)} #{@changeset.revision}") -%>
59 <% html_title("#{l(:label_revision)} #{@changeset.revision}") -%>
@@ -1,174 +1,174
1 require 'redmine/access_control'
1 require 'redmine/access_control'
2 require 'redmine/menu_manager'
2 require 'redmine/menu_manager'
3 require 'redmine/activity'
3 require 'redmine/activity'
4 require 'redmine/mime_type'
4 require 'redmine/mime_type'
5 require 'redmine/core_ext'
5 require 'redmine/core_ext'
6 require 'redmine/themes'
6 require 'redmine/themes'
7 require 'redmine/hook'
7 require 'redmine/hook'
8 require 'redmine/plugin'
8 require 'redmine/plugin'
9 require 'redmine/wiki_formatting'
9 require 'redmine/wiki_formatting'
10
10
11 begin
11 begin
12 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
12 require_library_or_gem 'RMagick' unless Object.const_defined?(:Magick)
13 rescue LoadError
13 rescue LoadError
14 # RMagick is not available
14 # RMagick is not available
15 end
15 end
16
16
17 if RUBY_VERSION < '1.9'
17 if RUBY_VERSION < '1.9'
18 require 'faster_csv'
18 require 'faster_csv'
19 else
19 else
20 require 'csv'
20 require 'csv'
21 FCSV = CSV
21 FCSV = CSV
22 end
22 end
23
23
24 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
24 REDMINE_SUPPORTED_SCM = %w( Subversion Darcs Mercurial Cvs Bazaar Git Filesystem )
25
25
26 # Permissions
26 # Permissions
27 Redmine::AccessControl.map do |map|
27 Redmine::AccessControl.map do |map|
28 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
28 map.permission :view_project, {:projects => [:show, :activity]}, :public => true
29 map.permission :search_project, {:search => :index}, :public => true
29 map.permission :search_project, {:search => :index}, :public => true
30 map.permission :add_project, {:projects => :add}, :require => :loggedin
30 map.permission :add_project, {:projects => :add}, :require => :loggedin
31 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
31 map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
32 map.permission :select_project_modules, {:projects => :modules}, :require => :member
32 map.permission :select_project_modules, {:projects => :modules}, :require => :member
33 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
33 map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
34 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
34 map.permission :manage_versions, {:projects => [:settings, :add_version], :versions => [:edit, :close_completed, :destroy]}, :require => :member
35
35
36 map.project_module :issue_tracking do |map|
36 map.project_module :issue_tracking do |map|
37 # Issue categories
37 # Issue categories
38 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
38 map.permission :manage_categories, {:projects => [:settings, :add_issue_category], :issue_categories => [:edit, :destroy]}, :require => :member
39 # Issues
39 # Issues
40 map.permission :view_issues, {:projects => [:changelog, :roadmap],
40 map.permission :view_issues, {:projects => [:changelog, :roadmap],
41 :issues => [:index, :changes, :show, :context_menu],
41 :issues => [:index, :changes, :show, :context_menu],
42 :versions => [:show, :status_by],
42 :versions => [:show, :status_by],
43 :queries => :index,
43 :queries => :index,
44 :reports => :issue_report}, :public => true
44 :reports => :issue_report}
45 map.permission :add_issues, {:issues => :new}
45 map.permission :add_issues, {:issues => :new}
46 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
46 map.permission :edit_issues, {:issues => [:edit, :reply, :bulk_edit]}
47 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
47 map.permission :manage_issue_relations, {:issue_relations => [:new, :destroy]}
48 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
48 map.permission :add_issue_notes, {:issues => [:edit, :reply]}
49 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
49 map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin
50 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
50 map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin
51 map.permission :move_issues, {:issues => :move}, :require => :loggedin
51 map.permission :move_issues, {:issues => :move}, :require => :loggedin
52 map.permission :delete_issues, {:issues => :destroy}, :require => :member
52 map.permission :delete_issues, {:issues => :destroy}, :require => :member
53 # Queries
53 # Queries
54 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
54 map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
55 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
55 map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
56 # Gantt & calendar
56 # Gantt & calendar
57 map.permission :view_gantt, :issues => :gantt
57 map.permission :view_gantt, :issues => :gantt
58 map.permission :view_calendar, :issues => :calendar
58 map.permission :view_calendar, :issues => :calendar
59 # Watchers
59 # Watchers
60 map.permission :view_issue_watchers, {}
60 map.permission :view_issue_watchers, {}
61 map.permission :add_issue_watchers, {:watchers => :new}
61 map.permission :add_issue_watchers, {:watchers => :new}
62 map.permission :delete_issue_watchers, {:watchers => :destroy}
62 map.permission :delete_issue_watchers, {:watchers => :destroy}
63 end
63 end
64
64
65 map.project_module :time_tracking do |map|
65 map.project_module :time_tracking do |map|
66 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
66 map.permission :log_time, {:timelog => :edit}, :require => :loggedin
67 map.permission :view_time_entries, :timelog => [:details, :report]
67 map.permission :view_time_entries, :timelog => [:details, :report]
68 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
68 map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
69 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
69 map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
70 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
70 map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
71 end
71 end
72
72
73 map.project_module :news do |map|
73 map.project_module :news do |map|
74 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
74 map.permission :manage_news, {:news => [:new, :edit, :destroy, :destroy_comment]}, :require => :member
75 map.permission :view_news, {:news => [:index, :show]}, :public => true
75 map.permission :view_news, {:news => [:index, :show]}, :public => true
76 map.permission :comment_news, {:news => :add_comment}
76 map.permission :comment_news, {:news => :add_comment}
77 end
77 end
78
78
79 map.project_module :documents do |map|
79 map.project_module :documents do |map|
80 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
80 map.permission :manage_documents, {:documents => [:new, :edit, :destroy, :add_attachment]}, :require => :loggedin
81 map.permission :view_documents, :documents => [:index, :show, :download]
81 map.permission :view_documents, :documents => [:index, :show, :download]
82 end
82 end
83
83
84 map.project_module :files do |map|
84 map.project_module :files do |map|
85 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
85 map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
86 map.permission :view_files, :projects => :list_files, :versions => :download
86 map.permission :view_files, :projects => :list_files, :versions => :download
87 end
87 end
88
88
89 map.project_module :wiki do |map|
89 map.project_module :wiki do |map|
90 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
90 map.permission :manage_wiki, {:wikis => [:edit, :destroy]}, :require => :member
91 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
91 map.permission :rename_wiki_pages, {:wiki => :rename}, :require => :member
92 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
92 map.permission :delete_wiki_pages, {:wiki => :destroy}, :require => :member
93 map.permission :view_wiki_pages, :wiki => [:index, :special]
93 map.permission :view_wiki_pages, :wiki => [:index, :special]
94 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
94 map.permission :view_wiki_edits, :wiki => [:history, :diff, :annotate]
95 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
95 map.permission :edit_wiki_pages, :wiki => [:edit, :preview, :add_attachment]
96 map.permission :delete_wiki_pages_attachments, {}
96 map.permission :delete_wiki_pages_attachments, {}
97 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
97 map.permission :protect_wiki_pages, {:wiki => :protect}, :require => :member
98 end
98 end
99
99
100 map.project_module :repository do |map|
100 map.project_module :repository do |map|
101 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
101 map.permission :manage_repository, {:repositories => [:edit, :committers, :destroy]}, :require => :member
102 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
102 map.permission :browse_repository, :repositories => [:show, :browse, :entry, :annotate, :changes, :diff, :stats, :graph]
103 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
103 map.permission :view_changesets, :repositories => [:show, :revisions, :revision]
104 map.permission :commit_access, {}
104 map.permission :commit_access, {}
105 end
105 end
106
106
107 map.project_module :boards do |map|
107 map.project_module :boards do |map|
108 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
108 map.permission :manage_boards, {:boards => [:new, :edit, :destroy]}, :require => :member
109 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
109 map.permission :view_messages, {:boards => [:index, :show], :messages => [:show]}, :public => true
110 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
110 map.permission :add_messages, {:messages => [:new, :reply, :quote]}
111 map.permission :edit_messages, {:messages => :edit}, :require => :member
111 map.permission :edit_messages, {:messages => :edit}, :require => :member
112 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
112 map.permission :edit_own_messages, {:messages => :edit}, :require => :loggedin
113 map.permission :delete_messages, {:messages => :destroy}, :require => :member
113 map.permission :delete_messages, {:messages => :destroy}, :require => :member
114 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
114 map.permission :delete_own_messages, {:messages => :destroy}, :require => :loggedin
115 end
115 end
116 end
116 end
117
117
118 Redmine::MenuManager.map :top_menu do |menu|
118 Redmine::MenuManager.map :top_menu do |menu|
119 menu.push :home, :home_path
119 menu.push :home, :home_path
120 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
120 menu.push :my_page, { :controller => 'my', :action => 'page' }, :if => Proc.new { User.current.logged? }
121 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
121 menu.push :projects, { :controller => 'projects', :action => 'index' }, :caption => :label_project_plural
122 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
122 menu.push :administration, { :controller => 'admin', :action => 'index' }, :if => Proc.new { User.current.admin? }, :last => true
123 menu.push :help, Redmine::Info.help_url, :last => true
123 menu.push :help, Redmine::Info.help_url, :last => true
124 end
124 end
125
125
126 Redmine::MenuManager.map :account_menu do |menu|
126 Redmine::MenuManager.map :account_menu do |menu|
127 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
127 menu.push :login, :signin_path, :if => Proc.new { !User.current.logged? }
128 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
128 menu.push :register, { :controller => 'account', :action => 'register' }, :if => Proc.new { !User.current.logged? && Setting.self_registration? }
129 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
129 menu.push :my_account, { :controller => 'my', :action => 'account' }, :if => Proc.new { User.current.logged? }
130 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
130 menu.push :logout, :signout_path, :if => Proc.new { User.current.logged? }
131 end
131 end
132
132
133 Redmine::MenuManager.map :application_menu do |menu|
133 Redmine::MenuManager.map :application_menu do |menu|
134 # Empty
134 # Empty
135 end
135 end
136
136
137 Redmine::MenuManager.map :admin_menu do |menu|
137 Redmine::MenuManager.map :admin_menu do |menu|
138 # Empty
138 # Empty
139 end
139 end
140
140
141 Redmine::MenuManager.map :project_menu do |menu|
141 Redmine::MenuManager.map :project_menu do |menu|
142 menu.push :overview, { :controller => 'projects', :action => 'show' }
142 menu.push :overview, { :controller => 'projects', :action => 'show' }
143 menu.push :activity, { :controller => 'projects', :action => 'activity' }
143 menu.push :activity, { :controller => 'projects', :action => 'activity' }
144 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
144 menu.push :roadmap, { :controller => 'projects', :action => 'roadmap' },
145 :if => Proc.new { |p| p.versions.any? }
145 :if => Proc.new { |p| p.versions.any? }
146 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
146 menu.push :issues, { :controller => 'issues', :action => 'index' }, :param => :project_id, :caption => :label_issue_plural
147 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
147 menu.push :new_issue, { :controller => 'issues', :action => 'new' }, :param => :project_id, :caption => :label_issue_new,
148 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
148 :html => { :accesskey => Redmine::AccessKeys.key_for(:new_issue) }
149 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
149 menu.push :news, { :controller => 'news', :action => 'index' }, :param => :project_id, :caption => :label_news_plural
150 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
150 menu.push :documents, { :controller => 'documents', :action => 'index' }, :param => :project_id, :caption => :label_document_plural
151 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
151 menu.push :wiki, { :controller => 'wiki', :action => 'index', :page => nil },
152 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
152 :if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
153 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
153 menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
154 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
154 :if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
155 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
155 menu.push :files, { :controller => 'projects', :action => 'list_files' }, :caption => :label_attachment_plural
156 menu.push :repository, { :controller => 'repositories', :action => 'show' },
156 menu.push :repository, { :controller => 'repositories', :action => 'show' },
157 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
157 :if => Proc.new { |p| p.repository && !p.repository.new_record? }
158 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
158 menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true
159 end
159 end
160
160
161 Redmine::Activity.map do |activity|
161 Redmine::Activity.map do |activity|
162 activity.register :issues, :class_name => %w(Issue Journal)
162 activity.register :issues, :class_name => %w(Issue Journal)
163 activity.register :changesets
163 activity.register :changesets
164 activity.register :news
164 activity.register :news
165 activity.register :documents, :class_name => %w(Document Attachment)
165 activity.register :documents, :class_name => %w(Document Attachment)
166 activity.register :files, :class_name => 'Attachment'
166 activity.register :files, :class_name => 'Attachment'
167 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
167 activity.register :wiki_edits, :class_name => 'WikiContent::Version', :default => false
168 activity.register :messages, :default => false
168 activity.register :messages, :default => false
169 activity.register :time_entries, :default => false
169 activity.register :time_entries, :default => false
170 end
170 end
171
171
172 Redmine::WikiFormatting.map do |format|
172 Redmine::WikiFormatting.map do |format|
173 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
173 format.register :textile, Redmine::WikiFormatting::Textile::Formatter, Redmine::WikiFormatting::Textile::Helper
174 end
174 end
@@ -1,176 +1,180
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module Redmine
18 module Redmine
19 module DefaultData
19 module DefaultData
20 class DataAlreadyLoaded < Exception; end
20 class DataAlreadyLoaded < Exception; end
21
21
22 module Loader
22 module Loader
23 include Redmine::I18n
23 include Redmine::I18n
24
24
25 class << self
25 class << self
26 # Returns true if no data is already loaded in the database
26 # Returns true if no data is already loaded in the database
27 # otherwise false
27 # otherwise false
28 def no_data?
28 def no_data?
29 !Role.find(:first, :conditions => {:builtin => 0}) &&
29 !Role.find(:first, :conditions => {:builtin => 0}) &&
30 !Tracker.find(:first) &&
30 !Tracker.find(:first) &&
31 !IssueStatus.find(:first) &&
31 !IssueStatus.find(:first) &&
32 !Enumeration.find(:first)
32 !Enumeration.find(:first)
33 end
33 end
34
34
35 # Loads the default data
35 # Loads the default data
36 # Raises a RecordNotSaved exception if something goes wrong
36 # Raises a RecordNotSaved exception if something goes wrong
37 def load(lang=nil)
37 def load(lang=nil)
38 raise DataAlreadyLoaded.new("Some configuration data is already loaded.") unless no_data?
38 raise DataAlreadyLoaded.new("Some configuration data is already loaded.") unless no_data?
39 set_language_if_valid(lang)
39 set_language_if_valid(lang)
40
40
41 Role.transaction do
41 Role.transaction do
42 # Roles
42 # Roles
43 manager = Role.create! :name => l(:default_role_manager),
43 manager = Role.create! :name => l(:default_role_manager),
44 :position => 1
44 :position => 1
45 manager.permissions = manager.setable_permissions.collect {|p| p.name}
45 manager.permissions = manager.setable_permissions.collect {|p| p.name}
46 manager.save!
46 manager.save!
47
47
48 developper = Role.create! :name => l(:default_role_developper),
48 developper = Role.create! :name => l(:default_role_developper),
49 :position => 2,
49 :position => 2,
50 :permissions => [:manage_versions,
50 :permissions => [:manage_versions,
51 :manage_categories,
51 :manage_categories,
52 :view_issues,
52 :add_issues,
53 :add_issues,
53 :edit_issues,
54 :edit_issues,
54 :manage_issue_relations,
55 :manage_issue_relations,
55 :add_issue_notes,
56 :add_issue_notes,
56 :save_queries,
57 :save_queries,
57 :view_gantt,
58 :view_gantt,
58 :view_calendar,
59 :view_calendar,
59 :log_time,
60 :log_time,
60 :view_time_entries,
61 :view_time_entries,
61 :comment_news,
62 :comment_news,
62 :view_documents,
63 :view_documents,
63 :view_wiki_pages,
64 :view_wiki_pages,
64 :view_wiki_edits,
65 :view_wiki_edits,
65 :edit_wiki_pages,
66 :edit_wiki_pages,
66 :delete_wiki_pages,
67 :delete_wiki_pages,
67 :add_messages,
68 :add_messages,
68 :edit_own_messages,
69 :edit_own_messages,
69 :view_files,
70 :view_files,
70 :manage_files,
71 :manage_files,
71 :browse_repository,
72 :browse_repository,
72 :view_changesets,
73 :view_changesets,
73 :commit_access]
74 :commit_access]
74
75
75 reporter = Role.create! :name => l(:default_role_reporter),
76 reporter = Role.create! :name => l(:default_role_reporter),
76 :position => 3,
77 :position => 3,
77 :permissions => [:add_issues,
78 :permissions => [:view_issues,
79 :add_issues,
78 :add_issue_notes,
80 :add_issue_notes,
79 :save_queries,
81 :save_queries,
80 :view_gantt,
82 :view_gantt,
81 :view_calendar,
83 :view_calendar,
82 :log_time,
84 :log_time,
83 :view_time_entries,
85 :view_time_entries,
84 :comment_news,
86 :comment_news,
85 :view_documents,
87 :view_documents,
86 :view_wiki_pages,
88 :view_wiki_pages,
87 :view_wiki_edits,
89 :view_wiki_edits,
88 :add_messages,
90 :add_messages,
89 :edit_own_messages,
91 :edit_own_messages,
90 :view_files,
92 :view_files,
91 :browse_repository,
93 :browse_repository,
92 :view_changesets]
94 :view_changesets]
93
95
94 Role.non_member.update_attribute :permissions, [:add_issues,
96 Role.non_member.update_attribute :permissions, [:view_issues,
97 :add_issues,
95 :add_issue_notes,
98 :add_issue_notes,
96 :save_queries,
99 :save_queries,
97 :view_gantt,
100 :view_gantt,
98 :view_calendar,
101 :view_calendar,
99 :view_time_entries,
102 :view_time_entries,
100 :comment_news,
103 :comment_news,
101 :view_documents,
104 :view_documents,
102 :view_wiki_pages,
105 :view_wiki_pages,
103 :view_wiki_edits,
106 :view_wiki_edits,
104 :add_messages,
107 :add_messages,
105 :view_files,
108 :view_files,
106 :browse_repository,
109 :browse_repository,
107 :view_changesets]
110 :view_changesets]
108
111
109 Role.anonymous.update_attribute :permissions, [:view_gantt,
112 Role.anonymous.update_attribute :permissions, [:view_issues,
113 :view_gantt,
110 :view_calendar,
114 :view_calendar,
111 :view_time_entries,
115 :view_time_entries,
112 :view_documents,
116 :view_documents,
113 :view_wiki_pages,
117 :view_wiki_pages,
114 :view_wiki_edits,
118 :view_wiki_edits,
115 :view_files,
119 :view_files,
116 :browse_repository,
120 :browse_repository,
117 :view_changesets]
121 :view_changesets]
118
122
119 # Trackers
123 # Trackers
120 Tracker.create!(:name => l(:default_tracker_bug), :is_in_chlog => true, :is_in_roadmap => false, :position => 1)
124 Tracker.create!(:name => l(:default_tracker_bug), :is_in_chlog => true, :is_in_roadmap => false, :position => 1)
121 Tracker.create!(:name => l(:default_tracker_feature), :is_in_chlog => true, :is_in_roadmap => true, :position => 2)
125 Tracker.create!(:name => l(:default_tracker_feature), :is_in_chlog => true, :is_in_roadmap => true, :position => 2)
122 Tracker.create!(:name => l(:default_tracker_support), :is_in_chlog => false, :is_in_roadmap => false, :position => 3)
126 Tracker.create!(:name => l(:default_tracker_support), :is_in_chlog => false, :is_in_roadmap => false, :position => 3)
123
127
124 # Issue statuses
128 # Issue statuses
125 new = IssueStatus.create!(:name => l(:default_issue_status_new), :is_closed => false, :is_default => true, :position => 1)
129 new = IssueStatus.create!(:name => l(:default_issue_status_new), :is_closed => false, :is_default => true, :position => 1)
126 in_progress = IssueStatus.create!(:name => l(:default_issue_status_in_progress), :is_closed => false, :is_default => false, :position => 2)
130 in_progress = IssueStatus.create!(:name => l(:default_issue_status_in_progress), :is_closed => false, :is_default => false, :position => 2)
127 resolved = IssueStatus.create!(:name => l(:default_issue_status_resolved), :is_closed => false, :is_default => false, :position => 3)
131 resolved = IssueStatus.create!(:name => l(:default_issue_status_resolved), :is_closed => false, :is_default => false, :position => 3)
128 feedback = IssueStatus.create!(:name => l(:default_issue_status_feedback), :is_closed => false, :is_default => false, :position => 4)
132 feedback = IssueStatus.create!(:name => l(:default_issue_status_feedback), :is_closed => false, :is_default => false, :position => 4)
129 closed = IssueStatus.create!(:name => l(:default_issue_status_closed), :is_closed => true, :is_default => false, :position => 5)
133 closed = IssueStatus.create!(:name => l(:default_issue_status_closed), :is_closed => true, :is_default => false, :position => 5)
130 rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6)
134 rejected = IssueStatus.create!(:name => l(:default_issue_status_rejected), :is_closed => true, :is_default => false, :position => 6)
131
135
132 # Workflow
136 # Workflow
133 Tracker.find(:all).each { |t|
137 Tracker.find(:all).each { |t|
134 IssueStatus.find(:all).each { |os|
138 IssueStatus.find(:all).each { |os|
135 IssueStatus.find(:all).each { |ns|
139 IssueStatus.find(:all).each { |ns|
136 Workflow.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
140 Workflow.create!(:tracker_id => t.id, :role_id => manager.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
137 }
141 }
138 }
142 }
139 }
143 }
140
144
141 Tracker.find(:all).each { |t|
145 Tracker.find(:all).each { |t|
142 [new, in_progress, resolved, feedback].each { |os|
146 [new, in_progress, resolved, feedback].each { |os|
143 [in_progress, resolved, feedback, closed].each { |ns|
147 [in_progress, resolved, feedback, closed].each { |ns|
144 Workflow.create!(:tracker_id => t.id, :role_id => developper.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
148 Workflow.create!(:tracker_id => t.id, :role_id => developper.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
145 }
149 }
146 }
150 }
147 }
151 }
148
152
149 Tracker.find(:all).each { |t|
153 Tracker.find(:all).each { |t|
150 [new, in_progress, resolved, feedback].each { |os|
154 [new, in_progress, resolved, feedback].each { |os|
151 [closed].each { |ns|
155 [closed].each { |ns|
152 Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
156 Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => os.id, :new_status_id => ns.id) unless os == ns
153 }
157 }
154 }
158 }
155 Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id)
159 Workflow.create!(:tracker_id => t.id, :role_id => reporter.id, :old_status_id => resolved.id, :new_status_id => feedback.id)
156 }
160 }
157
161
158 # Enumerations
162 # Enumerations
159 DocumentCategory.create!(:opt => "DCAT", :name => l(:default_doc_category_user), :position => 1)
163 DocumentCategory.create!(:opt => "DCAT", :name => l(:default_doc_category_user), :position => 1)
160 DocumentCategory.create!(:opt => "DCAT", :name => l(:default_doc_category_tech), :position => 2)
164 DocumentCategory.create!(:opt => "DCAT", :name => l(:default_doc_category_tech), :position => 2)
161
165
162 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_low), :position => 1)
166 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_low), :position => 1)
163 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_normal), :position => 2, :is_default => true)
167 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_normal), :position => 2, :is_default => true)
164 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_high), :position => 3)
168 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_high), :position => 3)
165 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_urgent), :position => 4)
169 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_urgent), :position => 4)
166 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_immediate), :position => 5)
170 IssuePriority.create!(:opt => "IPRI", :name => l(:default_priority_immediate), :position => 5)
167
171
168 TimeEntryActivity.create!(:opt => "ACTI", :name => l(:default_activity_design), :position => 1)
172 TimeEntryActivity.create!(:opt => "ACTI", :name => l(:default_activity_design), :position => 1)
169 TimeEntryActivity.create!(:opt => "ACTI", :name => l(:default_activity_development), :position => 2)
173 TimeEntryActivity.create!(:opt => "ACTI", :name => l(:default_activity_development), :position => 2)
170 end
174 end
171 true
175 true
172 end
176 end
173 end
177 end
174 end
178 end
175 end
179 end
176 end
180 end
@@ -1,179 +1,184
1 ---
1 ---
2 roles_001:
2 roles_001:
3 name: Manager
3 name: Manager
4 id: 1
4 id: 1
5 builtin: 0
5 builtin: 0
6 permissions: |
6 permissions: |
7 ---
7 ---
8 - :add_project
8 - :add_project
9 - :edit_project
9 - :edit_project
10 - :manage_members
10 - :manage_members
11 - :manage_versions
11 - :manage_versions
12 - :manage_categories
12 - :manage_categories
13 - :view_issues
13 - :add_issues
14 - :add_issues
14 - :edit_issues
15 - :edit_issues
15 - :manage_issue_relations
16 - :manage_issue_relations
16 - :add_issue_notes
17 - :add_issue_notes
17 - :move_issues
18 - :move_issues
18 - :delete_issues
19 - :delete_issues
19 - :view_issue_watchers
20 - :view_issue_watchers
20 - :add_issue_watchers
21 - :add_issue_watchers
21 - :delete_issue_watchers
22 - :delete_issue_watchers
22 - :manage_public_queries
23 - :manage_public_queries
23 - :save_queries
24 - :save_queries
24 - :view_gantt
25 - :view_gantt
25 - :view_calendar
26 - :view_calendar
26 - :log_time
27 - :log_time
27 - :view_time_entries
28 - :view_time_entries
28 - :edit_time_entries
29 - :edit_time_entries
29 - :delete_time_entries
30 - :delete_time_entries
30 - :manage_news
31 - :manage_news
31 - :comment_news
32 - :comment_news
32 - :view_documents
33 - :view_documents
33 - :manage_documents
34 - :manage_documents
34 - :view_wiki_pages
35 - :view_wiki_pages
35 - :view_wiki_edits
36 - :view_wiki_edits
36 - :edit_wiki_pages
37 - :edit_wiki_pages
37 - :delete_wiki_pages_attachments
38 - :delete_wiki_pages_attachments
38 - :protect_wiki_pages
39 - :protect_wiki_pages
39 - :delete_wiki_pages
40 - :delete_wiki_pages
40 - :rename_wiki_pages
41 - :rename_wiki_pages
41 - :add_messages
42 - :add_messages
42 - :edit_messages
43 - :edit_messages
43 - :delete_messages
44 - :delete_messages
44 - :manage_boards
45 - :manage_boards
45 - :view_files
46 - :view_files
46 - :manage_files
47 - :manage_files
47 - :browse_repository
48 - :browse_repository
48 - :manage_repository
49 - :manage_repository
49 - :view_changesets
50 - :view_changesets
50 - :manage_project_activities
51 - :manage_project_activities
51
52
52 position: 1
53 position: 1
53 roles_002:
54 roles_002:
54 name: Developer
55 name: Developer
55 id: 2
56 id: 2
56 builtin: 0
57 builtin: 0
57 permissions: |
58 permissions: |
58 ---
59 ---
59 - :edit_project
60 - :edit_project
60 - :manage_members
61 - :manage_members
61 - :manage_versions
62 - :manage_versions
62 - :manage_categories
63 - :manage_categories
64 - :view_issues
63 - :add_issues
65 - :add_issues
64 - :edit_issues
66 - :edit_issues
65 - :manage_issue_relations
67 - :manage_issue_relations
66 - :add_issue_notes
68 - :add_issue_notes
67 - :move_issues
69 - :move_issues
68 - :delete_issues
70 - :delete_issues
69 - :view_issue_watchers
71 - :view_issue_watchers
70 - :save_queries
72 - :save_queries
71 - :view_gantt
73 - :view_gantt
72 - :view_calendar
74 - :view_calendar
73 - :log_time
75 - :log_time
74 - :view_time_entries
76 - :view_time_entries
75 - :edit_own_time_entries
77 - :edit_own_time_entries
76 - :manage_news
78 - :manage_news
77 - :comment_news
79 - :comment_news
78 - :view_documents
80 - :view_documents
79 - :manage_documents
81 - :manage_documents
80 - :view_wiki_pages
82 - :view_wiki_pages
81 - :view_wiki_edits
83 - :view_wiki_edits
82 - :edit_wiki_pages
84 - :edit_wiki_pages
83 - :protect_wiki_pages
85 - :protect_wiki_pages
84 - :delete_wiki_pages
86 - :delete_wiki_pages
85 - :add_messages
87 - :add_messages
86 - :edit_own_messages
88 - :edit_own_messages
87 - :delete_own_messages
89 - :delete_own_messages
88 - :manage_boards
90 - :manage_boards
89 - :view_files
91 - :view_files
90 - :manage_files
92 - :manage_files
91 - :browse_repository
93 - :browse_repository
92 - :view_changesets
94 - :view_changesets
93
95
94 position: 2
96 position: 2
95 roles_003:
97 roles_003:
96 name: Reporter
98 name: Reporter
97 id: 3
99 id: 3
98 builtin: 0
100 builtin: 0
99 permissions: |
101 permissions: |
100 ---
102 ---
101 - :edit_project
103 - :edit_project
102 - :manage_members
104 - :manage_members
103 - :manage_versions
105 - :manage_versions
104 - :manage_categories
106 - :manage_categories
107 - :view_issues
105 - :add_issues
108 - :add_issues
106 - :edit_issues
109 - :edit_issues
107 - :manage_issue_relations
110 - :manage_issue_relations
108 - :add_issue_notes
111 - :add_issue_notes
109 - :move_issues
112 - :move_issues
110 - :view_issue_watchers
113 - :view_issue_watchers
111 - :save_queries
114 - :save_queries
112 - :view_gantt
115 - :view_gantt
113 - :view_calendar
116 - :view_calendar
114 - :log_time
117 - :log_time
115 - :view_time_entries
118 - :view_time_entries
116 - :manage_news
119 - :manage_news
117 - :comment_news
120 - :comment_news
118 - :view_documents
121 - :view_documents
119 - :manage_documents
122 - :manage_documents
120 - :view_wiki_pages
123 - :view_wiki_pages
121 - :view_wiki_edits
124 - :view_wiki_edits
122 - :edit_wiki_pages
125 - :edit_wiki_pages
123 - :delete_wiki_pages
126 - :delete_wiki_pages
124 - :add_messages
127 - :add_messages
125 - :manage_boards
128 - :manage_boards
126 - :view_files
129 - :view_files
127 - :manage_files
130 - :manage_files
128 - :browse_repository
131 - :browse_repository
129 - :view_changesets
132 - :view_changesets
130
133
131 position: 3
134 position: 3
132 roles_004:
135 roles_004:
133 name: Non member
136 name: Non member
134 id: 4
137 id: 4
135 builtin: 1
138 builtin: 1
136 permissions: |
139 permissions: |
137 ---
140 ---
141 - :view_issues
138 - :add_issues
142 - :add_issues
139 - :edit_issues
143 - :edit_issues
140 - :manage_issue_relations
144 - :manage_issue_relations
141 - :add_issue_notes
145 - :add_issue_notes
142 - :move_issues
146 - :move_issues
143 - :save_queries
147 - :save_queries
144 - :view_gantt
148 - :view_gantt
145 - :view_calendar
149 - :view_calendar
146 - :log_time
150 - :log_time
147 - :view_time_entries
151 - :view_time_entries
148 - :comment_news
152 - :comment_news
149 - :view_documents
153 - :view_documents
150 - :manage_documents
154 - :manage_documents
151 - :view_wiki_pages
155 - :view_wiki_pages
152 - :view_wiki_edits
156 - :view_wiki_edits
153 - :edit_wiki_pages
157 - :edit_wiki_pages
154 - :add_messages
158 - :add_messages
155 - :view_files
159 - :view_files
156 - :manage_files
160 - :manage_files
157 - :browse_repository
161 - :browse_repository
158 - :view_changesets
162 - :view_changesets
159
163
160 position: 4
164 position: 4
161 roles_005:
165 roles_005:
162 name: Anonymous
166 name: Anonymous
163 id: 5
167 id: 5
164 builtin: 2
168 builtin: 2
165 permissions: |
169 permissions: |
166 ---
170 ---
171 - :view_issues
167 - :add_issue_notes
172 - :add_issue_notes
168 - :view_gantt
173 - :view_gantt
169 - :view_calendar
174 - :view_calendar
170 - :view_time_entries
175 - :view_time_entries
171 - :view_documents
176 - :view_documents
172 - :view_wiki_pages
177 - :view_wiki_pages
173 - :view_wiki_edits
178 - :view_wiki_edits
174 - :view_files
179 - :view_files
175 - :browse_repository
180 - :browse_repository
176 - :view_changesets
181 - :view_changesets
177
182
178 position: 5
183 position: 5
179
184
@@ -1,1106 +1,1126
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class IssuesController; def rescue_action(e) raise e end; end
22 class IssuesController; def rescue_action(e) raise e end; end
23
23
24 class IssuesControllerTest < ActionController::TestCase
24 class IssuesControllerTest < ActionController::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :member_roles,
29 :member_roles,
30 :issues,
30 :issues,
31 :issue_statuses,
31 :issue_statuses,
32 :versions,
32 :versions,
33 :trackers,
33 :trackers,
34 :projects_trackers,
34 :projects_trackers,
35 :issue_categories,
35 :issue_categories,
36 :enabled_modules,
36 :enabled_modules,
37 :enumerations,
37 :enumerations,
38 :attachments,
38 :attachments,
39 :workflows,
39 :workflows,
40 :custom_fields,
40 :custom_fields,
41 :custom_values,
41 :custom_values,
42 :custom_fields_trackers,
42 :custom_fields_trackers,
43 :time_entries,
43 :time_entries,
44 :journals,
44 :journals,
45 :journal_details,
45 :journal_details,
46 :queries
46 :queries
47
47
48 def setup
48 def setup
49 @controller = IssuesController.new
49 @controller = IssuesController.new
50 @request = ActionController::TestRequest.new
50 @request = ActionController::TestRequest.new
51 @response = ActionController::TestResponse.new
51 @response = ActionController::TestResponse.new
52 User.current = nil
52 User.current = nil
53 end
53 end
54
54
55 def test_index_routing
55 def test_index_routing
56 assert_routing(
56 assert_routing(
57 {:method => :get, :path => '/issues'},
57 {:method => :get, :path => '/issues'},
58 :controller => 'issues', :action => 'index'
58 :controller => 'issues', :action => 'index'
59 )
59 )
60 end
60 end
61
61
62 def test_index
62 def test_index
63 Setting.default_language = 'en'
63 Setting.default_language = 'en'
64
64
65 get :index
65 get :index
66 assert_response :success
66 assert_response :success
67 assert_template 'index.rhtml'
67 assert_template 'index.rhtml'
68 assert_not_nil assigns(:issues)
68 assert_not_nil assigns(:issues)
69 assert_nil assigns(:project)
69 assert_nil assigns(:project)
70 assert_tag :tag => 'a', :content => /Can't print recipes/
70 assert_tag :tag => 'a', :content => /Can't print recipes/
71 assert_tag :tag => 'a', :content => /Subproject issue/
71 assert_tag :tag => 'a', :content => /Subproject issue/
72 # private projects hidden
72 # private projects hidden
73 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
73 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
74 assert_no_tag :tag => 'a', :content => /Issue on project 2/
74 assert_no_tag :tag => 'a', :content => /Issue on project 2/
75 # project column
75 # project column
76 assert_tag :tag => 'th', :content => /Project/
76 assert_tag :tag => 'th', :content => /Project/
77 end
77 end
78
78
79 def test_index_should_not_list_issues_when_module_disabled
79 def test_index_should_not_list_issues_when_module_disabled
80 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
80 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
81 get :index
81 get :index
82 assert_response :success
82 assert_response :success
83 assert_template 'index.rhtml'
83 assert_template 'index.rhtml'
84 assert_not_nil assigns(:issues)
84 assert_not_nil assigns(:issues)
85 assert_nil assigns(:project)
85 assert_nil assigns(:project)
86 assert_no_tag :tag => 'a', :content => /Can't print recipes/
86 assert_no_tag :tag => 'a', :content => /Can't print recipes/
87 assert_tag :tag => 'a', :content => /Subproject issue/
87 assert_tag :tag => 'a', :content => /Subproject issue/
88 end
88 end
89
89
90 def test_index_with_project_routing
90 def test_index_with_project_routing
91 assert_routing(
91 assert_routing(
92 {:method => :get, :path => '/projects/23/issues'},
92 {:method => :get, :path => '/projects/23/issues'},
93 :controller => 'issues', :action => 'index', :project_id => '23'
93 :controller => 'issues', :action => 'index', :project_id => '23'
94 )
94 )
95 end
95 end
96
96
97 def test_index_should_not_list_issues_when_module_disabled
97 def test_index_should_not_list_issues_when_module_disabled
98 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
98 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
99 get :index
99 get :index
100 assert_response :success
100 assert_response :success
101 assert_template 'index.rhtml'
101 assert_template 'index.rhtml'
102 assert_not_nil assigns(:issues)
102 assert_not_nil assigns(:issues)
103 assert_nil assigns(:project)
103 assert_nil assigns(:project)
104 assert_no_tag :tag => 'a', :content => /Can't print recipes/
104 assert_no_tag :tag => 'a', :content => /Can't print recipes/
105 assert_tag :tag => 'a', :content => /Subproject issue/
105 assert_tag :tag => 'a', :content => /Subproject issue/
106 end
106 end
107
107
108 def test_index_with_project_routing
108 def test_index_with_project_routing
109 assert_routing(
109 assert_routing(
110 {:method => :get, :path => 'projects/23/issues'},
110 {:method => :get, :path => 'projects/23/issues'},
111 :controller => 'issues', :action => 'index', :project_id => '23'
111 :controller => 'issues', :action => 'index', :project_id => '23'
112 )
112 )
113 end
113 end
114
114
115 def test_index_with_project
115 def test_index_with_project
116 Setting.display_subprojects_issues = 0
116 Setting.display_subprojects_issues = 0
117 get :index, :project_id => 1
117 get :index, :project_id => 1
118 assert_response :success
118 assert_response :success
119 assert_template 'index.rhtml'
119 assert_template 'index.rhtml'
120 assert_not_nil assigns(:issues)
120 assert_not_nil assigns(:issues)
121 assert_tag :tag => 'a', :content => /Can't print recipes/
121 assert_tag :tag => 'a', :content => /Can't print recipes/
122 assert_no_tag :tag => 'a', :content => /Subproject issue/
122 assert_no_tag :tag => 'a', :content => /Subproject issue/
123 end
123 end
124
124
125 def test_index_with_project_and_subprojects
125 def test_index_with_project_and_subprojects
126 Setting.display_subprojects_issues = 1
126 Setting.display_subprojects_issues = 1
127 get :index, :project_id => 1
127 get :index, :project_id => 1
128 assert_response :success
128 assert_response :success
129 assert_template 'index.rhtml'
129 assert_template 'index.rhtml'
130 assert_not_nil assigns(:issues)
130 assert_not_nil assigns(:issues)
131 assert_tag :tag => 'a', :content => /Can't print recipes/
131 assert_tag :tag => 'a', :content => /Can't print recipes/
132 assert_tag :tag => 'a', :content => /Subproject issue/
132 assert_tag :tag => 'a', :content => /Subproject issue/
133 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
133 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
134 end
134 end
135
135
136 def test_index_with_project_and_subprojects_should_show_private_subprojects
136 def test_index_with_project_and_subprojects_should_show_private_subprojects
137 @request.session[:user_id] = 2
137 @request.session[:user_id] = 2
138 Setting.display_subprojects_issues = 1
138 Setting.display_subprojects_issues = 1
139 get :index, :project_id => 1
139 get :index, :project_id => 1
140 assert_response :success
140 assert_response :success
141 assert_template 'index.rhtml'
141 assert_template 'index.rhtml'
142 assert_not_nil assigns(:issues)
142 assert_not_nil assigns(:issues)
143 assert_tag :tag => 'a', :content => /Can't print recipes/
143 assert_tag :tag => 'a', :content => /Can't print recipes/
144 assert_tag :tag => 'a', :content => /Subproject issue/
144 assert_tag :tag => 'a', :content => /Subproject issue/
145 assert_tag :tag => 'a', :content => /Issue of a private subproject/
145 assert_tag :tag => 'a', :content => /Issue of a private subproject/
146 end
146 end
147
147
148 def test_index_with_project_routing_formatted
148 def test_index_with_project_routing_formatted
149 assert_routing(
149 assert_routing(
150 {:method => :get, :path => 'projects/23/issues.pdf'},
150 {:method => :get, :path => 'projects/23/issues.pdf'},
151 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
151 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'pdf'
152 )
152 )
153 assert_routing(
153 assert_routing(
154 {:method => :get, :path => 'projects/23/issues.atom'},
154 {:method => :get, :path => 'projects/23/issues.atom'},
155 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
155 :controller => 'issues', :action => 'index', :project_id => '23', :format => 'atom'
156 )
156 )
157 end
157 end
158
158
159 def test_index_with_project_and_filter
159 def test_index_with_project_and_filter
160 get :index, :project_id => 1, :set_filter => 1
160 get :index, :project_id => 1, :set_filter => 1
161 assert_response :success
161 assert_response :success
162 assert_template 'index.rhtml'
162 assert_template 'index.rhtml'
163 assert_not_nil assigns(:issues)
163 assert_not_nil assigns(:issues)
164 end
164 end
165
165
166 def test_index_with_query
166 def test_index_with_query
167 get :index, :project_id => 1, :query_id => 5
167 get :index, :project_id => 1, :query_id => 5
168 assert_response :success
168 assert_response :success
169 assert_template 'index.rhtml'
169 assert_template 'index.rhtml'
170 assert_not_nil assigns(:issues)
170 assert_not_nil assigns(:issues)
171 assert_nil assigns(:issue_count_by_group)
171 assert_nil assigns(:issue_count_by_group)
172 end
172 end
173
173
174 def test_index_with_grouped_query
174 def test_index_with_grouped_query
175 get :index, :project_id => 1, :query_id => 6
175 get :index, :project_id => 1, :query_id => 6
176 assert_response :success
176 assert_response :success
177 assert_template 'index.rhtml'
177 assert_template 'index.rhtml'
178 assert_not_nil assigns(:issues)
178 assert_not_nil assigns(:issues)
179 assert_not_nil assigns(:issue_count_by_group)
179 assert_not_nil assigns(:issue_count_by_group)
180 end
180 end
181
181
182 def test_index_csv_with_project
182 def test_index_csv_with_project
183 Setting.default_language = 'en'
183 Setting.default_language = 'en'
184
184
185 get :index, :format => 'csv'
185 get :index, :format => 'csv'
186 assert_response :success
186 assert_response :success
187 assert_not_nil assigns(:issues)
187 assert_not_nil assigns(:issues)
188 assert_equal 'text/csv', @response.content_type
188 assert_equal 'text/csv', @response.content_type
189 assert @response.body.starts_with?("#,")
189 assert @response.body.starts_with?("#,")
190
190
191 get :index, :project_id => 1, :format => 'csv'
191 get :index, :project_id => 1, :format => 'csv'
192 assert_response :success
192 assert_response :success
193 assert_not_nil assigns(:issues)
193 assert_not_nil assigns(:issues)
194 assert_equal 'text/csv', @response.content_type
194 assert_equal 'text/csv', @response.content_type
195 end
195 end
196
196
197 def test_index_formatted
197 def test_index_formatted
198 assert_routing(
198 assert_routing(
199 {:method => :get, :path => 'issues.pdf'},
199 {:method => :get, :path => 'issues.pdf'},
200 :controller => 'issues', :action => 'index', :format => 'pdf'
200 :controller => 'issues', :action => 'index', :format => 'pdf'
201 )
201 )
202 assert_routing(
202 assert_routing(
203 {:method => :get, :path => 'issues.atom'},
203 {:method => :get, :path => 'issues.atom'},
204 :controller => 'issues', :action => 'index', :format => 'atom'
204 :controller => 'issues', :action => 'index', :format => 'atom'
205 )
205 )
206 end
206 end
207
207
208 def test_index_pdf
208 def test_index_pdf
209 get :index, :format => 'pdf'
209 get :index, :format => 'pdf'
210 assert_response :success
210 assert_response :success
211 assert_not_nil assigns(:issues)
211 assert_not_nil assigns(:issues)
212 assert_equal 'application/pdf', @response.content_type
212 assert_equal 'application/pdf', @response.content_type
213
213
214 get :index, :project_id => 1, :format => 'pdf'
214 get :index, :project_id => 1, :format => 'pdf'
215 assert_response :success
215 assert_response :success
216 assert_not_nil assigns(:issues)
216 assert_not_nil assigns(:issues)
217 assert_equal 'application/pdf', @response.content_type
217 assert_equal 'application/pdf', @response.content_type
218
218
219 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
219 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
220 assert_response :success
220 assert_response :success
221 assert_not_nil assigns(:issues)
221 assert_not_nil assigns(:issues)
222 assert_equal 'application/pdf', @response.content_type
222 assert_equal 'application/pdf', @response.content_type
223 end
223 end
224
224
225 def test_index_sort
225 def test_index_sort
226 get :index, :sort => 'tracker,id:desc'
226 get :index, :sort => 'tracker,id:desc'
227 assert_response :success
227 assert_response :success
228
228
229 sort_params = @request.session['issues_index_sort']
229 sort_params = @request.session['issues_index_sort']
230 assert sort_params.is_a?(String)
230 assert sort_params.is_a?(String)
231 assert_equal 'tracker,id:desc', sort_params
231 assert_equal 'tracker,id:desc', sort_params
232
232
233 issues = assigns(:issues)
233 issues = assigns(:issues)
234 assert_not_nil issues
234 assert_not_nil issues
235 assert !issues.empty?
235 assert !issues.empty?
236 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
236 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
237 end
237 end
238
238
239 def test_gantt
239 def test_gantt
240 get :gantt, :project_id => 1
240 get :gantt, :project_id => 1
241 assert_response :success
241 assert_response :success
242 assert_template 'gantt.rhtml'
242 assert_template 'gantt.rhtml'
243 assert_not_nil assigns(:gantt)
243 assert_not_nil assigns(:gantt)
244 events = assigns(:gantt).events
244 events = assigns(:gantt).events
245 assert_not_nil events
245 assert_not_nil events
246 # Issue with start and due dates
246 # Issue with start and due dates
247 i = Issue.find(1)
247 i = Issue.find(1)
248 assert_not_nil i.due_date
248 assert_not_nil i.due_date
249 assert events.include?(Issue.find(1))
249 assert events.include?(Issue.find(1))
250 # Issue with without due date but targeted to a version with date
250 # Issue with without due date but targeted to a version with date
251 i = Issue.find(2)
251 i = Issue.find(2)
252 assert_nil i.due_date
252 assert_nil i.due_date
253 assert events.include?(i)
253 assert events.include?(i)
254 end
254 end
255
255
256 def test_cross_project_gantt
256 def test_cross_project_gantt
257 get :gantt
257 get :gantt
258 assert_response :success
258 assert_response :success
259 assert_template 'gantt.rhtml'
259 assert_template 'gantt.rhtml'
260 assert_not_nil assigns(:gantt)
260 assert_not_nil assigns(:gantt)
261 events = assigns(:gantt).events
261 events = assigns(:gantt).events
262 assert_not_nil events
262 assert_not_nil events
263 end
263 end
264
264
265 def test_gantt_export_to_pdf
265 def test_gantt_export_to_pdf
266 get :gantt, :project_id => 1, :format => 'pdf'
266 get :gantt, :project_id => 1, :format => 'pdf'
267 assert_response :success
267 assert_response :success
268 assert_equal 'application/pdf', @response.content_type
268 assert_equal 'application/pdf', @response.content_type
269 assert @response.body.starts_with?('%PDF')
269 assert @response.body.starts_with?('%PDF')
270 assert_not_nil assigns(:gantt)
270 assert_not_nil assigns(:gantt)
271 end
271 end
272
272
273 def test_cross_project_gantt_export_to_pdf
273 def test_cross_project_gantt_export_to_pdf
274 get :gantt, :format => 'pdf'
274 get :gantt, :format => 'pdf'
275 assert_response :success
275 assert_response :success
276 assert_equal 'application/pdf', @response.content_type
276 assert_equal 'application/pdf', @response.content_type
277 assert @response.body.starts_with?('%PDF')
277 assert @response.body.starts_with?('%PDF')
278 assert_not_nil assigns(:gantt)
278 assert_not_nil assigns(:gantt)
279 end
279 end
280
280
281 if Object.const_defined?(:Magick)
281 if Object.const_defined?(:Magick)
282 def test_gantt_image
282 def test_gantt_image
283 get :gantt, :project_id => 1, :format => 'png'
283 get :gantt, :project_id => 1, :format => 'png'
284 assert_response :success
284 assert_response :success
285 assert_equal 'image/png', @response.content_type
285 assert_equal 'image/png', @response.content_type
286 end
286 end
287 else
287 else
288 puts "RMagick not installed. Skipping tests !!!"
288 puts "RMagick not installed. Skipping tests !!!"
289 end
289 end
290
290
291 def test_calendar
291 def test_calendar
292 get :calendar, :project_id => 1
292 get :calendar, :project_id => 1
293 assert_response :success
293 assert_response :success
294 assert_template 'calendar'
294 assert_template 'calendar'
295 assert_not_nil assigns(:calendar)
295 assert_not_nil assigns(:calendar)
296 end
296 end
297
297
298 def test_cross_project_calendar
298 def test_cross_project_calendar
299 get :calendar
299 get :calendar
300 assert_response :success
300 assert_response :success
301 assert_template 'calendar'
301 assert_template 'calendar'
302 assert_not_nil assigns(:calendar)
302 assert_not_nil assigns(:calendar)
303 end
303 end
304
304
305 def test_changes
305 def test_changes
306 get :changes, :project_id => 1
306 get :changes, :project_id => 1
307 assert_response :success
307 assert_response :success
308 assert_not_nil assigns(:journals)
308 assert_not_nil assigns(:journals)
309 assert_equal 'application/atom+xml', @response.content_type
309 assert_equal 'application/atom+xml', @response.content_type
310 end
310 end
311
311
312 def test_show_routing
312 def test_show_routing
313 assert_routing(
313 assert_routing(
314 {:method => :get, :path => '/issues/64'},
314 {:method => :get, :path => '/issues/64'},
315 :controller => 'issues', :action => 'show', :id => '64'
315 :controller => 'issues', :action => 'show', :id => '64'
316 )
316 )
317 end
317 end
318
318
319 def test_show_routing_formatted
319 def test_show_routing_formatted
320 assert_routing(
320 assert_routing(
321 {:method => :get, :path => '/issues/2332.pdf'},
321 {:method => :get, :path => '/issues/2332.pdf'},
322 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
322 :controller => 'issues', :action => 'show', :id => '2332', :format => 'pdf'
323 )
323 )
324 assert_routing(
324 assert_routing(
325 {:method => :get, :path => '/issues/23123.atom'},
325 {:method => :get, :path => '/issues/23123.atom'},
326 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
326 :controller => 'issues', :action => 'show', :id => '23123', :format => 'atom'
327 )
327 )
328 end
328 end
329
329
330 def test_show_by_anonymous
330 def test_show_by_anonymous
331 get :show, :id => 1
331 get :show, :id => 1
332 assert_response :success
332 assert_response :success
333 assert_template 'show.rhtml'
333 assert_template 'show.rhtml'
334 assert_not_nil assigns(:issue)
334 assert_not_nil assigns(:issue)
335 assert_equal Issue.find(1), assigns(:issue)
335 assert_equal Issue.find(1), assigns(:issue)
336
336
337 # anonymous role is allowed to add a note
337 # anonymous role is allowed to add a note
338 assert_tag :tag => 'form',
338 assert_tag :tag => 'form',
339 :descendant => { :tag => 'fieldset',
339 :descendant => { :tag => 'fieldset',
340 :child => { :tag => 'legend',
340 :child => { :tag => 'legend',
341 :content => /Notes/ } }
341 :content => /Notes/ } }
342 end
342 end
343
343
344 def test_show_by_manager
344 def test_show_by_manager
345 @request.session[:user_id] = 2
345 @request.session[:user_id] = 2
346 get :show, :id => 1
346 get :show, :id => 1
347 assert_response :success
347 assert_response :success
348
348
349 assert_tag :tag => 'form',
349 assert_tag :tag => 'form',
350 :descendant => { :tag => 'fieldset',
350 :descendant => { :tag => 'fieldset',
351 :child => { :tag => 'legend',
351 :child => { :tag => 'legend',
352 :content => /Change properties/ } },
352 :content => /Change properties/ } },
353 :descendant => { :tag => 'fieldset',
353 :descendant => { :tag => 'fieldset',
354 :child => { :tag => 'legend',
354 :child => { :tag => 'legend',
355 :content => /Log time/ } },
355 :content => /Log time/ } },
356 :descendant => { :tag => 'fieldset',
356 :descendant => { :tag => 'fieldset',
357 :child => { :tag => 'legend',
357 :child => { :tag => 'legend',
358 :content => /Notes/ } }
358 :content => /Notes/ } }
359 end
359 end
360
360
361 def test_show_should_deny_anonymous_access_without_permission
362 Role.anonymous.remove_permission!(:view_issues)
363 get :show, :id => 1
364 assert_response :redirect
365 end
366
367 def test_show_should_deny_non_member_access_without_permission
368 Role.non_member.remove_permission!(:view_issues)
369 @request.session[:user_id] = 9
370 get :show, :id => 1
371 assert_response 403
372 end
373
374 def test_show_should_deny_member_access_without_permission
375 Role.find(1).remove_permission!(:view_issues)
376 @request.session[:user_id] = 2
377 get :show, :id => 1
378 assert_response 403
379 end
380
361 def test_show_should_not_disclose_relations_to_invisible_issues
381 def test_show_should_not_disclose_relations_to_invisible_issues
362 Setting.cross_project_issue_relations = '1'
382 Setting.cross_project_issue_relations = '1'
363 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
383 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
364 # Relation to a private project issue
384 # Relation to a private project issue
365 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
385 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
366
386
367 get :show, :id => 1
387 get :show, :id => 1
368 assert_response :success
388 assert_response :success
369
389
370 assert_tag :div, :attributes => { :id => 'relations' },
390 assert_tag :div, :attributes => { :id => 'relations' },
371 :descendant => { :tag => 'a', :content => /#2$/ }
391 :descendant => { :tag => 'a', :content => /#2$/ }
372 assert_no_tag :div, :attributes => { :id => 'relations' },
392 assert_no_tag :div, :attributes => { :id => 'relations' },
373 :descendant => { :tag => 'a', :content => /#4$/ }
393 :descendant => { :tag => 'a', :content => /#4$/ }
374 end
394 end
375
395
376 def test_show_atom
396 def test_show_atom
377 get :show, :id => 2, :format => 'atom'
397 get :show, :id => 2, :format => 'atom'
378 assert_response :success
398 assert_response :success
379 assert_template 'changes.rxml'
399 assert_template 'changes.rxml'
380 # Inline image
400 # Inline image
381 assert @response.body.include?("&lt;img src=\"http://test.host/attachments/download/10\" alt=\"\" /&gt;"), "Body did not match. Body: #{@response.body}"
401 assert @response.body.include?("&lt;img src=\"http://test.host/attachments/download/10\" alt=\"\" /&gt;"), "Body did not match. Body: #{@response.body}"
382 end
402 end
383
403
384 def test_new_routing
404 def test_new_routing
385 assert_routing(
405 assert_routing(
386 {:method => :get, :path => '/projects/1/issues/new'},
406 {:method => :get, :path => '/projects/1/issues/new'},
387 :controller => 'issues', :action => 'new', :project_id => '1'
407 :controller => 'issues', :action => 'new', :project_id => '1'
388 )
408 )
389 assert_recognizes(
409 assert_recognizes(
390 {:controller => 'issues', :action => 'new', :project_id => '1'},
410 {:controller => 'issues', :action => 'new', :project_id => '1'},
391 {:method => :post, :path => '/projects/1/issues'}
411 {:method => :post, :path => '/projects/1/issues'}
392 )
412 )
393 end
413 end
394
414
395 def test_show_export_to_pdf
415 def test_show_export_to_pdf
396 get :show, :id => 3, :format => 'pdf'
416 get :show, :id => 3, :format => 'pdf'
397 assert_response :success
417 assert_response :success
398 assert_equal 'application/pdf', @response.content_type
418 assert_equal 'application/pdf', @response.content_type
399 assert @response.body.starts_with?('%PDF')
419 assert @response.body.starts_with?('%PDF')
400 assert_not_nil assigns(:issue)
420 assert_not_nil assigns(:issue)
401 end
421 end
402
422
403 def test_get_new
423 def test_get_new
404 @request.session[:user_id] = 2
424 @request.session[:user_id] = 2
405 get :new, :project_id => 1, :tracker_id => 1
425 get :new, :project_id => 1, :tracker_id => 1
406 assert_response :success
426 assert_response :success
407 assert_template 'new'
427 assert_template 'new'
408
428
409 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
429 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
410 :value => 'Default string' }
430 :value => 'Default string' }
411 end
431 end
412
432
413 def test_get_new_without_tracker_id
433 def test_get_new_without_tracker_id
414 @request.session[:user_id] = 2
434 @request.session[:user_id] = 2
415 get :new, :project_id => 1
435 get :new, :project_id => 1
416 assert_response :success
436 assert_response :success
417 assert_template 'new'
437 assert_template 'new'
418
438
419 issue = assigns(:issue)
439 issue = assigns(:issue)
420 assert_not_nil issue
440 assert_not_nil issue
421 assert_equal Project.find(1).trackers.first, issue.tracker
441 assert_equal Project.find(1).trackers.first, issue.tracker
422 end
442 end
423
443
424 def test_get_new_with_no_default_status_should_display_an_error
444 def test_get_new_with_no_default_status_should_display_an_error
425 @request.session[:user_id] = 2
445 @request.session[:user_id] = 2
426 IssueStatus.delete_all
446 IssueStatus.delete_all
427
447
428 get :new, :project_id => 1
448 get :new, :project_id => 1
429 assert_response 500
449 assert_response 500
430 assert_not_nil flash[:error]
450 assert_not_nil flash[:error]
431 assert_tag :tag => 'div', :attributes => { :class => /error/ },
451 assert_tag :tag => 'div', :attributes => { :class => /error/ },
432 :content => /No default issue/
452 :content => /No default issue/
433 end
453 end
434
454
435 def test_get_new_with_no_tracker_should_display_an_error
455 def test_get_new_with_no_tracker_should_display_an_error
436 @request.session[:user_id] = 2
456 @request.session[:user_id] = 2
437 Tracker.delete_all
457 Tracker.delete_all
438
458
439 get :new, :project_id => 1
459 get :new, :project_id => 1
440 assert_response 500
460 assert_response 500
441 assert_not_nil flash[:error]
461 assert_not_nil flash[:error]
442 assert_tag :tag => 'div', :attributes => { :class => /error/ },
462 assert_tag :tag => 'div', :attributes => { :class => /error/ },
443 :content => /No tracker/
463 :content => /No tracker/
444 end
464 end
445
465
446 def test_update_new_form
466 def test_update_new_form
447 @request.session[:user_id] = 2
467 @request.session[:user_id] = 2
448 xhr :post, :new, :project_id => 1,
468 xhr :post, :new, :project_id => 1,
449 :issue => {:tracker_id => 2,
469 :issue => {:tracker_id => 2,
450 :subject => 'This is the test_new issue',
470 :subject => 'This is the test_new issue',
451 :description => 'This is the description',
471 :description => 'This is the description',
452 :priority_id => 5}
472 :priority_id => 5}
453 assert_response :success
473 assert_response :success
454 assert_template 'new'
474 assert_template 'new'
455 end
475 end
456
476
457 def test_post_new
477 def test_post_new
458 @request.session[:user_id] = 2
478 @request.session[:user_id] = 2
459 assert_difference 'Issue.count' do
479 assert_difference 'Issue.count' do
460 post :new, :project_id => 1,
480 post :new, :project_id => 1,
461 :issue => {:tracker_id => 3,
481 :issue => {:tracker_id => 3,
462 :subject => 'This is the test_new issue',
482 :subject => 'This is the test_new issue',
463 :description => 'This is the description',
483 :description => 'This is the description',
464 :priority_id => 5,
484 :priority_id => 5,
465 :estimated_hours => '',
485 :estimated_hours => '',
466 :custom_field_values => {'2' => 'Value for field 2'}}
486 :custom_field_values => {'2' => 'Value for field 2'}}
467 end
487 end
468 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
488 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
469
489
470 issue = Issue.find_by_subject('This is the test_new issue')
490 issue = Issue.find_by_subject('This is the test_new issue')
471 assert_not_nil issue
491 assert_not_nil issue
472 assert_equal 2, issue.author_id
492 assert_equal 2, issue.author_id
473 assert_equal 3, issue.tracker_id
493 assert_equal 3, issue.tracker_id
474 assert_nil issue.estimated_hours
494 assert_nil issue.estimated_hours
475 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
495 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
476 assert_not_nil v
496 assert_not_nil v
477 assert_equal 'Value for field 2', v.value
497 assert_equal 'Value for field 2', v.value
478 end
498 end
479
499
480 def test_post_new_and_continue
500 def test_post_new_and_continue
481 @request.session[:user_id] = 2
501 @request.session[:user_id] = 2
482 post :new, :project_id => 1,
502 post :new, :project_id => 1,
483 :issue => {:tracker_id => 3,
503 :issue => {:tracker_id => 3,
484 :subject => 'This is first issue',
504 :subject => 'This is first issue',
485 :priority_id => 5},
505 :priority_id => 5},
486 :continue => ''
506 :continue => ''
487 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
507 assert_redirected_to :controller => 'issues', :action => 'new', :tracker_id => 3
488 end
508 end
489
509
490 def test_post_new_without_custom_fields_param
510 def test_post_new_without_custom_fields_param
491 @request.session[:user_id] = 2
511 @request.session[:user_id] = 2
492 assert_difference 'Issue.count' do
512 assert_difference 'Issue.count' do
493 post :new, :project_id => 1,
513 post :new, :project_id => 1,
494 :issue => {:tracker_id => 1,
514 :issue => {:tracker_id => 1,
495 :subject => 'This is the test_new issue',
515 :subject => 'This is the test_new issue',
496 :description => 'This is the description',
516 :description => 'This is the description',
497 :priority_id => 5}
517 :priority_id => 5}
498 end
518 end
499 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
519 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
500 end
520 end
501
521
502 def test_post_new_with_required_custom_field_and_without_custom_fields_param
522 def test_post_new_with_required_custom_field_and_without_custom_fields_param
503 field = IssueCustomField.find_by_name('Database')
523 field = IssueCustomField.find_by_name('Database')
504 field.update_attribute(:is_required, true)
524 field.update_attribute(:is_required, true)
505
525
506 @request.session[:user_id] = 2
526 @request.session[:user_id] = 2
507 post :new, :project_id => 1,
527 post :new, :project_id => 1,
508 :issue => {:tracker_id => 1,
528 :issue => {:tracker_id => 1,
509 :subject => 'This is the test_new issue',
529 :subject => 'This is the test_new issue',
510 :description => 'This is the description',
530 :description => 'This is the description',
511 :priority_id => 5}
531 :priority_id => 5}
512 assert_response :success
532 assert_response :success
513 assert_template 'new'
533 assert_template 'new'
514 issue = assigns(:issue)
534 issue = assigns(:issue)
515 assert_not_nil issue
535 assert_not_nil issue
516 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
536 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
517 end
537 end
518
538
519 def test_post_new_with_watchers
539 def test_post_new_with_watchers
520 @request.session[:user_id] = 2
540 @request.session[:user_id] = 2
521 ActionMailer::Base.deliveries.clear
541 ActionMailer::Base.deliveries.clear
522
542
523 assert_difference 'Watcher.count', 2 do
543 assert_difference 'Watcher.count', 2 do
524 post :new, :project_id => 1,
544 post :new, :project_id => 1,
525 :issue => {:tracker_id => 1,
545 :issue => {:tracker_id => 1,
526 :subject => 'This is a new issue with watchers',
546 :subject => 'This is a new issue with watchers',
527 :description => 'This is the description',
547 :description => 'This is the description',
528 :priority_id => 5,
548 :priority_id => 5,
529 :watcher_user_ids => ['2', '3']}
549 :watcher_user_ids => ['2', '3']}
530 end
550 end
531 issue = Issue.find_by_subject('This is a new issue with watchers')
551 issue = Issue.find_by_subject('This is a new issue with watchers')
532 assert_not_nil issue
552 assert_not_nil issue
533 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
553 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
534
554
535 # Watchers added
555 # Watchers added
536 assert_equal [2, 3], issue.watcher_user_ids.sort
556 assert_equal [2, 3], issue.watcher_user_ids.sort
537 assert issue.watched_by?(User.find(3))
557 assert issue.watched_by?(User.find(3))
538 # Watchers notified
558 # Watchers notified
539 mail = ActionMailer::Base.deliveries.last
559 mail = ActionMailer::Base.deliveries.last
540 assert_kind_of TMail::Mail, mail
560 assert_kind_of TMail::Mail, mail
541 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
561 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
542 end
562 end
543
563
544 def test_post_new_should_send_a_notification
564 def test_post_new_should_send_a_notification
545 ActionMailer::Base.deliveries.clear
565 ActionMailer::Base.deliveries.clear
546 @request.session[:user_id] = 2
566 @request.session[:user_id] = 2
547 assert_difference 'Issue.count' do
567 assert_difference 'Issue.count' do
548 post :new, :project_id => 1,
568 post :new, :project_id => 1,
549 :issue => {:tracker_id => 3,
569 :issue => {:tracker_id => 3,
550 :subject => 'This is the test_new issue',
570 :subject => 'This is the test_new issue',
551 :description => 'This is the description',
571 :description => 'This is the description',
552 :priority_id => 5,
572 :priority_id => 5,
553 :estimated_hours => '',
573 :estimated_hours => '',
554 :custom_field_values => {'2' => 'Value for field 2'}}
574 :custom_field_values => {'2' => 'Value for field 2'}}
555 end
575 end
556 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
576 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
557
577
558 assert_equal 1, ActionMailer::Base.deliveries.size
578 assert_equal 1, ActionMailer::Base.deliveries.size
559 end
579 end
560
580
561 def test_post_should_preserve_fields_values_on_validation_failure
581 def test_post_should_preserve_fields_values_on_validation_failure
562 @request.session[:user_id] = 2
582 @request.session[:user_id] = 2
563 post :new, :project_id => 1,
583 post :new, :project_id => 1,
564 :issue => {:tracker_id => 1,
584 :issue => {:tracker_id => 1,
565 # empty subject
585 # empty subject
566 :subject => '',
586 :subject => '',
567 :description => 'This is a description',
587 :description => 'This is a description',
568 :priority_id => 6,
588 :priority_id => 6,
569 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
589 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
570 assert_response :success
590 assert_response :success
571 assert_template 'new'
591 assert_template 'new'
572
592
573 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
593 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
574 :content => 'This is a description'
594 :content => 'This is a description'
575 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
595 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
576 :child => { :tag => 'option', :attributes => { :selected => 'selected',
596 :child => { :tag => 'option', :attributes => { :selected => 'selected',
577 :value => '6' },
597 :value => '6' },
578 :content => 'High' }
598 :content => 'High' }
579 # Custom fields
599 # Custom fields
580 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
600 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
581 :child => { :tag => 'option', :attributes => { :selected => 'selected',
601 :child => { :tag => 'option', :attributes => { :selected => 'selected',
582 :value => 'Oracle' },
602 :value => 'Oracle' },
583 :content => 'Oracle' }
603 :content => 'Oracle' }
584 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
604 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
585 :value => 'Value for field 2'}
605 :value => 'Value for field 2'}
586 end
606 end
587
607
588 def test_copy_routing
608 def test_copy_routing
589 assert_routing(
609 assert_routing(
590 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
610 {:method => :get, :path => '/projects/world_domination/issues/567/copy'},
591 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
611 :controller => 'issues', :action => 'new', :project_id => 'world_domination', :copy_from => '567'
592 )
612 )
593 end
613 end
594
614
595 def test_copy_issue
615 def test_copy_issue
596 @request.session[:user_id] = 2
616 @request.session[:user_id] = 2
597 get :new, :project_id => 1, :copy_from => 1
617 get :new, :project_id => 1, :copy_from => 1
598 assert_template 'new'
618 assert_template 'new'
599 assert_not_nil assigns(:issue)
619 assert_not_nil assigns(:issue)
600 orig = Issue.find(1)
620 orig = Issue.find(1)
601 assert_equal orig.subject, assigns(:issue).subject
621 assert_equal orig.subject, assigns(:issue).subject
602 end
622 end
603
623
604 def test_edit_routing
624 def test_edit_routing
605 assert_routing(
625 assert_routing(
606 {:method => :get, :path => '/issues/1/edit'},
626 {:method => :get, :path => '/issues/1/edit'},
607 :controller => 'issues', :action => 'edit', :id => '1'
627 :controller => 'issues', :action => 'edit', :id => '1'
608 )
628 )
609 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
629 assert_recognizes( #TODO: use a PUT on the issue URI isntead, need to adjust form
610 {:controller => 'issues', :action => 'edit', :id => '1'},
630 {:controller => 'issues', :action => 'edit', :id => '1'},
611 {:method => :post, :path => '/issues/1/edit'}
631 {:method => :post, :path => '/issues/1/edit'}
612 )
632 )
613 end
633 end
614
634
615 def test_get_edit
635 def test_get_edit
616 @request.session[:user_id] = 2
636 @request.session[:user_id] = 2
617 get :edit, :id => 1
637 get :edit, :id => 1
618 assert_response :success
638 assert_response :success
619 assert_template 'edit'
639 assert_template 'edit'
620 assert_not_nil assigns(:issue)
640 assert_not_nil assigns(:issue)
621 assert_equal Issue.find(1), assigns(:issue)
641 assert_equal Issue.find(1), assigns(:issue)
622 end
642 end
623
643
624 def test_get_edit_with_params
644 def test_get_edit_with_params
625 @request.session[:user_id] = 2
645 @request.session[:user_id] = 2
626 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
646 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
627 assert_response :success
647 assert_response :success
628 assert_template 'edit'
648 assert_template 'edit'
629
649
630 issue = assigns(:issue)
650 issue = assigns(:issue)
631 assert_not_nil issue
651 assert_not_nil issue
632
652
633 assert_equal 5, issue.status_id
653 assert_equal 5, issue.status_id
634 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
654 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
635 :child => { :tag => 'option',
655 :child => { :tag => 'option',
636 :content => 'Closed',
656 :content => 'Closed',
637 :attributes => { :selected => 'selected' } }
657 :attributes => { :selected => 'selected' } }
638
658
639 assert_equal 7, issue.priority_id
659 assert_equal 7, issue.priority_id
640 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
660 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
641 :child => { :tag => 'option',
661 :child => { :tag => 'option',
642 :content => 'Urgent',
662 :content => 'Urgent',
643 :attributes => { :selected => 'selected' } }
663 :attributes => { :selected => 'selected' } }
644 end
664 end
645
665
646 def test_reply_routing
666 def test_reply_routing
647 assert_routing(
667 assert_routing(
648 {:method => :post, :path => '/issues/1/quoted'},
668 {:method => :post, :path => '/issues/1/quoted'},
649 :controller => 'issues', :action => 'reply', :id => '1'
669 :controller => 'issues', :action => 'reply', :id => '1'
650 )
670 )
651 end
671 end
652
672
653 def test_reply_to_issue
673 def test_reply_to_issue
654 @request.session[:user_id] = 2
674 @request.session[:user_id] = 2
655 get :reply, :id => 1
675 get :reply, :id => 1
656 assert_response :success
676 assert_response :success
657 assert_select_rjs :show, "update"
677 assert_select_rjs :show, "update"
658 end
678 end
659
679
660 def test_reply_to_note
680 def test_reply_to_note
661 @request.session[:user_id] = 2
681 @request.session[:user_id] = 2
662 get :reply, :id => 1, :journal_id => 2
682 get :reply, :id => 1, :journal_id => 2
663 assert_response :success
683 assert_response :success
664 assert_select_rjs :show, "update"
684 assert_select_rjs :show, "update"
665 end
685 end
666
686
667 def test_post_edit_without_custom_fields_param
687 def test_post_edit_without_custom_fields_param
668 @request.session[:user_id] = 2
688 @request.session[:user_id] = 2
669 ActionMailer::Base.deliveries.clear
689 ActionMailer::Base.deliveries.clear
670
690
671 issue = Issue.find(1)
691 issue = Issue.find(1)
672 assert_equal '125', issue.custom_value_for(2).value
692 assert_equal '125', issue.custom_value_for(2).value
673 old_subject = issue.subject
693 old_subject = issue.subject
674 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
694 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
675
695
676 assert_difference('Journal.count') do
696 assert_difference('Journal.count') do
677 assert_difference('JournalDetail.count', 2) do
697 assert_difference('JournalDetail.count', 2) do
678 post :edit, :id => 1, :issue => {:subject => new_subject,
698 post :edit, :id => 1, :issue => {:subject => new_subject,
679 :priority_id => '6',
699 :priority_id => '6',
680 :category_id => '1' # no change
700 :category_id => '1' # no change
681 }
701 }
682 end
702 end
683 end
703 end
684 assert_redirected_to :action => 'show', :id => '1'
704 assert_redirected_to :action => 'show', :id => '1'
685 issue.reload
705 issue.reload
686 assert_equal new_subject, issue.subject
706 assert_equal new_subject, issue.subject
687 # Make sure custom fields were not cleared
707 # Make sure custom fields were not cleared
688 assert_equal '125', issue.custom_value_for(2).value
708 assert_equal '125', issue.custom_value_for(2).value
689
709
690 mail = ActionMailer::Base.deliveries.last
710 mail = ActionMailer::Base.deliveries.last
691 assert_kind_of TMail::Mail, mail
711 assert_kind_of TMail::Mail, mail
692 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
712 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
693 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
713 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
694 end
714 end
695
715
696 def test_post_edit_with_custom_field_change
716 def test_post_edit_with_custom_field_change
697 @request.session[:user_id] = 2
717 @request.session[:user_id] = 2
698 issue = Issue.find(1)
718 issue = Issue.find(1)
699 assert_equal '125', issue.custom_value_for(2).value
719 assert_equal '125', issue.custom_value_for(2).value
700
720
701 assert_difference('Journal.count') do
721 assert_difference('Journal.count') do
702 assert_difference('JournalDetail.count', 3) do
722 assert_difference('JournalDetail.count', 3) do
703 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
723 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
704 :priority_id => '6',
724 :priority_id => '6',
705 :category_id => '1', # no change
725 :category_id => '1', # no change
706 :custom_field_values => { '2' => 'New custom value' }
726 :custom_field_values => { '2' => 'New custom value' }
707 }
727 }
708 end
728 end
709 end
729 end
710 assert_redirected_to :action => 'show', :id => '1'
730 assert_redirected_to :action => 'show', :id => '1'
711 issue.reload
731 issue.reload
712 assert_equal 'New custom value', issue.custom_value_for(2).value
732 assert_equal 'New custom value', issue.custom_value_for(2).value
713
733
714 mail = ActionMailer::Base.deliveries.last
734 mail = ActionMailer::Base.deliveries.last
715 assert_kind_of TMail::Mail, mail
735 assert_kind_of TMail::Mail, mail
716 assert mail.body.include?("Searchable field changed from 125 to New custom value")
736 assert mail.body.include?("Searchable field changed from 125 to New custom value")
717 end
737 end
718
738
719 def test_post_edit_with_status_and_assignee_change
739 def test_post_edit_with_status_and_assignee_change
720 issue = Issue.find(1)
740 issue = Issue.find(1)
721 assert_equal 1, issue.status_id
741 assert_equal 1, issue.status_id
722 @request.session[:user_id] = 2
742 @request.session[:user_id] = 2
723 assert_difference('TimeEntry.count', 0) do
743 assert_difference('TimeEntry.count', 0) do
724 post :edit,
744 post :edit,
725 :id => 1,
745 :id => 1,
726 :issue => { :status_id => 2, :assigned_to_id => 3 },
746 :issue => { :status_id => 2, :assigned_to_id => 3 },
727 :notes => 'Assigned to dlopper',
747 :notes => 'Assigned to dlopper',
728 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
748 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
729 end
749 end
730 assert_redirected_to :action => 'show', :id => '1'
750 assert_redirected_to :action => 'show', :id => '1'
731 issue.reload
751 issue.reload
732 assert_equal 2, issue.status_id
752 assert_equal 2, issue.status_id
733 j = issue.journals.find(:first, :order => 'id DESC')
753 j = issue.journals.find(:first, :order => 'id DESC')
734 assert_equal 'Assigned to dlopper', j.notes
754 assert_equal 'Assigned to dlopper', j.notes
735 assert_equal 2, j.details.size
755 assert_equal 2, j.details.size
736
756
737 mail = ActionMailer::Base.deliveries.last
757 mail = ActionMailer::Base.deliveries.last
738 assert mail.body.include?("Status changed from New to Assigned")
758 assert mail.body.include?("Status changed from New to Assigned")
739 # subject should contain the new status
759 # subject should contain the new status
740 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
760 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
741 end
761 end
742
762
743 def test_post_edit_with_note_only
763 def test_post_edit_with_note_only
744 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
764 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
745 # anonymous user
765 # anonymous user
746 post :edit,
766 post :edit,
747 :id => 1,
767 :id => 1,
748 :notes => notes
768 :notes => notes
749 assert_redirected_to :action => 'show', :id => '1'
769 assert_redirected_to :action => 'show', :id => '1'
750 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
770 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
751 assert_equal notes, j.notes
771 assert_equal notes, j.notes
752 assert_equal 0, j.details.size
772 assert_equal 0, j.details.size
753 assert_equal User.anonymous, j.user
773 assert_equal User.anonymous, j.user
754
774
755 mail = ActionMailer::Base.deliveries.last
775 mail = ActionMailer::Base.deliveries.last
756 assert mail.body.include?(notes)
776 assert mail.body.include?(notes)
757 end
777 end
758
778
759 def test_post_edit_with_note_and_spent_time
779 def test_post_edit_with_note_and_spent_time
760 @request.session[:user_id] = 2
780 @request.session[:user_id] = 2
761 spent_hours_before = Issue.find(1).spent_hours
781 spent_hours_before = Issue.find(1).spent_hours
762 assert_difference('TimeEntry.count') do
782 assert_difference('TimeEntry.count') do
763 post :edit,
783 post :edit,
764 :id => 1,
784 :id => 1,
765 :notes => '2.5 hours added',
785 :notes => '2.5 hours added',
766 :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
786 :time_entry => { :hours => '2.5', :comments => '', :activity_id => TimeEntryActivity.first }
767 end
787 end
768 assert_redirected_to :action => 'show', :id => '1'
788 assert_redirected_to :action => 'show', :id => '1'
769
789
770 issue = Issue.find(1)
790 issue = Issue.find(1)
771
791
772 j = issue.journals.find(:first, :order => 'id DESC')
792 j = issue.journals.find(:first, :order => 'id DESC')
773 assert_equal '2.5 hours added', j.notes
793 assert_equal '2.5 hours added', j.notes
774 assert_equal 0, j.details.size
794 assert_equal 0, j.details.size
775
795
776 t = issue.time_entries.find(:first, :order => 'id DESC')
796 t = issue.time_entries.find(:first, :order => 'id DESC')
777 assert_not_nil t
797 assert_not_nil t
778 assert_equal 2.5, t.hours
798 assert_equal 2.5, t.hours
779 assert_equal spent_hours_before + 2.5, issue.spent_hours
799 assert_equal spent_hours_before + 2.5, issue.spent_hours
780 end
800 end
781
801
782 def test_post_edit_with_attachment_only
802 def test_post_edit_with_attachment_only
783 set_tmp_attachments_directory
803 set_tmp_attachments_directory
784
804
785 # Delete all fixtured journals, a race condition can occur causing the wrong
805 # Delete all fixtured journals, a race condition can occur causing the wrong
786 # journal to get fetched in the next find.
806 # journal to get fetched in the next find.
787 Journal.delete_all
807 Journal.delete_all
788
808
789 # anonymous user
809 # anonymous user
790 post :edit,
810 post :edit,
791 :id => 1,
811 :id => 1,
792 :notes => '',
812 :notes => '',
793 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
813 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
794 assert_redirected_to :action => 'show', :id => '1'
814 assert_redirected_to :action => 'show', :id => '1'
795 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
815 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
796 assert j.notes.blank?
816 assert j.notes.blank?
797 assert_equal 1, j.details.size
817 assert_equal 1, j.details.size
798 assert_equal 'testfile.txt', j.details.first.value
818 assert_equal 'testfile.txt', j.details.first.value
799 assert_equal User.anonymous, j.user
819 assert_equal User.anonymous, j.user
800
820
801 mail = ActionMailer::Base.deliveries.last
821 mail = ActionMailer::Base.deliveries.last
802 assert mail.body.include?('testfile.txt')
822 assert mail.body.include?('testfile.txt')
803 end
823 end
804
824
805 def test_post_edit_with_no_change
825 def test_post_edit_with_no_change
806 issue = Issue.find(1)
826 issue = Issue.find(1)
807 issue.journals.clear
827 issue.journals.clear
808 ActionMailer::Base.deliveries.clear
828 ActionMailer::Base.deliveries.clear
809
829
810 post :edit,
830 post :edit,
811 :id => 1,
831 :id => 1,
812 :notes => ''
832 :notes => ''
813 assert_redirected_to :action => 'show', :id => '1'
833 assert_redirected_to :action => 'show', :id => '1'
814
834
815 issue.reload
835 issue.reload
816 assert issue.journals.empty?
836 assert issue.journals.empty?
817 # No email should be sent
837 # No email should be sent
818 assert ActionMailer::Base.deliveries.empty?
838 assert ActionMailer::Base.deliveries.empty?
819 end
839 end
820
840
821 def test_post_edit_should_send_a_notification
841 def test_post_edit_should_send_a_notification
822 @request.session[:user_id] = 2
842 @request.session[:user_id] = 2
823 ActionMailer::Base.deliveries.clear
843 ActionMailer::Base.deliveries.clear
824 issue = Issue.find(1)
844 issue = Issue.find(1)
825 old_subject = issue.subject
845 old_subject = issue.subject
826 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
846 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
827
847
828 post :edit, :id => 1, :issue => {:subject => new_subject,
848 post :edit, :id => 1, :issue => {:subject => new_subject,
829 :priority_id => '6',
849 :priority_id => '6',
830 :category_id => '1' # no change
850 :category_id => '1' # no change
831 }
851 }
832 assert_equal 1, ActionMailer::Base.deliveries.size
852 assert_equal 1, ActionMailer::Base.deliveries.size
833 end
853 end
834
854
835 def test_post_edit_with_invalid_spent_time
855 def test_post_edit_with_invalid_spent_time
836 @request.session[:user_id] = 2
856 @request.session[:user_id] = 2
837 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
857 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
838
858
839 assert_no_difference('Journal.count') do
859 assert_no_difference('Journal.count') do
840 post :edit,
860 post :edit,
841 :id => 1,
861 :id => 1,
842 :notes => notes,
862 :notes => notes,
843 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
863 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
844 end
864 end
845 assert_response :success
865 assert_response :success
846 assert_template 'edit'
866 assert_template 'edit'
847
867
848 assert_tag :textarea, :attributes => { :name => 'notes' },
868 assert_tag :textarea, :attributes => { :name => 'notes' },
849 :content => notes
869 :content => notes
850 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
870 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
851 end
871 end
852
872
853 def test_get_bulk_edit
873 def test_get_bulk_edit
854 @request.session[:user_id] = 2
874 @request.session[:user_id] = 2
855 get :bulk_edit, :ids => [1, 2]
875 get :bulk_edit, :ids => [1, 2]
856 assert_response :success
876 assert_response :success
857 assert_template 'bulk_edit'
877 assert_template 'bulk_edit'
858 end
878 end
859
879
860 def test_bulk_edit
880 def test_bulk_edit
861 @request.session[:user_id] = 2
881 @request.session[:user_id] = 2
862 # update issues priority
882 # update issues priority
863 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
883 post :bulk_edit, :ids => [1, 2], :priority_id => 7,
864 :assigned_to_id => '',
884 :assigned_to_id => '',
865 :custom_field_values => {'2' => ''},
885 :custom_field_values => {'2' => ''},
866 :notes => 'Bulk editing'
886 :notes => 'Bulk editing'
867 assert_response 302
887 assert_response 302
868 # check that the issues were updated
888 # check that the issues were updated
869 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
889 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
870
890
871 issue = Issue.find(1)
891 issue = Issue.find(1)
872 journal = issue.journals.find(:first, :order => 'created_on DESC')
892 journal = issue.journals.find(:first, :order => 'created_on DESC')
873 assert_equal '125', issue.custom_value_for(2).value
893 assert_equal '125', issue.custom_value_for(2).value
874 assert_equal 'Bulk editing', journal.notes
894 assert_equal 'Bulk editing', journal.notes
875 assert_equal 1, journal.details.size
895 assert_equal 1, journal.details.size
876 end
896 end
877
897
878 def test_bullk_edit_should_send_a_notification
898 def test_bullk_edit_should_send_a_notification
879 @request.session[:user_id] = 2
899 @request.session[:user_id] = 2
880 ActionMailer::Base.deliveries.clear
900 ActionMailer::Base.deliveries.clear
881 post(:bulk_edit,
901 post(:bulk_edit,
882 {
902 {
883 :ids => [1, 2],
903 :ids => [1, 2],
884 :priority_id => 7,
904 :priority_id => 7,
885 :assigned_to_id => '',
905 :assigned_to_id => '',
886 :custom_field_values => {'2' => ''},
906 :custom_field_values => {'2' => ''},
887 :notes => 'Bulk editing'
907 :notes => 'Bulk editing'
888 })
908 })
889
909
890 assert_response 302
910 assert_response 302
891 assert_equal 2, ActionMailer::Base.deliveries.size
911 assert_equal 2, ActionMailer::Base.deliveries.size
892 end
912 end
893
913
894 def test_bulk_edit_status
914 def test_bulk_edit_status
895 @request.session[:user_id] = 2
915 @request.session[:user_id] = 2
896 # update issues priority
916 # update issues priority
897 post :bulk_edit, :ids => [1, 2], :priority_id => '',
917 post :bulk_edit, :ids => [1, 2], :priority_id => '',
898 :assigned_to_id => '',
918 :assigned_to_id => '',
899 :status_id => '5',
919 :status_id => '5',
900 :notes => 'Bulk editing status'
920 :notes => 'Bulk editing status'
901 assert_response 302
921 assert_response 302
902 issue = Issue.find(1)
922 issue = Issue.find(1)
903 assert issue.closed?
923 assert issue.closed?
904 end
924 end
905
925
906 def test_bulk_edit_custom_field
926 def test_bulk_edit_custom_field
907 @request.session[:user_id] = 2
927 @request.session[:user_id] = 2
908 # update issues priority
928 # update issues priority
909 post :bulk_edit, :ids => [1, 2], :priority_id => '',
929 post :bulk_edit, :ids => [1, 2], :priority_id => '',
910 :assigned_to_id => '',
930 :assigned_to_id => '',
911 :custom_field_values => {'2' => '777'},
931 :custom_field_values => {'2' => '777'},
912 :notes => 'Bulk editing custom field'
932 :notes => 'Bulk editing custom field'
913 assert_response 302
933 assert_response 302
914
934
915 issue = Issue.find(1)
935 issue = Issue.find(1)
916 journal = issue.journals.find(:first, :order => 'created_on DESC')
936 journal = issue.journals.find(:first, :order => 'created_on DESC')
917 assert_equal '777', issue.custom_value_for(2).value
937 assert_equal '777', issue.custom_value_for(2).value
918 assert_equal 1, journal.details.size
938 assert_equal 1, journal.details.size
919 assert_equal '125', journal.details.first.old_value
939 assert_equal '125', journal.details.first.old_value
920 assert_equal '777', journal.details.first.value
940 assert_equal '777', journal.details.first.value
921 end
941 end
922
942
923 def test_bulk_unassign
943 def test_bulk_unassign
924 assert_not_nil Issue.find(2).assigned_to
944 assert_not_nil Issue.find(2).assigned_to
925 @request.session[:user_id] = 2
945 @request.session[:user_id] = 2
926 # unassign issues
946 # unassign issues
927 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
947 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
928 assert_response 302
948 assert_response 302
929 # check that the issues were updated
949 # check that the issues were updated
930 assert_nil Issue.find(2).assigned_to
950 assert_nil Issue.find(2).assigned_to
931 end
951 end
932
952
933 def test_move_routing
953 def test_move_routing
934 assert_routing(
954 assert_routing(
935 {:method => :get, :path => '/issues/1/move'},
955 {:method => :get, :path => '/issues/1/move'},
936 :controller => 'issues', :action => 'move', :id => '1'
956 :controller => 'issues', :action => 'move', :id => '1'
937 )
957 )
938 assert_recognizes(
958 assert_recognizes(
939 {:controller => 'issues', :action => 'move', :id => '1'},
959 {:controller => 'issues', :action => 'move', :id => '1'},
940 {:method => :post, :path => '/issues/1/move'}
960 {:method => :post, :path => '/issues/1/move'}
941 )
961 )
942 end
962 end
943
963
944 def test_move_one_issue_to_another_project
964 def test_move_one_issue_to_another_project
945 @request.session[:user_id] = 2
965 @request.session[:user_id] = 2
946 post :move, :id => 1, :new_project_id => 2
966 post :move, :id => 1, :new_project_id => 2
947 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
967 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
948 assert_equal 2, Issue.find(1).project_id
968 assert_equal 2, Issue.find(1).project_id
949 end
969 end
950
970
951 def test_bulk_move_to_another_project
971 def test_bulk_move_to_another_project
952 @request.session[:user_id] = 2
972 @request.session[:user_id] = 2
953 post :move, :ids => [1, 2], :new_project_id => 2
973 post :move, :ids => [1, 2], :new_project_id => 2
954 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
974 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
955 # Issues moved to project 2
975 # Issues moved to project 2
956 assert_equal 2, Issue.find(1).project_id
976 assert_equal 2, Issue.find(1).project_id
957 assert_equal 2, Issue.find(2).project_id
977 assert_equal 2, Issue.find(2).project_id
958 # No tracker change
978 # No tracker change
959 assert_equal 1, Issue.find(1).tracker_id
979 assert_equal 1, Issue.find(1).tracker_id
960 assert_equal 2, Issue.find(2).tracker_id
980 assert_equal 2, Issue.find(2).tracker_id
961 end
981 end
962
982
963 def test_bulk_move_to_another_tracker
983 def test_bulk_move_to_another_tracker
964 @request.session[:user_id] = 2
984 @request.session[:user_id] = 2
965 post :move, :ids => [1, 2], :new_tracker_id => 2
985 post :move, :ids => [1, 2], :new_tracker_id => 2
966 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
986 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
967 assert_equal 2, Issue.find(1).tracker_id
987 assert_equal 2, Issue.find(1).tracker_id
968 assert_equal 2, Issue.find(2).tracker_id
988 assert_equal 2, Issue.find(2).tracker_id
969 end
989 end
970
990
971 def test_bulk_copy_to_another_project
991 def test_bulk_copy_to_another_project
972 @request.session[:user_id] = 2
992 @request.session[:user_id] = 2
973 assert_difference 'Issue.count', 2 do
993 assert_difference 'Issue.count', 2 do
974 assert_no_difference 'Project.find(1).issues.count' do
994 assert_no_difference 'Project.find(1).issues.count' do
975 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
995 post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}
976 end
996 end
977 end
997 end
978 assert_redirected_to 'projects/ecookbook/issues'
998 assert_redirected_to 'projects/ecookbook/issues'
979 end
999 end
980
1000
981 def test_context_menu_one_issue
1001 def test_context_menu_one_issue
982 @request.session[:user_id] = 2
1002 @request.session[:user_id] = 2
983 get :context_menu, :ids => [1]
1003 get :context_menu, :ids => [1]
984 assert_response :success
1004 assert_response :success
985 assert_template 'context_menu'
1005 assert_template 'context_menu'
986 assert_tag :tag => 'a', :content => 'Edit',
1006 assert_tag :tag => 'a', :content => 'Edit',
987 :attributes => { :href => '/issues/1/edit',
1007 :attributes => { :href => '/issues/1/edit',
988 :class => 'icon-edit' }
1008 :class => 'icon-edit' }
989 assert_tag :tag => 'a', :content => 'Closed',
1009 assert_tag :tag => 'a', :content => 'Closed',
990 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
1010 :attributes => { :href => '/issues/1/edit?issue%5Bstatus_id%5D=5',
991 :class => '' }
1011 :class => '' }
992 assert_tag :tag => 'a', :content => 'Immediate',
1012 assert_tag :tag => 'a', :content => 'Immediate',
993 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
1013 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
994 :class => '' }
1014 :class => '' }
995 assert_tag :tag => 'a', :content => 'Dave Lopper',
1015 assert_tag :tag => 'a', :content => 'Dave Lopper',
996 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
1016 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
997 :class => '' }
1017 :class => '' }
998 assert_tag :tag => 'a', :content => 'Copy',
1018 assert_tag :tag => 'a', :content => 'Copy',
999 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1019 :attributes => { :href => '/projects/ecookbook/issues/1/copy',
1000 :class => 'icon-copy' }
1020 :class => 'icon-copy' }
1001 assert_tag :tag => 'a', :content => 'Move',
1021 assert_tag :tag => 'a', :content => 'Move',
1002 :attributes => { :href => '/issues/move?ids%5B%5D=1',
1022 :attributes => { :href => '/issues/move?ids%5B%5D=1',
1003 :class => 'icon-move' }
1023 :class => 'icon-move' }
1004 assert_tag :tag => 'a', :content => 'Delete',
1024 assert_tag :tag => 'a', :content => 'Delete',
1005 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1025 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
1006 :class => 'icon-del' }
1026 :class => 'icon-del' }
1007 end
1027 end
1008
1028
1009 def test_context_menu_one_issue_by_anonymous
1029 def test_context_menu_one_issue_by_anonymous
1010 get :context_menu, :ids => [1]
1030 get :context_menu, :ids => [1]
1011 assert_response :success
1031 assert_response :success
1012 assert_template 'context_menu'
1032 assert_template 'context_menu'
1013 assert_tag :tag => 'a', :content => 'Delete',
1033 assert_tag :tag => 'a', :content => 'Delete',
1014 :attributes => { :href => '#',
1034 :attributes => { :href => '#',
1015 :class => 'icon-del disabled' }
1035 :class => 'icon-del disabled' }
1016 end
1036 end
1017
1037
1018 def test_context_menu_multiple_issues_of_same_project
1038 def test_context_menu_multiple_issues_of_same_project
1019 @request.session[:user_id] = 2
1039 @request.session[:user_id] = 2
1020 get :context_menu, :ids => [1, 2]
1040 get :context_menu, :ids => [1, 2]
1021 assert_response :success
1041 assert_response :success
1022 assert_template 'context_menu'
1042 assert_template 'context_menu'
1023 assert_tag :tag => 'a', :content => 'Edit',
1043 assert_tag :tag => 'a', :content => 'Edit',
1024 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1044 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
1025 :class => 'icon-edit' }
1045 :class => 'icon-edit' }
1026 assert_tag :tag => 'a', :content => 'Immediate',
1046 assert_tag :tag => 'a', :content => 'Immediate',
1027 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1047 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
1028 :class => '' }
1048 :class => '' }
1029 assert_tag :tag => 'a', :content => 'Dave Lopper',
1049 assert_tag :tag => 'a', :content => 'Dave Lopper',
1030 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1050 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
1031 :class => '' }
1051 :class => '' }
1032 assert_tag :tag => 'a', :content => 'Move',
1052 assert_tag :tag => 'a', :content => 'Move',
1033 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1053 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
1034 :class => 'icon-move' }
1054 :class => 'icon-move' }
1035 assert_tag :tag => 'a', :content => 'Delete',
1055 assert_tag :tag => 'a', :content => 'Delete',
1036 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1056 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
1037 :class => 'icon-del' }
1057 :class => 'icon-del' }
1038 end
1058 end
1039
1059
1040 def test_context_menu_multiple_issues_of_different_project
1060 def test_context_menu_multiple_issues_of_different_project
1041 @request.session[:user_id] = 2
1061 @request.session[:user_id] = 2
1042 get :context_menu, :ids => [1, 2, 4]
1062 get :context_menu, :ids => [1, 2, 4]
1043 assert_response :success
1063 assert_response :success
1044 assert_template 'context_menu'
1064 assert_template 'context_menu'
1045 assert_tag :tag => 'a', :content => 'Delete',
1065 assert_tag :tag => 'a', :content => 'Delete',
1046 :attributes => { :href => '#',
1066 :attributes => { :href => '#',
1047 :class => 'icon-del disabled' }
1067 :class => 'icon-del disabled' }
1048 end
1068 end
1049
1069
1050 def test_destroy_routing
1070 def test_destroy_routing
1051 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
1071 assert_recognizes( #TODO: use DELETE on issue URI (need to change forms)
1052 {:controller => 'issues', :action => 'destroy', :id => '1'},
1072 {:controller => 'issues', :action => 'destroy', :id => '1'},
1053 {:method => :post, :path => '/issues/1/destroy'}
1073 {:method => :post, :path => '/issues/1/destroy'}
1054 )
1074 )
1055 end
1075 end
1056
1076
1057 def test_destroy_issue_with_no_time_entries
1077 def test_destroy_issue_with_no_time_entries
1058 assert_nil TimeEntry.find_by_issue_id(2)
1078 assert_nil TimeEntry.find_by_issue_id(2)
1059 @request.session[:user_id] = 2
1079 @request.session[:user_id] = 2
1060 post :destroy, :id => 2
1080 post :destroy, :id => 2
1061 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1081 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1062 assert_nil Issue.find_by_id(2)
1082 assert_nil Issue.find_by_id(2)
1063 end
1083 end
1064
1084
1065 def test_destroy_issues_with_time_entries
1085 def test_destroy_issues_with_time_entries
1066 @request.session[:user_id] = 2
1086 @request.session[:user_id] = 2
1067 post :destroy, :ids => [1, 3]
1087 post :destroy, :ids => [1, 3]
1068 assert_response :success
1088 assert_response :success
1069 assert_template 'destroy'
1089 assert_template 'destroy'
1070 assert_not_nil assigns(:hours)
1090 assert_not_nil assigns(:hours)
1071 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1091 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1072 end
1092 end
1073
1093
1074 def test_destroy_issues_and_destroy_time_entries
1094 def test_destroy_issues_and_destroy_time_entries
1075 @request.session[:user_id] = 2
1095 @request.session[:user_id] = 2
1076 post :destroy, :ids => [1, 3], :todo => 'destroy'
1096 post :destroy, :ids => [1, 3], :todo => 'destroy'
1077 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1097 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1078 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1098 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1079 assert_nil TimeEntry.find_by_id([1, 2])
1099 assert_nil TimeEntry.find_by_id([1, 2])
1080 end
1100 end
1081
1101
1082 def test_destroy_issues_and_assign_time_entries_to_project
1102 def test_destroy_issues_and_assign_time_entries_to_project
1083 @request.session[:user_id] = 2
1103 @request.session[:user_id] = 2
1084 post :destroy, :ids => [1, 3], :todo => 'nullify'
1104 post :destroy, :ids => [1, 3], :todo => 'nullify'
1085 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1105 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1086 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1106 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1087 assert_nil TimeEntry.find(1).issue_id
1107 assert_nil TimeEntry.find(1).issue_id
1088 assert_nil TimeEntry.find(2).issue_id
1108 assert_nil TimeEntry.find(2).issue_id
1089 end
1109 end
1090
1110
1091 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1111 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1092 @request.session[:user_id] = 2
1112 @request.session[:user_id] = 2
1093 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1113 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1094 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1114 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1095 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1115 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1096 assert_equal 2, TimeEntry.find(1).issue_id
1116 assert_equal 2, TimeEntry.find(1).issue_id
1097 assert_equal 2, TimeEntry.find(2).issue_id
1117 assert_equal 2, TimeEntry.find(2).issue_id
1098 end
1118 end
1099
1119
1100 def test_default_search_scope
1120 def test_default_search_scope
1101 get :index
1121 get :index
1102 assert_tag :div, :attributes => {:id => 'quick-search'},
1122 assert_tag :div, :attributes => {:id => 'quick-search'},
1103 :child => {:tag => 'form',
1123 :child => {:tag => 'form',
1104 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1124 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1105 end
1125 end
1106 end
1126 end
@@ -1,352 +1,393
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class IssueTest < ActiveSupport::TestCase
20 class IssueTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :members, :member_roles,
21 fixtures :projects, :users, :members, :member_roles, :roles,
22 :trackers, :projects_trackers,
22 :trackers, :projects_trackers,
23 :versions,
23 :versions,
24 :issue_statuses, :issue_categories, :issue_relations, :workflows,
24 :issue_statuses, :issue_categories, :issue_relations, :workflows,
25 :enumerations,
25 :enumerations,
26 :issues,
26 :issues,
27 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
27 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
28 :time_entries
28 :time_entries
29
29
30 def test_create
30 def test_create
31 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
31 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :description => 'IssueTest#test_create', :estimated_hours => '1:30')
32 assert issue.save
32 assert issue.save
33 issue.reload
33 issue.reload
34 assert_equal 1.5, issue.estimated_hours
34 assert_equal 1.5, issue.estimated_hours
35 end
35 end
36
36
37 def test_create_minimal
37 def test_create_minimal
38 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create')
38 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create')
39 assert issue.save
39 assert issue.save
40 assert issue.description.nil?
40 assert issue.description.nil?
41 end
41 end
42
42
43 def test_create_with_required_custom_field
43 def test_create_with_required_custom_field
44 field = IssueCustomField.find_by_name('Database')
44 field = IssueCustomField.find_by_name('Database')
45 field.update_attribute(:is_required, true)
45 field.update_attribute(:is_required, true)
46
46
47 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
47 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
48 assert issue.available_custom_fields.include?(field)
48 assert issue.available_custom_fields.include?(field)
49 # No value for the custom field
49 # No value for the custom field
50 assert !issue.save
50 assert !issue.save
51 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
51 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
52 # Blank value
52 # Blank value
53 issue.custom_field_values = { field.id => '' }
53 issue.custom_field_values = { field.id => '' }
54 assert !issue.save
54 assert !issue.save
55 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
55 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
56 # Invalid value
56 # Invalid value
57 issue.custom_field_values = { field.id => 'SQLServer' }
57 issue.custom_field_values = { field.id => 'SQLServer' }
58 assert !issue.save
58 assert !issue.save
59 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
59 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
60 # Valid value
60 # Valid value
61 issue.custom_field_values = { field.id => 'PostgreSQL' }
61 issue.custom_field_values = { field.id => 'PostgreSQL' }
62 assert issue.save
62 assert issue.save
63 issue.reload
63 issue.reload
64 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
64 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
65 end
65 end
66
66
67 def test_visible_scope_for_anonymous
68 # Anonymous user should see issues of public projects only
69 issues = Issue.visible(User.anonymous).all
70 assert issues.any?
71 assert_nil issues.detect {|issue| !issue.project.is_public?}
72 # Anonymous user should not see issues without permission
73 Role.anonymous.remove_permission!(:view_issues)
74 issues = Issue.visible(User.anonymous).all
75 assert issues.empty?
76 end
77
78 def test_visible_scope_for_user
79 user = User.find(9)
80 assert user.projects.empty?
81 # Non member user should see issues of public projects only
82 issues = Issue.visible(user).all
83 assert issues.any?
84 assert_nil issues.detect {|issue| !issue.project.is_public?}
85 # Non member user should not see issues without permission
86 Role.non_member.remove_permission!(:view_issues)
87 user.reload
88 issues = Issue.visible(user).all
89 assert issues.empty?
90 # User should see issues of projects for which he has view_issues permissions only
91 Member.create!(:principal => user, :project_id => 2, :role_ids => [1])
92 user.reload
93 issues = Issue.visible(user).all
94 assert issues.any?
95 assert_nil issues.detect {|issue| issue.project_id != 2}
96 end
97
98 def test_visible_scope_for_admin
99 user = User.find(1)
100 user.members.each(&:destroy)
101 assert user.projects.empty?
102 issues = Issue.visible(user).all
103 assert issues.any?
104 # Admin should see issues on private projects that he does not belong to
105 assert issues.detect {|issue| !issue.project.is_public?}
106 end
107
67 def test_errors_full_messages_should_include_custom_fields_errors
108 def test_errors_full_messages_should_include_custom_fields_errors
68 field = IssueCustomField.find_by_name('Database')
109 field = IssueCustomField.find_by_name('Database')
69
110
70 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
111 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :subject => 'test_create', :description => 'IssueTest#test_create_with_required_custom_field')
71 assert issue.available_custom_fields.include?(field)
112 assert issue.available_custom_fields.include?(field)
72 # Invalid value
113 # Invalid value
73 issue.custom_field_values = { field.id => 'SQLServer' }
114 issue.custom_field_values = { field.id => 'SQLServer' }
74
115
75 assert !issue.valid?
116 assert !issue.valid?
76 assert_equal 1, issue.errors.full_messages.size
117 assert_equal 1, issue.errors.full_messages.size
77 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first
118 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}", issue.errors.full_messages.first
78 end
119 end
79
120
80 def test_update_issue_with_required_custom_field
121 def test_update_issue_with_required_custom_field
81 field = IssueCustomField.find_by_name('Database')
122 field = IssueCustomField.find_by_name('Database')
82 field.update_attribute(:is_required, true)
123 field.update_attribute(:is_required, true)
83
124
84 issue = Issue.find(1)
125 issue = Issue.find(1)
85 assert_nil issue.custom_value_for(field)
126 assert_nil issue.custom_value_for(field)
86 assert issue.available_custom_fields.include?(field)
127 assert issue.available_custom_fields.include?(field)
87 # No change to custom values, issue can be saved
128 # No change to custom values, issue can be saved
88 assert issue.save
129 assert issue.save
89 # Blank value
130 # Blank value
90 issue.custom_field_values = { field.id => '' }
131 issue.custom_field_values = { field.id => '' }
91 assert !issue.save
132 assert !issue.save
92 # Valid value
133 # Valid value
93 issue.custom_field_values = { field.id => 'PostgreSQL' }
134 issue.custom_field_values = { field.id => 'PostgreSQL' }
94 assert issue.save
135 assert issue.save
95 issue.reload
136 issue.reload
96 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
137 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
97 end
138 end
98
139
99 def test_should_not_update_attributes_if_custom_fields_validation_fails
140 def test_should_not_update_attributes_if_custom_fields_validation_fails
100 issue = Issue.find(1)
141 issue = Issue.find(1)
101 field = IssueCustomField.find_by_name('Database')
142 field = IssueCustomField.find_by_name('Database')
102 assert issue.available_custom_fields.include?(field)
143 assert issue.available_custom_fields.include?(field)
103
144
104 issue.custom_field_values = { field.id => 'Invalid' }
145 issue.custom_field_values = { field.id => 'Invalid' }
105 issue.subject = 'Should be not be saved'
146 issue.subject = 'Should be not be saved'
106 assert !issue.save
147 assert !issue.save
107
148
108 issue.reload
149 issue.reload
109 assert_equal "Can't print recipes", issue.subject
150 assert_equal "Can't print recipes", issue.subject
110 end
151 end
111
152
112 def test_should_not_recreate_custom_values_objects_on_update
153 def test_should_not_recreate_custom_values_objects_on_update
113 field = IssueCustomField.find_by_name('Database')
154 field = IssueCustomField.find_by_name('Database')
114
155
115 issue = Issue.find(1)
156 issue = Issue.find(1)
116 issue.custom_field_values = { field.id => 'PostgreSQL' }
157 issue.custom_field_values = { field.id => 'PostgreSQL' }
117 assert issue.save
158 assert issue.save
118 custom_value = issue.custom_value_for(field)
159 custom_value = issue.custom_value_for(field)
119 issue.reload
160 issue.reload
120 issue.custom_field_values = { field.id => 'MySQL' }
161 issue.custom_field_values = { field.id => 'MySQL' }
121 assert issue.save
162 assert issue.save
122 issue.reload
163 issue.reload
123 assert_equal custom_value.id, issue.custom_value_for(field).id
164 assert_equal custom_value.id, issue.custom_value_for(field).id
124 end
165 end
125
166
126 def test_category_based_assignment
167 def test_category_based_assignment
127 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
168 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
128 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
169 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
129 end
170 end
130
171
131 def test_copy
172 def test_copy
132 issue = Issue.new.copy_from(1)
173 issue = Issue.new.copy_from(1)
133 assert issue.save
174 assert issue.save
134 issue.reload
175 issue.reload
135 orig = Issue.find(1)
176 orig = Issue.find(1)
136 assert_equal orig.subject, issue.subject
177 assert_equal orig.subject, issue.subject
137 assert_equal orig.tracker, issue.tracker
178 assert_equal orig.tracker, issue.tracker
138 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
179 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
139 end
180 end
140
181
141 def test_should_close_duplicates
182 def test_should_close_duplicates
142 # Create 3 issues
183 # Create 3 issues
143 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
184 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
144 assert issue1.save
185 assert issue1.save
145 issue2 = issue1.clone
186 issue2 = issue1.clone
146 assert issue2.save
187 assert issue2.save
147 issue3 = issue1.clone
188 issue3 = issue1.clone
148 assert issue3.save
189 assert issue3.save
149
190
150 # 2 is a dupe of 1
191 # 2 is a dupe of 1
151 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
192 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
152 # And 3 is a dupe of 2
193 # And 3 is a dupe of 2
153 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
194 IssueRelation.create(:issue_from => issue3, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
154 # And 3 is a dupe of 1 (circular duplicates)
195 # And 3 is a dupe of 1 (circular duplicates)
155 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
196 IssueRelation.create(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
156
197
157 assert issue1.reload.duplicates.include?(issue2)
198 assert issue1.reload.duplicates.include?(issue2)
158
199
159 # Closing issue 1
200 # Closing issue 1
160 issue1.init_journal(User.find(:first), "Closing issue1")
201 issue1.init_journal(User.find(:first), "Closing issue1")
161 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
202 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
162 assert issue1.save
203 assert issue1.save
163 # 2 and 3 should be also closed
204 # 2 and 3 should be also closed
164 assert issue2.reload.closed?
205 assert issue2.reload.closed?
165 assert issue3.reload.closed?
206 assert issue3.reload.closed?
166 end
207 end
167
208
168 def test_should_not_close_duplicated_issue
209 def test_should_not_close_duplicated_issue
169 # Create 3 issues
210 # Create 3 issues
170 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
211 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'Duplicates test', :description => 'Duplicates test')
171 assert issue1.save
212 assert issue1.save
172 issue2 = issue1.clone
213 issue2 = issue1.clone
173 assert issue2.save
214 assert issue2.save
174
215
175 # 2 is a dupe of 1
216 # 2 is a dupe of 1
176 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
217 IssueRelation.create(:issue_from => issue2, :issue_to => issue1, :relation_type => IssueRelation::TYPE_DUPLICATES)
177 # 2 is a dup of 1 but 1 is not a duplicate of 2
218 # 2 is a dup of 1 but 1 is not a duplicate of 2
178 assert !issue2.reload.duplicates.include?(issue1)
219 assert !issue2.reload.duplicates.include?(issue1)
179
220
180 # Closing issue 2
221 # Closing issue 2
181 issue2.init_journal(User.find(:first), "Closing issue2")
222 issue2.init_journal(User.find(:first), "Closing issue2")
182 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
223 issue2.status = IssueStatus.find :first, :conditions => {:is_closed => true}
183 assert issue2.save
224 assert issue2.save
184 # 1 should not be also closed
225 # 1 should not be also closed
185 assert !issue1.reload.closed?
226 assert !issue1.reload.closed?
186 end
227 end
187
228
188 def test_assignable_versions
229 def test_assignable_versions
189 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
230 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
190 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
231 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
191 end
232 end
192
233
193 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
234 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
194 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
235 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1, :subject => 'New issue')
195 assert !issue.save
236 assert !issue.save
196 assert_not_nil issue.errors.on(:fixed_version_id)
237 assert_not_nil issue.errors.on(:fixed_version_id)
197 end
238 end
198
239
199 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
240 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
200 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
241 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 2, :subject => 'New issue')
201 assert !issue.save
242 assert !issue.save
202 assert_not_nil issue.errors.on(:fixed_version_id)
243 assert_not_nil issue.errors.on(:fixed_version_id)
203 end
244 end
204
245
205 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
246 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
206 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
247 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 3, :subject => 'New issue')
207 assert issue.save
248 assert issue.save
208 end
249 end
209
250
210 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
251 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
211 issue = Issue.find(11)
252 issue = Issue.find(11)
212 assert_equal 'closed', issue.fixed_version.status
253 assert_equal 'closed', issue.fixed_version.status
213 issue.subject = 'Subject changed'
254 issue.subject = 'Subject changed'
214 assert issue.save
255 assert issue.save
215 end
256 end
216
257
217 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
258 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
218 issue = Issue.find(11)
259 issue = Issue.find(11)
219 issue.status_id = 1
260 issue.status_id = 1
220 assert !issue.save
261 assert !issue.save
221 assert_not_nil issue.errors.on_base
262 assert_not_nil issue.errors.on_base
222 end
263 end
223
264
224 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
265 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
225 issue = Issue.find(11)
266 issue = Issue.find(11)
226 issue.status_id = 1
267 issue.status_id = 1
227 issue.fixed_version_id = 3
268 issue.fixed_version_id = 3
228 assert issue.save
269 assert issue.save
229 end
270 end
230
271
231 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
272 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
232 issue = Issue.find(12)
273 issue = Issue.find(12)
233 assert_equal 'locked', issue.fixed_version.status
274 assert_equal 'locked', issue.fixed_version.status
234 issue.status_id = 1
275 issue.status_id = 1
235 assert issue.save
276 assert issue.save
236 end
277 end
237
278
238 def test_move_to_another_project_with_same_category
279 def test_move_to_another_project_with_same_category
239 issue = Issue.find(1)
280 issue = Issue.find(1)
240 assert issue.move_to(Project.find(2))
281 assert issue.move_to(Project.find(2))
241 issue.reload
282 issue.reload
242 assert_equal 2, issue.project_id
283 assert_equal 2, issue.project_id
243 # Category changes
284 # Category changes
244 assert_equal 4, issue.category_id
285 assert_equal 4, issue.category_id
245 # Make sure time entries were move to the target project
286 # Make sure time entries were move to the target project
246 assert_equal 2, issue.time_entries.first.project_id
287 assert_equal 2, issue.time_entries.first.project_id
247 end
288 end
248
289
249 def test_move_to_another_project_without_same_category
290 def test_move_to_another_project_without_same_category
250 issue = Issue.find(2)
291 issue = Issue.find(2)
251 assert issue.move_to(Project.find(2))
292 assert issue.move_to(Project.find(2))
252 issue.reload
293 issue.reload
253 assert_equal 2, issue.project_id
294 assert_equal 2, issue.project_id
254 # Category cleared
295 # Category cleared
255 assert_nil issue.category_id
296 assert_nil issue.category_id
256 end
297 end
257
298
258 def test_copy_to_the_same_project
299 def test_copy_to_the_same_project
259 issue = Issue.find(1)
300 issue = Issue.find(1)
260 copy = nil
301 copy = nil
261 assert_difference 'Issue.count' do
302 assert_difference 'Issue.count' do
262 copy = issue.move_to(issue.project, nil, :copy => true)
303 copy = issue.move_to(issue.project, nil, :copy => true)
263 end
304 end
264 assert_kind_of Issue, copy
305 assert_kind_of Issue, copy
265 assert_equal issue.project, copy.project
306 assert_equal issue.project, copy.project
266 assert_equal "125", copy.custom_value_for(2).value
307 assert_equal "125", copy.custom_value_for(2).value
267 end
308 end
268
309
269 def test_copy_to_another_project_and_tracker
310 def test_copy_to_another_project_and_tracker
270 issue = Issue.find(1)
311 issue = Issue.find(1)
271 copy = nil
312 copy = nil
272 assert_difference 'Issue.count' do
313 assert_difference 'Issue.count' do
273 copy = issue.move_to(Project.find(3), Tracker.find(2), :copy => true)
314 copy = issue.move_to(Project.find(3), Tracker.find(2), :copy => true)
274 end
315 end
275 assert_kind_of Issue, copy
316 assert_kind_of Issue, copy
276 assert_equal Project.find(3), copy.project
317 assert_equal Project.find(3), copy.project
277 assert_equal Tracker.find(2), copy.tracker
318 assert_equal Tracker.find(2), copy.tracker
278 # Custom field #2 is not associated with target tracker
319 # Custom field #2 is not associated with target tracker
279 assert_nil copy.custom_value_for(2)
320 assert_nil copy.custom_value_for(2)
280 end
321 end
281
322
282 def test_issue_destroy
323 def test_issue_destroy
283 Issue.find(1).destroy
324 Issue.find(1).destroy
284 assert_nil Issue.find_by_id(1)
325 assert_nil Issue.find_by_id(1)
285 assert_nil TimeEntry.find_by_issue_id(1)
326 assert_nil TimeEntry.find_by_issue_id(1)
286 end
327 end
287
328
288 def test_blocked
329 def test_blocked
289 blocked_issue = Issue.find(9)
330 blocked_issue = Issue.find(9)
290 blocking_issue = Issue.find(10)
331 blocking_issue = Issue.find(10)
291
332
292 assert blocked_issue.blocked?
333 assert blocked_issue.blocked?
293 assert !blocking_issue.blocked?
334 assert !blocking_issue.blocked?
294 end
335 end
295
336
296 def test_blocked_issues_dont_allow_closed_statuses
337 def test_blocked_issues_dont_allow_closed_statuses
297 blocked_issue = Issue.find(9)
338 blocked_issue = Issue.find(9)
298
339
299 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
340 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
300 assert !allowed_statuses.empty?
341 assert !allowed_statuses.empty?
301 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
342 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
302 assert closed_statuses.empty?
343 assert closed_statuses.empty?
303 end
344 end
304
345
305 def test_unblocked_issues_allow_closed_statuses
346 def test_unblocked_issues_allow_closed_statuses
306 blocking_issue = Issue.find(10)
347 blocking_issue = Issue.find(10)
307
348
308 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
349 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
309 assert !allowed_statuses.empty?
350 assert !allowed_statuses.empty?
310 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
351 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
311 assert !closed_statuses.empty?
352 assert !closed_statuses.empty?
312 end
353 end
313
354
314 def test_overdue
355 def test_overdue
315 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
356 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
316 assert !Issue.new(:due_date => Date.today).overdue?
357 assert !Issue.new(:due_date => Date.today).overdue?
317 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
358 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
318 assert !Issue.new(:due_date => nil).overdue?
359 assert !Issue.new(:due_date => nil).overdue?
319 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
360 assert !Issue.new(:due_date => 1.day.ago.to_date, :status => IssueStatus.find(:first, :conditions => {:is_closed => true})).overdue?
320 end
361 end
321
362
322 def test_assignable_users
363 def test_assignable_users
323 assert_kind_of User, Issue.find(1).assignable_users.first
364 assert_kind_of User, Issue.find(1).assignable_users.first
324 end
365 end
325
366
326 def test_create_should_send_email_notification
367 def test_create_should_send_email_notification
327 ActionMailer::Base.deliveries.clear
368 ActionMailer::Base.deliveries.clear
328 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :estimated_hours => '1:30')
369 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => IssuePriority.all.first, :subject => 'test_create', :estimated_hours => '1:30')
329
370
330 assert issue.save
371 assert issue.save
331 assert_equal 1, ActionMailer::Base.deliveries.size
372 assert_equal 1, ActionMailer::Base.deliveries.size
332 end
373 end
333
374
334 def test_stale_issue_should_not_send_email_notification
375 def test_stale_issue_should_not_send_email_notification
335 ActionMailer::Base.deliveries.clear
376 ActionMailer::Base.deliveries.clear
336 issue = Issue.find(1)
377 issue = Issue.find(1)
337 stale = Issue.find(1)
378 stale = Issue.find(1)
338
379
339 issue.init_journal(User.find(1))
380 issue.init_journal(User.find(1))
340 issue.subject = 'Subjet update'
381 issue.subject = 'Subjet update'
341 assert issue.save
382 assert issue.save
342 assert_equal 1, ActionMailer::Base.deliveries.size
383 assert_equal 1, ActionMailer::Base.deliveries.size
343 ActionMailer::Base.deliveries.clear
384 ActionMailer::Base.deliveries.clear
344
385
345 stale.init_journal(User.find(1))
386 stale.init_journal(User.find(1))
346 stale.subject = 'Another subjet update'
387 stale.subject = 'Another subjet update'
347 assert_raise ActiveRecord::StaleObjectError do
388 assert_raise ActiveRecord::StaleObjectError do
348 stale.save
389 stale.save
349 end
390 end
350 assert ActionMailer::Base.deliveries.empty?
391 assert ActionMailer::Base.deliveries.empty?
351 end
392 end
352 end
393 end
General Comments 0
You need to be logged in to leave comments. Login now