##// END OF EJS Templates
Merged r2250 and r2251 from trunk....
Jean-Philippe Lang -
r2250:764393aa6ac2
parent child
Show More
@@ -1,195 +1,196
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 require 'csv'
18 require 'csv'
19
19
20 module IssuesHelper
20 module IssuesHelper
21 include ApplicationHelper
21 include ApplicationHelper
22
22
23 def render_issue_tooltip(issue)
23 def render_issue_tooltip(issue)
24 @cached_label_start_date ||= l(:field_start_date)
24 @cached_label_start_date ||= l(:field_start_date)
25 @cached_label_due_date ||= l(:field_due_date)
25 @cached_label_due_date ||= l(:field_due_date)
26 @cached_label_assigned_to ||= l(:field_assigned_to)
26 @cached_label_assigned_to ||= l(:field_assigned_to)
27 @cached_label_priority ||= l(:field_priority)
27 @cached_label_priority ||= l(:field_priority)
28
28
29 link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
29 link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
30 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
30 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
31 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
31 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
32 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
32 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
33 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
33 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
34 end
34 end
35
35
36 # Returns a string of css classes that apply to the given issue
36 # Returns a string of css classes that apply to the given issue
37 def css_issue_classes(issue)
37 def css_issue_classes(issue)
38 s = "issue status-#{issue.status.position} priority-#{issue.priority.position}"
38 s = "issue status-#{issue.status.position} priority-#{issue.priority.position}"
39 s << ' closed' if issue.closed?
39 s << ' overdue' if issue.overdue?
40 s << ' overdue' if issue.overdue?
40 s
41 s
41 end
42 end
42
43
43 def sidebar_queries
44 def sidebar_queries
44 unless @sidebar_queries
45 unless @sidebar_queries
45 # User can see public queries and his own queries
46 # User can see public queries and his own queries
46 visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
47 visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
47 # Project specific queries and global queries
48 # Project specific queries and global queries
48 visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
49 visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
49 @sidebar_queries = Query.find(:all,
50 @sidebar_queries = Query.find(:all,
50 :order => "name ASC",
51 :order => "name ASC",
51 :conditions => visible.conditions)
52 :conditions => visible.conditions)
52 end
53 end
53 @sidebar_queries
54 @sidebar_queries
54 end
55 end
55
56
56 def show_detail(detail, no_html=false)
57 def show_detail(detail, no_html=false)
57 case detail.property
58 case detail.property
58 when 'attr'
59 when 'attr'
59 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
60 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
60 case detail.prop_key
61 case detail.prop_key
61 when 'due_date', 'start_date'
62 when 'due_date', 'start_date'
62 value = format_date(detail.value.to_date) if detail.value
63 value = format_date(detail.value.to_date) if detail.value
63 old_value = format_date(detail.old_value.to_date) if detail.old_value
64 old_value = format_date(detail.old_value.to_date) if detail.old_value
64 when 'project_id'
65 when 'project_id'
65 p = Project.find_by_id(detail.value) and value = p.name if detail.value
66 p = Project.find_by_id(detail.value) and value = p.name if detail.value
66 p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value
67 p = Project.find_by_id(detail.old_value) and old_value = p.name if detail.old_value
67 when 'status_id'
68 when 'status_id'
68 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
69 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
69 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
70 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
70 when 'tracker_id'
71 when 'tracker_id'
71 t = Tracker.find_by_id(detail.value) and value = t.name if detail.value
72 t = Tracker.find_by_id(detail.value) and value = t.name if detail.value
72 t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value
73 t = Tracker.find_by_id(detail.old_value) and old_value = t.name if detail.old_value
73 when 'assigned_to_id'
74 when 'assigned_to_id'
74 u = User.find_by_id(detail.value) and value = u.name if detail.value
75 u = User.find_by_id(detail.value) and value = u.name if detail.value
75 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
76 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
76 when 'priority_id'
77 when 'priority_id'
77 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
78 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
78 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
79 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
79 when 'category_id'
80 when 'category_id'
80 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
81 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
81 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
82 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
82 when 'fixed_version_id'
83 when 'fixed_version_id'
83 v = Version.find_by_id(detail.value) and value = v.name if detail.value
84 v = Version.find_by_id(detail.value) and value = v.name if detail.value
84 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
85 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
85 when 'estimated_hours'
86 when 'estimated_hours'
86 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
87 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
87 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
88 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
88 end
89 end
89 when 'cf'
90 when 'cf'
90 custom_field = CustomField.find_by_id(detail.prop_key)
91 custom_field = CustomField.find_by_id(detail.prop_key)
91 if custom_field
92 if custom_field
92 label = custom_field.name
93 label = custom_field.name
93 value = format_value(detail.value, custom_field.field_format) if detail.value
94 value = format_value(detail.value, custom_field.field_format) if detail.value
94 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
95 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
95 end
96 end
96 when 'attachment'
97 when 'attachment'
97 label = l(:label_attachment)
98 label = l(:label_attachment)
98 end
99 end
99 call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
100 call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
100
101
101 label ||= detail.prop_key
102 label ||= detail.prop_key
102 value ||= detail.value
103 value ||= detail.value
103 old_value ||= detail.old_value
104 old_value ||= detail.old_value
104
105
105 unless no_html
106 unless no_html
106 label = content_tag('strong', label)
107 label = content_tag('strong', label)
107 old_value = content_tag("i", h(old_value)) if detail.old_value
108 old_value = content_tag("i", h(old_value)) if detail.old_value
108 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
109 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
109 if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
110 if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
110 # Link to the attachment if it has not been removed
111 # Link to the attachment if it has not been removed
111 value = link_to_attachment(a)
112 value = link_to_attachment(a)
112 else
113 else
113 value = content_tag("i", h(value)) if value
114 value = content_tag("i", h(value)) if value
114 end
115 end
115 end
116 end
116
117
117 if !detail.value.blank?
118 if !detail.value.blank?
118 case detail.property
119 case detail.property
119 when 'attr', 'cf'
120 when 'attr', 'cf'
120 if !detail.old_value.blank?
121 if !detail.old_value.blank?
121 label + " " + l(:text_journal_changed, old_value, value)
122 label + " " + l(:text_journal_changed, old_value, value)
122 else
123 else
123 label + " " + l(:text_journal_set_to, value)
124 label + " " + l(:text_journal_set_to, value)
124 end
125 end
125 when 'attachment'
126 when 'attachment'
126 "#{label} #{value} #{l(:label_added)}"
127 "#{label} #{value} #{l(:label_added)}"
127 end
128 end
128 else
129 else
129 case detail.property
130 case detail.property
130 when 'attr', 'cf'
131 when 'attr', 'cf'
131 label + " " + l(:text_journal_deleted) + " (#{old_value})"
132 label + " " + l(:text_journal_deleted) + " (#{old_value})"
132 when 'attachment'
133 when 'attachment'
133 "#{label} #{old_value} #{l(:label_deleted)}"
134 "#{label} #{old_value} #{l(:label_deleted)}"
134 end
135 end
135 end
136 end
136 end
137 end
137
138
138 def issues_to_csv(issues, project = nil)
139 def issues_to_csv(issues, project = nil)
139 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
140 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
140 decimal_separator = l(:general_csv_decimal_separator)
141 decimal_separator = l(:general_csv_decimal_separator)
141 export = StringIO.new
142 export = StringIO.new
142 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
143 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
143 # csv header fields
144 # csv header fields
144 headers = [ "#",
145 headers = [ "#",
145 l(:field_status),
146 l(:field_status),
146 l(:field_project),
147 l(:field_project),
147 l(:field_tracker),
148 l(:field_tracker),
148 l(:field_priority),
149 l(:field_priority),
149 l(:field_subject),
150 l(:field_subject),
150 l(:field_assigned_to),
151 l(:field_assigned_to),
151 l(:field_category),
152 l(:field_category),
152 l(:field_fixed_version),
153 l(:field_fixed_version),
153 l(:field_author),
154 l(:field_author),
154 l(:field_start_date),
155 l(:field_start_date),
155 l(:field_due_date),
156 l(:field_due_date),
156 l(:field_done_ratio),
157 l(:field_done_ratio),
157 l(:field_estimated_hours),
158 l(:field_estimated_hours),
158 l(:field_created_on),
159 l(:field_created_on),
159 l(:field_updated_on)
160 l(:field_updated_on)
160 ]
161 ]
161 # Export project custom fields if project is given
162 # Export project custom fields if project is given
162 # otherwise export custom fields marked as "For all projects"
163 # otherwise export custom fields marked as "For all projects"
163 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
164 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
164 custom_fields.each {|f| headers << f.name}
165 custom_fields.each {|f| headers << f.name}
165 # Description in the last column
166 # Description in the last column
166 headers << l(:field_description)
167 headers << l(:field_description)
167 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
168 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
168 # csv lines
169 # csv lines
169 issues.each do |issue|
170 issues.each do |issue|
170 fields = [issue.id,
171 fields = [issue.id,
171 issue.status.name,
172 issue.status.name,
172 issue.project.name,
173 issue.project.name,
173 issue.tracker.name,
174 issue.tracker.name,
174 issue.priority.name,
175 issue.priority.name,
175 issue.subject,
176 issue.subject,
176 issue.assigned_to,
177 issue.assigned_to,
177 issue.category,
178 issue.category,
178 issue.fixed_version,
179 issue.fixed_version,
179 issue.author.name,
180 issue.author.name,
180 format_date(issue.start_date),
181 format_date(issue.start_date),
181 format_date(issue.due_date),
182 format_date(issue.due_date),
182 issue.done_ratio,
183 issue.done_ratio,
183 issue.estimated_hours.to_s.gsub('.', decimal_separator),
184 issue.estimated_hours.to_s.gsub('.', decimal_separator),
184 format_time(issue.created_on),
185 format_time(issue.created_on),
185 format_time(issue.updated_on)
186 format_time(issue.updated_on)
186 ]
187 ]
187 custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
188 custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
188 fields << issue.description
189 fields << issue.description
189 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
190 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
190 end
191 end
191 end
192 end
192 export.rewind
193 export.rewind
193 export
194 export
194 end
195 end
195 end
196 end
@@ -1,79 +1,79
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 class TimeEntry < ActiveRecord::Base
18 class TimeEntry < ActiveRecord::Base
19 # could have used polymorphic association
19 # could have used polymorphic association
20 # project association here allows easy loading of time entries at project level with one database trip
20 # project association here allows easy loading of time entries at project level with one database trip
21 belongs_to :project
21 belongs_to :project
22 belongs_to :issue
22 belongs_to :issue
23 belongs_to :user
23 belongs_to :user
24 belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id
24 belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id
25
25
26 attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
26 attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
27
27
28 acts_as_customizable
28 acts_as_customizable
29 acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"},
29 acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"},
30 :url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}},
30 :url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}},
31 :author => :user,
31 :author => :user,
32 :description => :comments
32 :description => :comments
33
33
34 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
34 validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
35 validates_numericality_of :hours, :allow_nil => true
35 validates_numericality_of :hours, :allow_nil => true, :message => :activerecord_error_invalid
36 validates_length_of :comments, :maximum => 255, :allow_nil => true
36 validates_length_of :comments, :maximum => 255, :allow_nil => true
37
37
38 def after_initialize
38 def after_initialize
39 if new_record? && self.activity.nil?
39 if new_record? && self.activity.nil?
40 if default_activity = Enumeration.default('ACTI')
40 if default_activity = Enumeration.default('ACTI')
41 self.activity_id = default_activity.id
41 self.activity_id = default_activity.id
42 end
42 end
43 end
43 end
44 end
44 end
45
45
46 def before_validation
46 def before_validation
47 self.project = issue.project if issue && project.nil?
47 self.project = issue.project if issue && project.nil?
48 end
48 end
49
49
50 def validate
50 def validate
51 errors.add :hours, :activerecord_error_invalid if hours && (hours < 0 || hours >= 1000)
51 errors.add :hours, :activerecord_error_invalid if hours && (hours < 0 || hours >= 1000)
52 errors.add :project_id, :activerecord_error_invalid if project.nil?
52 errors.add :project_id, :activerecord_error_invalid if project.nil?
53 errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project)
53 errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project)
54 end
54 end
55
55
56 def hours=(h)
56 def hours=(h)
57 write_attribute :hours, (h.is_a?(String) ? h.to_hours : h)
57 write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
58 end
58 end
59
59
60 # tyear, tmonth, tweek assigned where setting spent_on attributes
60 # tyear, tmonth, tweek assigned where setting spent_on attributes
61 # these attributes make time aggregations easier
61 # these attributes make time aggregations easier
62 def spent_on=(date)
62 def spent_on=(date)
63 super
63 super
64 self.tyear = spent_on ? spent_on.year : nil
64 self.tyear = spent_on ? spent_on.year : nil
65 self.tmonth = spent_on ? spent_on.month : nil
65 self.tmonth = spent_on ? spent_on.month : nil
66 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
66 self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
67 end
67 end
68
68
69 # Returns true if the time entry can be edited by usr, otherwise false
69 # Returns true if the time entry can be edited by usr, otherwise false
70 def editable_by?(usr)
70 def editable_by?(usr)
71 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
71 (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
72 end
72 end
73
73
74 def self.visible_by(usr)
74 def self.visible_by(usr)
75 with_scope(:find => { :conditions => Project.allowed_to_condition(usr, :view_time_entries) }) do
75 with_scope(:find => { :conditions => Project.allowed_to_condition(usr, :view_time_entries) }) do
76 yield
76 yield
77 end
77 end
78 end
78 end
79 end
79 end
@@ -1,40 +1,42
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2008 Jean-Philippe Lang
2 # Copyright (C) 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 module Redmine #:nodoc:
18 module Redmine #:nodoc:
19 module CoreExtensions #:nodoc:
19 module CoreExtensions #:nodoc:
20 module String #:nodoc:
20 module String #:nodoc:
21 # Custom string conversions
21 # Custom string conversions
22 module Conversions
22 module Conversions
23 # Parses hours format and returns a float
23 # Parses hours format and returns a float
24 def to_hours
24 def to_hours
25 s = self.dup
25 s = self.dup
26 s.strip!
26 s.strip!
27 unless s =~ %r{^[\d\.,]+$}
27 if s =~ %r{^(\d+([.,]\d+)?)h?$}
28 s = $1
29 else
28 # 2:30 => 2.5
30 # 2:30 => 2.5
29 s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 }
31 s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 }
30 # 2h30, 2h, 30m => 2.5, 2, 0.5
32 # 2h30, 2h, 30m => 2.5, 2, 0.5
31 s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] }
33 s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] }
32 end
34 end
33 # 2,5 => 2.5
35 # 2,5 => 2.5
34 s.gsub!(',', '.')
36 s.gsub!(',', '.')
35 begin; Kernel.Float(s); rescue; nil; end
37 begin; Kernel.Float(s); rescue; nil; end
36 end
38 end
37 end
39 end
38 end
40 end
39 end
41 end
40 end
42 end
@@ -1,758 +1,776
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 < Test::Unit::TestCase
24 class IssuesControllerTest < Test::Unit::TestCase
25 fixtures :projects,
25 fixtures :projects,
26 :users,
26 :users,
27 :roles,
27 :roles,
28 :members,
28 :members,
29 :issues,
29 :issues,
30 :issue_statuses,
30 :issue_statuses,
31 :versions,
31 :versions,
32 :trackers,
32 :trackers,
33 :projects_trackers,
33 :projects_trackers,
34 :issue_categories,
34 :issue_categories,
35 :enabled_modules,
35 :enabled_modules,
36 :enumerations,
36 :enumerations,
37 :attachments,
37 :attachments,
38 :workflows,
38 :workflows,
39 :custom_fields,
39 :custom_fields,
40 :custom_values,
40 :custom_values,
41 :custom_fields_trackers,
41 :custom_fields_trackers,
42 :time_entries,
42 :time_entries,
43 :journals,
43 :journals,
44 :journal_details
44 :journal_details
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index
53 def test_index
54 get :index
54 get :index
55 assert_response :success
55 assert_response :success
56 assert_template 'index.rhtml'
56 assert_template 'index.rhtml'
57 assert_not_nil assigns(:issues)
57 assert_not_nil assigns(:issues)
58 assert_nil assigns(:project)
58 assert_nil assigns(:project)
59 assert_tag :tag => 'a', :content => /Can't print recipes/
59 assert_tag :tag => 'a', :content => /Can't print recipes/
60 assert_tag :tag => 'a', :content => /Subproject issue/
60 assert_tag :tag => 'a', :content => /Subproject issue/
61 # private projects hidden
61 # private projects hidden
62 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
62 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
63 assert_no_tag :tag => 'a', :content => /Issue on project 2/
63 assert_no_tag :tag => 'a', :content => /Issue on project 2/
64 end
64 end
65
65
66 def test_index_should_not_list_issues_when_module_disabled
66 def test_index_should_not_list_issues_when_module_disabled
67 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
67 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
68 get :index
68 get :index
69 assert_response :success
69 assert_response :success
70 assert_template 'index.rhtml'
70 assert_template 'index.rhtml'
71 assert_not_nil assigns(:issues)
71 assert_not_nil assigns(:issues)
72 assert_nil assigns(:project)
72 assert_nil assigns(:project)
73 assert_no_tag :tag => 'a', :content => /Can't print recipes/
73 assert_no_tag :tag => 'a', :content => /Can't print recipes/
74 assert_tag :tag => 'a', :content => /Subproject issue/
74 assert_tag :tag => 'a', :content => /Subproject issue/
75 end
75 end
76
76
77 def test_index_with_project
77 def test_index_with_project
78 Setting.display_subprojects_issues = 0
78 Setting.display_subprojects_issues = 0
79 get :index, :project_id => 1
79 get :index, :project_id => 1
80 assert_response :success
80 assert_response :success
81 assert_template 'index.rhtml'
81 assert_template 'index.rhtml'
82 assert_not_nil assigns(:issues)
82 assert_not_nil assigns(:issues)
83 assert_tag :tag => 'a', :content => /Can't print recipes/
83 assert_tag :tag => 'a', :content => /Can't print recipes/
84 assert_no_tag :tag => 'a', :content => /Subproject issue/
84 assert_no_tag :tag => 'a', :content => /Subproject issue/
85 end
85 end
86
86
87 def test_index_with_project_and_subprojects
87 def test_index_with_project_and_subprojects
88 Setting.display_subprojects_issues = 1
88 Setting.display_subprojects_issues = 1
89 get :index, :project_id => 1
89 get :index, :project_id => 1
90 assert_response :success
90 assert_response :success
91 assert_template 'index.rhtml'
91 assert_template 'index.rhtml'
92 assert_not_nil assigns(:issues)
92 assert_not_nil assigns(:issues)
93 assert_tag :tag => 'a', :content => /Can't print recipes/
93 assert_tag :tag => 'a', :content => /Can't print recipes/
94 assert_tag :tag => 'a', :content => /Subproject issue/
94 assert_tag :tag => 'a', :content => /Subproject issue/
95 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
95 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
96 end
96 end
97
97
98 def test_index_with_project_and_subprojects_should_show_private_subprojects
98 def test_index_with_project_and_subprojects_should_show_private_subprojects
99 @request.session[:user_id] = 2
99 @request.session[:user_id] = 2
100 Setting.display_subprojects_issues = 1
100 Setting.display_subprojects_issues = 1
101 get :index, :project_id => 1
101 get :index, :project_id => 1
102 assert_response :success
102 assert_response :success
103 assert_template 'index.rhtml'
103 assert_template 'index.rhtml'
104 assert_not_nil assigns(:issues)
104 assert_not_nil assigns(:issues)
105 assert_tag :tag => 'a', :content => /Can't print recipes/
105 assert_tag :tag => 'a', :content => /Can't print recipes/
106 assert_tag :tag => 'a', :content => /Subproject issue/
106 assert_tag :tag => 'a', :content => /Subproject issue/
107 assert_tag :tag => 'a', :content => /Issue of a private subproject/
107 assert_tag :tag => 'a', :content => /Issue of a private subproject/
108 end
108 end
109
109
110 def test_index_with_project_and_filter
110 def test_index_with_project_and_filter
111 get :index, :project_id => 1, :set_filter => 1
111 get :index, :project_id => 1, :set_filter => 1
112 assert_response :success
112 assert_response :success
113 assert_template 'index.rhtml'
113 assert_template 'index.rhtml'
114 assert_not_nil assigns(:issues)
114 assert_not_nil assigns(:issues)
115 end
115 end
116
116
117 def test_index_csv_with_project
117 def test_index_csv_with_project
118 get :index, :format => 'csv'
118 get :index, :format => 'csv'
119 assert_response :success
119 assert_response :success
120 assert_not_nil assigns(:issues)
120 assert_not_nil assigns(:issues)
121 assert_equal 'text/csv', @response.content_type
121 assert_equal 'text/csv', @response.content_type
122
122
123 get :index, :project_id => 1, :format => 'csv'
123 get :index, :project_id => 1, :format => 'csv'
124 assert_response :success
124 assert_response :success
125 assert_not_nil assigns(:issues)
125 assert_not_nil assigns(:issues)
126 assert_equal 'text/csv', @response.content_type
126 assert_equal 'text/csv', @response.content_type
127 end
127 end
128
128
129 def test_index_pdf
129 def test_index_pdf
130 get :index, :format => 'pdf'
130 get :index, :format => 'pdf'
131 assert_response :success
131 assert_response :success
132 assert_not_nil assigns(:issues)
132 assert_not_nil assigns(:issues)
133 assert_equal 'application/pdf', @response.content_type
133 assert_equal 'application/pdf', @response.content_type
134
134
135 get :index, :project_id => 1, :format => 'pdf'
135 get :index, :project_id => 1, :format => 'pdf'
136 assert_response :success
136 assert_response :success
137 assert_not_nil assigns(:issues)
137 assert_not_nil assigns(:issues)
138 assert_equal 'application/pdf', @response.content_type
138 assert_equal 'application/pdf', @response.content_type
139 end
139 end
140
140
141 def test_index_sort
141 def test_index_sort
142 get :index, :sort_key => 'tracker'
142 get :index, :sort_key => 'tracker'
143 assert_response :success
143 assert_response :success
144
144
145 sort_params = @request.session['issuesindex_sort']
145 sort_params = @request.session['issuesindex_sort']
146 assert sort_params.is_a?(Hash)
146 assert sort_params.is_a?(Hash)
147 assert_equal 'tracker', sort_params[:key]
147 assert_equal 'tracker', sort_params[:key]
148 assert_equal 'ASC', sort_params[:order]
148 assert_equal 'ASC', sort_params[:order]
149 end
149 end
150
150
151 def test_gantt
151 def test_gantt
152 get :gantt, :project_id => 1
152 get :gantt, :project_id => 1
153 assert_response :success
153 assert_response :success
154 assert_template 'gantt.rhtml'
154 assert_template 'gantt.rhtml'
155 assert_not_nil assigns(:gantt)
155 assert_not_nil assigns(:gantt)
156 events = assigns(:gantt).events
156 events = assigns(:gantt).events
157 assert_not_nil events
157 assert_not_nil events
158 # Issue with start and due dates
158 # Issue with start and due dates
159 i = Issue.find(1)
159 i = Issue.find(1)
160 assert_not_nil i.due_date
160 assert_not_nil i.due_date
161 assert events.include?(Issue.find(1))
161 assert events.include?(Issue.find(1))
162 # Issue with without due date but targeted to a version with date
162 # Issue with without due date but targeted to a version with date
163 i = Issue.find(2)
163 i = Issue.find(2)
164 assert_nil i.due_date
164 assert_nil i.due_date
165 assert events.include?(i)
165 assert events.include?(i)
166 end
166 end
167
167
168 def test_cross_project_gantt
168 def test_cross_project_gantt
169 get :gantt
169 get :gantt
170 assert_response :success
170 assert_response :success
171 assert_template 'gantt.rhtml'
171 assert_template 'gantt.rhtml'
172 assert_not_nil assigns(:gantt)
172 assert_not_nil assigns(:gantt)
173 events = assigns(:gantt).events
173 events = assigns(:gantt).events
174 assert_not_nil events
174 assert_not_nil events
175 end
175 end
176
176
177 def test_gantt_export_to_pdf
177 def test_gantt_export_to_pdf
178 get :gantt, :project_id => 1, :format => 'pdf'
178 get :gantt, :project_id => 1, :format => 'pdf'
179 assert_response :success
179 assert_response :success
180 assert_equal 'application/pdf', @response.content_type
180 assert_equal 'application/pdf', @response.content_type
181 assert @response.body.starts_with?('%PDF')
181 assert @response.body.starts_with?('%PDF')
182 assert_not_nil assigns(:gantt)
182 assert_not_nil assigns(:gantt)
183 end
183 end
184
184
185 def test_cross_project_gantt_export_to_pdf
185 def test_cross_project_gantt_export_to_pdf
186 get :gantt, :format => 'pdf'
186 get :gantt, :format => 'pdf'
187 assert_response :success
187 assert_response :success
188 assert_equal 'application/pdf', @response.content_type
188 assert_equal 'application/pdf', @response.content_type
189 assert @response.body.starts_with?('%PDF')
189 assert @response.body.starts_with?('%PDF')
190 assert_not_nil assigns(:gantt)
190 assert_not_nil assigns(:gantt)
191 end
191 end
192
192
193 if Object.const_defined?(:Magick)
193 if Object.const_defined?(:Magick)
194 def test_gantt_image
194 def test_gantt_image
195 get :gantt, :project_id => 1, :format => 'png'
195 get :gantt, :project_id => 1, :format => 'png'
196 assert_response :success
196 assert_response :success
197 assert_equal 'image/png', @response.content_type
197 assert_equal 'image/png', @response.content_type
198 end
198 end
199 else
199 else
200 puts "RMagick not installed. Skipping tests !!!"
200 puts "RMagick not installed. Skipping tests !!!"
201 end
201 end
202
202
203 def test_calendar
203 def test_calendar
204 get :calendar, :project_id => 1
204 get :calendar, :project_id => 1
205 assert_response :success
205 assert_response :success
206 assert_template 'calendar'
206 assert_template 'calendar'
207 assert_not_nil assigns(:calendar)
207 assert_not_nil assigns(:calendar)
208 end
208 end
209
209
210 def test_cross_project_calendar
210 def test_cross_project_calendar
211 get :calendar
211 get :calendar
212 assert_response :success
212 assert_response :success
213 assert_template 'calendar'
213 assert_template 'calendar'
214 assert_not_nil assigns(:calendar)
214 assert_not_nil assigns(:calendar)
215 end
215 end
216
216
217 def test_changes
217 def test_changes
218 get :changes, :project_id => 1
218 get :changes, :project_id => 1
219 assert_response :success
219 assert_response :success
220 assert_not_nil assigns(:journals)
220 assert_not_nil assigns(:journals)
221 assert_equal 'application/atom+xml', @response.content_type
221 assert_equal 'application/atom+xml', @response.content_type
222 end
222 end
223
223
224 def test_show_by_anonymous
224 def test_show_by_anonymous
225 get :show, :id => 1
225 get :show, :id => 1
226 assert_response :success
226 assert_response :success
227 assert_template 'show.rhtml'
227 assert_template 'show.rhtml'
228 assert_not_nil assigns(:issue)
228 assert_not_nil assigns(:issue)
229 assert_equal Issue.find(1), assigns(:issue)
229 assert_equal Issue.find(1), assigns(:issue)
230
230
231 # anonymous role is allowed to add a note
231 # anonymous role is allowed to add a note
232 assert_tag :tag => 'form',
232 assert_tag :tag => 'form',
233 :descendant => { :tag => 'fieldset',
233 :descendant => { :tag => 'fieldset',
234 :child => { :tag => 'legend',
234 :child => { :tag => 'legend',
235 :content => /Notes/ } }
235 :content => /Notes/ } }
236 end
236 end
237
237
238 def test_show_by_manager
238 def test_show_by_manager
239 @request.session[:user_id] = 2
239 @request.session[:user_id] = 2
240 get :show, :id => 1
240 get :show, :id => 1
241 assert_response :success
241 assert_response :success
242
242
243 assert_tag :tag => 'form',
243 assert_tag :tag => 'form',
244 :descendant => { :tag => 'fieldset',
244 :descendant => { :tag => 'fieldset',
245 :child => { :tag => 'legend',
245 :child => { :tag => 'legend',
246 :content => /Change properties/ } },
246 :content => /Change properties/ } },
247 :descendant => { :tag => 'fieldset',
247 :descendant => { :tag => 'fieldset',
248 :child => { :tag => 'legend',
248 :child => { :tag => 'legend',
249 :content => /Log time/ } },
249 :content => /Log time/ } },
250 :descendant => { :tag => 'fieldset',
250 :descendant => { :tag => 'fieldset',
251 :child => { :tag => 'legend',
251 :child => { :tag => 'legend',
252 :content => /Notes/ } }
252 :content => /Notes/ } }
253 end
253 end
254
254
255 def test_show_export_to_pdf
255 def test_show_export_to_pdf
256 get :show, :id => 1, :format => 'pdf'
256 get :show, :id => 1, :format => 'pdf'
257 assert_response :success
257 assert_response :success
258 assert_equal 'application/pdf', @response.content_type
258 assert_equal 'application/pdf', @response.content_type
259 assert @response.body.starts_with?('%PDF')
259 assert @response.body.starts_with?('%PDF')
260 assert_not_nil assigns(:issue)
260 assert_not_nil assigns(:issue)
261 end
261 end
262
262
263 def test_get_new
263 def test_get_new
264 @request.session[:user_id] = 2
264 @request.session[:user_id] = 2
265 get :new, :project_id => 1, :tracker_id => 1
265 get :new, :project_id => 1, :tracker_id => 1
266 assert_response :success
266 assert_response :success
267 assert_template 'new'
267 assert_template 'new'
268
268
269 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
269 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
270 :value => 'Default string' }
270 :value => 'Default string' }
271 end
271 end
272
272
273 def test_get_new_without_tracker_id
273 def test_get_new_without_tracker_id
274 @request.session[:user_id] = 2
274 @request.session[:user_id] = 2
275 get :new, :project_id => 1
275 get :new, :project_id => 1
276 assert_response :success
276 assert_response :success
277 assert_template 'new'
277 assert_template 'new'
278
278
279 issue = assigns(:issue)
279 issue = assigns(:issue)
280 assert_not_nil issue
280 assert_not_nil issue
281 assert_equal Project.find(1).trackers.first, issue.tracker
281 assert_equal Project.find(1).trackers.first, issue.tracker
282 end
282 end
283
283
284 def test_update_new_form
284 def test_update_new_form
285 @request.session[:user_id] = 2
285 @request.session[:user_id] = 2
286 xhr :post, :new, :project_id => 1,
286 xhr :post, :new, :project_id => 1,
287 :issue => {:tracker_id => 2,
287 :issue => {:tracker_id => 2,
288 :subject => 'This is the test_new issue',
288 :subject => 'This is the test_new issue',
289 :description => 'This is the description',
289 :description => 'This is the description',
290 :priority_id => 5}
290 :priority_id => 5}
291 assert_response :success
291 assert_response :success
292 assert_template 'new'
292 assert_template 'new'
293 end
293 end
294
294
295 def test_post_new
295 def test_post_new
296 @request.session[:user_id] = 2
296 @request.session[:user_id] = 2
297 post :new, :project_id => 1,
297 post :new, :project_id => 1,
298 :issue => {:tracker_id => 3,
298 :issue => {:tracker_id => 3,
299 :subject => 'This is the test_new issue',
299 :subject => 'This is the test_new issue',
300 :description => 'This is the description',
300 :description => 'This is the description',
301 :priority_id => 5,
301 :priority_id => 5,
302 :estimated_hours => '',
302 :estimated_hours => '',
303 :custom_field_values => {'2' => 'Value for field 2'}}
303 :custom_field_values => {'2' => 'Value for field 2'}}
304 assert_redirected_to 'issues/show'
304 assert_redirected_to 'issues/show'
305
305
306 issue = Issue.find_by_subject('This is the test_new issue')
306 issue = Issue.find_by_subject('This is the test_new issue')
307 assert_not_nil issue
307 assert_not_nil issue
308 assert_equal 2, issue.author_id
308 assert_equal 2, issue.author_id
309 assert_equal 3, issue.tracker_id
309 assert_equal 3, issue.tracker_id
310 assert_nil issue.estimated_hours
310 assert_nil issue.estimated_hours
311 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
311 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
312 assert_not_nil v
312 assert_not_nil v
313 assert_equal 'Value for field 2', v.value
313 assert_equal 'Value for field 2', v.value
314 end
314 end
315
315
316 def test_post_new_without_custom_fields_param
316 def test_post_new_without_custom_fields_param
317 @request.session[:user_id] = 2
317 @request.session[:user_id] = 2
318 post :new, :project_id => 1,
318 post :new, :project_id => 1,
319 :issue => {:tracker_id => 1,
319 :issue => {:tracker_id => 1,
320 :subject => 'This is the test_new issue',
320 :subject => 'This is the test_new issue',
321 :description => 'This is the description',
321 :description => 'This is the description',
322 :priority_id => 5}
322 :priority_id => 5}
323 assert_redirected_to 'issues/show'
323 assert_redirected_to 'issues/show'
324 end
324 end
325
325
326 def test_post_new_with_required_custom_field_and_without_custom_fields_param
326 def test_post_new_with_required_custom_field_and_without_custom_fields_param
327 field = IssueCustomField.find_by_name('Database')
327 field = IssueCustomField.find_by_name('Database')
328 field.update_attribute(:is_required, true)
328 field.update_attribute(:is_required, true)
329
329
330 @request.session[:user_id] = 2
330 @request.session[:user_id] = 2
331 post :new, :project_id => 1,
331 post :new, :project_id => 1,
332 :issue => {:tracker_id => 1,
332 :issue => {:tracker_id => 1,
333 :subject => 'This is the test_new issue',
333 :subject => 'This is the test_new issue',
334 :description => 'This is the description',
334 :description => 'This is the description',
335 :priority_id => 5}
335 :priority_id => 5}
336 assert_response :success
336 assert_response :success
337 assert_template 'new'
337 assert_template 'new'
338 issue = assigns(:issue)
338 issue = assigns(:issue)
339 assert_not_nil issue
339 assert_not_nil issue
340 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
340 assert_equal 'activerecord_error_invalid', issue.errors.on(:custom_values)
341 end
341 end
342
342
343 def test_post_new_with_watchers
343 def test_post_new_with_watchers
344 @request.session[:user_id] = 2
344 @request.session[:user_id] = 2
345 ActionMailer::Base.deliveries.clear
345 ActionMailer::Base.deliveries.clear
346
346
347 assert_difference 'Watcher.count', 2 do
347 assert_difference 'Watcher.count', 2 do
348 post :new, :project_id => 1,
348 post :new, :project_id => 1,
349 :issue => {:tracker_id => 1,
349 :issue => {:tracker_id => 1,
350 :subject => 'This is a new issue with watchers',
350 :subject => 'This is a new issue with watchers',
351 :description => 'This is the description',
351 :description => 'This is the description',
352 :priority_id => 5,
352 :priority_id => 5,
353 :watcher_user_ids => ['2', '3']}
353 :watcher_user_ids => ['2', '3']}
354 end
354 end
355 assert_redirected_to 'issues/show'
355 assert_redirected_to 'issues/show'
356
356
357 issue = Issue.find_by_subject('This is a new issue with watchers')
357 issue = Issue.find_by_subject('This is a new issue with watchers')
358 # Watchers added
358 # Watchers added
359 assert_equal [2, 3], issue.watcher_user_ids.sort
359 assert_equal [2, 3], issue.watcher_user_ids.sort
360 assert issue.watched_by?(User.find(3))
360 assert issue.watched_by?(User.find(3))
361 # Watchers notified
361 # Watchers notified
362 mail = ActionMailer::Base.deliveries.last
362 mail = ActionMailer::Base.deliveries.last
363 assert_kind_of TMail::Mail, mail
363 assert_kind_of TMail::Mail, mail
364 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
364 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
365 end
365 end
366
366
367 def test_post_should_preserve_fields_values_on_validation_failure
367 def test_post_should_preserve_fields_values_on_validation_failure
368 @request.session[:user_id] = 2
368 @request.session[:user_id] = 2
369 post :new, :project_id => 1,
369 post :new, :project_id => 1,
370 :issue => {:tracker_id => 1,
370 :issue => {:tracker_id => 1,
371 :subject => 'This is the test_new issue',
371 :subject => 'This is the test_new issue',
372 # empty description
372 # empty description
373 :description => '',
373 :description => '',
374 :priority_id => 6,
374 :priority_id => 6,
375 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
375 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
376 assert_response :success
376 assert_response :success
377 assert_template 'new'
377 assert_template 'new'
378
378
379 assert_tag :input, :attributes => { :name => 'issue[subject]',
379 assert_tag :input, :attributes => { :name => 'issue[subject]',
380 :value => 'This is the test_new issue' }
380 :value => 'This is the test_new issue' }
381 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
381 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
382 :child => { :tag => 'option', :attributes => { :selected => 'selected',
382 :child => { :tag => 'option', :attributes => { :selected => 'selected',
383 :value => '6' },
383 :value => '6' },
384 :content => 'High' }
384 :content => 'High' }
385 # Custom fields
385 # Custom fields
386 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
386 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
387 :child => { :tag => 'option', :attributes => { :selected => 'selected',
387 :child => { :tag => 'option', :attributes => { :selected => 'selected',
388 :value => 'Oracle' },
388 :value => 'Oracle' },
389 :content => 'Oracle' }
389 :content => 'Oracle' }
390 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
390 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
391 :value => 'Value for field 2'}
391 :value => 'Value for field 2'}
392 end
392 end
393
393
394 def test_copy_issue
394 def test_copy_issue
395 @request.session[:user_id] = 2
395 @request.session[:user_id] = 2
396 get :new, :project_id => 1, :copy_from => 1
396 get :new, :project_id => 1, :copy_from => 1
397 assert_template 'new'
397 assert_template 'new'
398 assert_not_nil assigns(:issue)
398 assert_not_nil assigns(:issue)
399 orig = Issue.find(1)
399 orig = Issue.find(1)
400 assert_equal orig.subject, assigns(:issue).subject
400 assert_equal orig.subject, assigns(:issue).subject
401 end
401 end
402
402
403 def test_get_edit
403 def test_get_edit
404 @request.session[:user_id] = 2
404 @request.session[:user_id] = 2
405 get :edit, :id => 1
405 get :edit, :id => 1
406 assert_response :success
406 assert_response :success
407 assert_template 'edit'
407 assert_template 'edit'
408 assert_not_nil assigns(:issue)
408 assert_not_nil assigns(:issue)
409 assert_equal Issue.find(1), assigns(:issue)
409 assert_equal Issue.find(1), assigns(:issue)
410 end
410 end
411
411
412 def test_get_edit_with_params
412 def test_get_edit_with_params
413 @request.session[:user_id] = 2
413 @request.session[:user_id] = 2
414 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
414 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
415 assert_response :success
415 assert_response :success
416 assert_template 'edit'
416 assert_template 'edit'
417
417
418 issue = assigns(:issue)
418 issue = assigns(:issue)
419 assert_not_nil issue
419 assert_not_nil issue
420
420
421 assert_equal 5, issue.status_id
421 assert_equal 5, issue.status_id
422 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
422 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
423 :child => { :tag => 'option',
423 :child => { :tag => 'option',
424 :content => 'Closed',
424 :content => 'Closed',
425 :attributes => { :selected => 'selected' } }
425 :attributes => { :selected => 'selected' } }
426
426
427 assert_equal 7, issue.priority_id
427 assert_equal 7, issue.priority_id
428 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
428 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
429 :child => { :tag => 'option',
429 :child => { :tag => 'option',
430 :content => 'Urgent',
430 :content => 'Urgent',
431 :attributes => { :selected => 'selected' } }
431 :attributes => { :selected => 'selected' } }
432 end
432 end
433
433
434 def test_reply_to_issue
434 def test_reply_to_issue
435 @request.session[:user_id] = 2
435 @request.session[:user_id] = 2
436 get :reply, :id => 1
436 get :reply, :id => 1
437 assert_response :success
437 assert_response :success
438 assert_select_rjs :show, "update"
438 assert_select_rjs :show, "update"
439 end
439 end
440
440
441 def test_reply_to_note
441 def test_reply_to_note
442 @request.session[:user_id] = 2
442 @request.session[:user_id] = 2
443 get :reply, :id => 1, :journal_id => 2
443 get :reply, :id => 1, :journal_id => 2
444 assert_response :success
444 assert_response :success
445 assert_select_rjs :show, "update"
445 assert_select_rjs :show, "update"
446 end
446 end
447
447
448 def test_post_edit_without_custom_fields_param
448 def test_post_edit_without_custom_fields_param
449 @request.session[:user_id] = 2
449 @request.session[:user_id] = 2
450 ActionMailer::Base.deliveries.clear
450 ActionMailer::Base.deliveries.clear
451
451
452 issue = Issue.find(1)
452 issue = Issue.find(1)
453 assert_equal '125', issue.custom_value_for(2).value
453 assert_equal '125', issue.custom_value_for(2).value
454 old_subject = issue.subject
454 old_subject = issue.subject
455 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
455 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
456
456
457 assert_difference('Journal.count') do
457 assert_difference('Journal.count') do
458 assert_difference('JournalDetail.count', 2) do
458 assert_difference('JournalDetail.count', 2) do
459 post :edit, :id => 1, :issue => {:subject => new_subject,
459 post :edit, :id => 1, :issue => {:subject => new_subject,
460 :priority_id => '6',
460 :priority_id => '6',
461 :category_id => '1' # no change
461 :category_id => '1' # no change
462 }
462 }
463 end
463 end
464 end
464 end
465 assert_redirected_to 'issues/show/1'
465 assert_redirected_to 'issues/show/1'
466 issue.reload
466 issue.reload
467 assert_equal new_subject, issue.subject
467 assert_equal new_subject, issue.subject
468 # Make sure custom fields were not cleared
468 # Make sure custom fields were not cleared
469 assert_equal '125', issue.custom_value_for(2).value
469 assert_equal '125', issue.custom_value_for(2).value
470
470
471 mail = ActionMailer::Base.deliveries.last
471 mail = ActionMailer::Base.deliveries.last
472 assert_kind_of TMail::Mail, mail
472 assert_kind_of TMail::Mail, mail
473 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
473 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
474 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
474 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
475 end
475 end
476
476
477 def test_post_edit_with_custom_field_change
477 def test_post_edit_with_custom_field_change
478 @request.session[:user_id] = 2
478 @request.session[:user_id] = 2
479 issue = Issue.find(1)
479 issue = Issue.find(1)
480 assert_equal '125', issue.custom_value_for(2).value
480 assert_equal '125', issue.custom_value_for(2).value
481
481
482 assert_difference('Journal.count') do
482 assert_difference('Journal.count') do
483 assert_difference('JournalDetail.count', 3) do
483 assert_difference('JournalDetail.count', 3) do
484 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
484 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
485 :priority_id => '6',
485 :priority_id => '6',
486 :category_id => '1', # no change
486 :category_id => '1', # no change
487 :custom_field_values => { '2' => 'New custom value' }
487 :custom_field_values => { '2' => 'New custom value' }
488 }
488 }
489 end
489 end
490 end
490 end
491 assert_redirected_to 'issues/show/1'
491 assert_redirected_to 'issues/show/1'
492 issue.reload
492 issue.reload
493 assert_equal 'New custom value', issue.custom_value_for(2).value
493 assert_equal 'New custom value', issue.custom_value_for(2).value
494
494
495 mail = ActionMailer::Base.deliveries.last
495 mail = ActionMailer::Base.deliveries.last
496 assert_kind_of TMail::Mail, mail
496 assert_kind_of TMail::Mail, mail
497 assert mail.body.include?("Searchable field changed from 125 to New custom value")
497 assert mail.body.include?("Searchable field changed from 125 to New custom value")
498 end
498 end
499
499
500 def test_post_edit_with_status_and_assignee_change
500 def test_post_edit_with_status_and_assignee_change
501 issue = Issue.find(1)
501 issue = Issue.find(1)
502 assert_equal 1, issue.status_id
502 assert_equal 1, issue.status_id
503 @request.session[:user_id] = 2
503 @request.session[:user_id] = 2
504 assert_difference('TimeEntry.count', 0) do
504 assert_difference('TimeEntry.count', 0) do
505 post :edit,
505 post :edit,
506 :id => 1,
506 :id => 1,
507 :issue => { :status_id => 2, :assigned_to_id => 3 },
507 :issue => { :status_id => 2, :assigned_to_id => 3 },
508 :notes => 'Assigned to dlopper',
508 :notes => 'Assigned to dlopper',
509 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
509 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
510 end
510 end
511 assert_redirected_to 'issues/show/1'
511 assert_redirected_to 'issues/show/1'
512 issue.reload
512 issue.reload
513 assert_equal 2, issue.status_id
513 assert_equal 2, issue.status_id
514 j = issue.journals.find(:first, :order => 'id DESC')
514 j = issue.journals.find(:first, :order => 'id DESC')
515 assert_equal 'Assigned to dlopper', j.notes
515 assert_equal 'Assigned to dlopper', j.notes
516 assert_equal 2, j.details.size
516 assert_equal 2, j.details.size
517
517
518 mail = ActionMailer::Base.deliveries.last
518 mail = ActionMailer::Base.deliveries.last
519 assert mail.body.include?("Status changed from New to Assigned")
519 assert mail.body.include?("Status changed from New to Assigned")
520 end
520 end
521
521
522 def test_post_edit_with_note_only
522 def test_post_edit_with_note_only
523 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
523 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
524 # anonymous user
524 # anonymous user
525 post :edit,
525 post :edit,
526 :id => 1,
526 :id => 1,
527 :notes => notes
527 :notes => notes
528 assert_redirected_to 'issues/show/1'
528 assert_redirected_to 'issues/show/1'
529 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
529 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
530 assert_equal notes, j.notes
530 assert_equal notes, j.notes
531 assert_equal 0, j.details.size
531 assert_equal 0, j.details.size
532 assert_equal User.anonymous, j.user
532 assert_equal User.anonymous, j.user
533
533
534 mail = ActionMailer::Base.deliveries.last
534 mail = ActionMailer::Base.deliveries.last
535 assert mail.body.include?(notes)
535 assert mail.body.include?(notes)
536 end
536 end
537
537
538 def test_post_edit_with_note_and_spent_time
538 def test_post_edit_with_note_and_spent_time
539 @request.session[:user_id] = 2
539 @request.session[:user_id] = 2
540 spent_hours_before = Issue.find(1).spent_hours
540 spent_hours_before = Issue.find(1).spent_hours
541 assert_difference('TimeEntry.count') do
541 assert_difference('TimeEntry.count') do
542 post :edit,
542 post :edit,
543 :id => 1,
543 :id => 1,
544 :notes => '2.5 hours added',
544 :notes => '2.5 hours added',
545 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
545 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
546 end
546 end
547 assert_redirected_to 'issues/show/1'
547 assert_redirected_to 'issues/show/1'
548
548
549 issue = Issue.find(1)
549 issue = Issue.find(1)
550
550
551 j = issue.journals.find(:first, :order => 'id DESC')
551 j = issue.journals.find(:first, :order => 'id DESC')
552 assert_equal '2.5 hours added', j.notes
552 assert_equal '2.5 hours added', j.notes
553 assert_equal 0, j.details.size
553 assert_equal 0, j.details.size
554
554
555 t = issue.time_entries.find(:first, :order => 'id DESC')
555 t = issue.time_entries.find(:first, :order => 'id DESC')
556 assert_not_nil t
556 assert_not_nil t
557 assert_equal 2.5, t.hours
557 assert_equal 2.5, t.hours
558 assert_equal spent_hours_before + 2.5, issue.spent_hours
558 assert_equal spent_hours_before + 2.5, issue.spent_hours
559 end
559 end
560
560
561 def test_post_edit_with_attachment_only
561 def test_post_edit_with_attachment_only
562 set_tmp_attachments_directory
562 set_tmp_attachments_directory
563
563
564 # Delete all fixtured journals, a race condition can occur causing the wrong
564 # Delete all fixtured journals, a race condition can occur causing the wrong
565 # journal to get fetched in the next find.
565 # journal to get fetched in the next find.
566 Journal.delete_all
566 Journal.delete_all
567
567
568 # anonymous user
568 # anonymous user
569 post :edit,
569 post :edit,
570 :id => 1,
570 :id => 1,
571 :notes => '',
571 :notes => '',
572 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
572 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
573 assert_redirected_to 'issues/show/1'
573 assert_redirected_to 'issues/show/1'
574 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
574 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
575 assert j.notes.blank?
575 assert j.notes.blank?
576 assert_equal 1, j.details.size
576 assert_equal 1, j.details.size
577 assert_equal 'testfile.txt', j.details.first.value
577 assert_equal 'testfile.txt', j.details.first.value
578 assert_equal User.anonymous, j.user
578 assert_equal User.anonymous, j.user
579
579
580 mail = ActionMailer::Base.deliveries.last
580 mail = ActionMailer::Base.deliveries.last
581 assert mail.body.include?('testfile.txt')
581 assert mail.body.include?('testfile.txt')
582 end
582 end
583
583
584 def test_post_edit_with_no_change
584 def test_post_edit_with_no_change
585 issue = Issue.find(1)
585 issue = Issue.find(1)
586 issue.journals.clear
586 issue.journals.clear
587 ActionMailer::Base.deliveries.clear
587 ActionMailer::Base.deliveries.clear
588
588
589 post :edit,
589 post :edit,
590 :id => 1,
590 :id => 1,
591 :notes => ''
591 :notes => ''
592 assert_redirected_to 'issues/show/1'
592 assert_redirected_to 'issues/show/1'
593
593
594 issue.reload
594 issue.reload
595 assert issue.journals.empty?
595 assert issue.journals.empty?
596 # No email should be sent
596 # No email should be sent
597 assert ActionMailer::Base.deliveries.empty?
597 assert ActionMailer::Base.deliveries.empty?
598 end
598 end
599
600 def test_post_edit_with_invalid_spent_time
601 @request.session[:user_id] = 2
602 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
603
604 assert_no_difference('Journal.count') do
605 post :edit,
606 :id => 1,
607 :notes => notes,
608 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
609 end
610 assert_response :success
611 assert_template 'edit'
612
613 assert_tag :textarea, :attributes => { :name => 'notes' },
614 :content => notes
615 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
616 end
599
617
600 def test_bulk_edit
618 def test_bulk_edit
601 @request.session[:user_id] = 2
619 @request.session[:user_id] = 2
602 # update issues priority
620 # update issues priority
603 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
621 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
604 assert_response 302
622 assert_response 302
605 # check that the issues were updated
623 # check that the issues were updated
606 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
624 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
607 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
625 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
608 end
626 end
609
627
610 def test_bulk_unassign
628 def test_bulk_unassign
611 assert_not_nil Issue.find(2).assigned_to
629 assert_not_nil Issue.find(2).assigned_to
612 @request.session[:user_id] = 2
630 @request.session[:user_id] = 2
613 # unassign issues
631 # unassign issues
614 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
632 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
615 assert_response 302
633 assert_response 302
616 # check that the issues were updated
634 # check that the issues were updated
617 assert_nil Issue.find(2).assigned_to
635 assert_nil Issue.find(2).assigned_to
618 end
636 end
619
637
620 def test_move_one_issue_to_another_project
638 def test_move_one_issue_to_another_project
621 @request.session[:user_id] = 1
639 @request.session[:user_id] = 1
622 post :move, :id => 1, :new_project_id => 2
640 post :move, :id => 1, :new_project_id => 2
623 assert_redirected_to 'projects/ecookbook/issues'
641 assert_redirected_to 'projects/ecookbook/issues'
624 assert_equal 2, Issue.find(1).project_id
642 assert_equal 2, Issue.find(1).project_id
625 end
643 end
626
644
627 def test_bulk_move_to_another_project
645 def test_bulk_move_to_another_project
628 @request.session[:user_id] = 1
646 @request.session[:user_id] = 1
629 post :move, :ids => [1, 2], :new_project_id => 2
647 post :move, :ids => [1, 2], :new_project_id => 2
630 assert_redirected_to 'projects/ecookbook/issues'
648 assert_redirected_to 'projects/ecookbook/issues'
631 # Issues moved to project 2
649 # Issues moved to project 2
632 assert_equal 2, Issue.find(1).project_id
650 assert_equal 2, Issue.find(1).project_id
633 assert_equal 2, Issue.find(2).project_id
651 assert_equal 2, Issue.find(2).project_id
634 # No tracker change
652 # No tracker change
635 assert_equal 1, Issue.find(1).tracker_id
653 assert_equal 1, Issue.find(1).tracker_id
636 assert_equal 2, Issue.find(2).tracker_id
654 assert_equal 2, Issue.find(2).tracker_id
637 end
655 end
638
656
639 def test_bulk_move_to_another_tracker
657 def test_bulk_move_to_another_tracker
640 @request.session[:user_id] = 1
658 @request.session[:user_id] = 1
641 post :move, :ids => [1, 2], :new_tracker_id => 2
659 post :move, :ids => [1, 2], :new_tracker_id => 2
642 assert_redirected_to 'projects/ecookbook/issues'
660 assert_redirected_to 'projects/ecookbook/issues'
643 assert_equal 2, Issue.find(1).tracker_id
661 assert_equal 2, Issue.find(1).tracker_id
644 assert_equal 2, Issue.find(2).tracker_id
662 assert_equal 2, Issue.find(2).tracker_id
645 end
663 end
646
664
647 def test_context_menu_one_issue
665 def test_context_menu_one_issue
648 @request.session[:user_id] = 2
666 @request.session[:user_id] = 2
649 get :context_menu, :ids => [1]
667 get :context_menu, :ids => [1]
650 assert_response :success
668 assert_response :success
651 assert_template 'context_menu'
669 assert_template 'context_menu'
652 assert_tag :tag => 'a', :content => 'Edit',
670 assert_tag :tag => 'a', :content => 'Edit',
653 :attributes => { :href => '/issues/edit/1',
671 :attributes => { :href => '/issues/edit/1',
654 :class => 'icon-edit' }
672 :class => 'icon-edit' }
655 assert_tag :tag => 'a', :content => 'Closed',
673 assert_tag :tag => 'a', :content => 'Closed',
656 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
674 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
657 :class => '' }
675 :class => '' }
658 assert_tag :tag => 'a', :content => 'Immediate',
676 assert_tag :tag => 'a', :content => 'Immediate',
659 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
677 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
660 :class => '' }
678 :class => '' }
661 assert_tag :tag => 'a', :content => 'Dave Lopper',
679 assert_tag :tag => 'a', :content => 'Dave Lopper',
662 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
680 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
663 :class => '' }
681 :class => '' }
664 assert_tag :tag => 'a', :content => 'Copy',
682 assert_tag :tag => 'a', :content => 'Copy',
665 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
683 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
666 :class => 'icon-copy' }
684 :class => 'icon-copy' }
667 assert_tag :tag => 'a', :content => 'Move',
685 assert_tag :tag => 'a', :content => 'Move',
668 :attributes => { :href => '/issues/move?ids%5B%5D=1',
686 :attributes => { :href => '/issues/move?ids%5B%5D=1',
669 :class => 'icon-move' }
687 :class => 'icon-move' }
670 assert_tag :tag => 'a', :content => 'Delete',
688 assert_tag :tag => 'a', :content => 'Delete',
671 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
689 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
672 :class => 'icon-del' }
690 :class => 'icon-del' }
673 end
691 end
674
692
675 def test_context_menu_one_issue_by_anonymous
693 def test_context_menu_one_issue_by_anonymous
676 get :context_menu, :ids => [1]
694 get :context_menu, :ids => [1]
677 assert_response :success
695 assert_response :success
678 assert_template 'context_menu'
696 assert_template 'context_menu'
679 assert_tag :tag => 'a', :content => 'Delete',
697 assert_tag :tag => 'a', :content => 'Delete',
680 :attributes => { :href => '#',
698 :attributes => { :href => '#',
681 :class => 'icon-del disabled' }
699 :class => 'icon-del disabled' }
682 end
700 end
683
701
684 def test_context_menu_multiple_issues_of_same_project
702 def test_context_menu_multiple_issues_of_same_project
685 @request.session[:user_id] = 2
703 @request.session[:user_id] = 2
686 get :context_menu, :ids => [1, 2]
704 get :context_menu, :ids => [1, 2]
687 assert_response :success
705 assert_response :success
688 assert_template 'context_menu'
706 assert_template 'context_menu'
689 assert_tag :tag => 'a', :content => 'Edit',
707 assert_tag :tag => 'a', :content => 'Edit',
690 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
708 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
691 :class => 'icon-edit' }
709 :class => 'icon-edit' }
692 assert_tag :tag => 'a', :content => 'Immediate',
710 assert_tag :tag => 'a', :content => 'Immediate',
693 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
711 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
694 :class => '' }
712 :class => '' }
695 assert_tag :tag => 'a', :content => 'Dave Lopper',
713 assert_tag :tag => 'a', :content => 'Dave Lopper',
696 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
714 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
697 :class => '' }
715 :class => '' }
698 assert_tag :tag => 'a', :content => 'Move',
716 assert_tag :tag => 'a', :content => 'Move',
699 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
717 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
700 :class => 'icon-move' }
718 :class => 'icon-move' }
701 assert_tag :tag => 'a', :content => 'Delete',
719 assert_tag :tag => 'a', :content => 'Delete',
702 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
720 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
703 :class => 'icon-del' }
721 :class => 'icon-del' }
704 end
722 end
705
723
706 def test_context_menu_multiple_issues_of_different_project
724 def test_context_menu_multiple_issues_of_different_project
707 @request.session[:user_id] = 2
725 @request.session[:user_id] = 2
708 get :context_menu, :ids => [1, 2, 4]
726 get :context_menu, :ids => [1, 2, 4]
709 assert_response :success
727 assert_response :success
710 assert_template 'context_menu'
728 assert_template 'context_menu'
711 assert_tag :tag => 'a', :content => 'Delete',
729 assert_tag :tag => 'a', :content => 'Delete',
712 :attributes => { :href => '#',
730 :attributes => { :href => '#',
713 :class => 'icon-del disabled' }
731 :class => 'icon-del disabled' }
714 end
732 end
715
733
716 def test_destroy_issue_with_no_time_entries
734 def test_destroy_issue_with_no_time_entries
717 assert_nil TimeEntry.find_by_issue_id(2)
735 assert_nil TimeEntry.find_by_issue_id(2)
718 @request.session[:user_id] = 2
736 @request.session[:user_id] = 2
719 post :destroy, :id => 2
737 post :destroy, :id => 2
720 assert_redirected_to 'projects/ecookbook/issues'
738 assert_redirected_to 'projects/ecookbook/issues'
721 assert_nil Issue.find_by_id(2)
739 assert_nil Issue.find_by_id(2)
722 end
740 end
723
741
724 def test_destroy_issues_with_time_entries
742 def test_destroy_issues_with_time_entries
725 @request.session[:user_id] = 2
743 @request.session[:user_id] = 2
726 post :destroy, :ids => [1, 3]
744 post :destroy, :ids => [1, 3]
727 assert_response :success
745 assert_response :success
728 assert_template 'destroy'
746 assert_template 'destroy'
729 assert_not_nil assigns(:hours)
747 assert_not_nil assigns(:hours)
730 assert Issue.find_by_id(1) && Issue.find_by_id(3)
748 assert Issue.find_by_id(1) && Issue.find_by_id(3)
731 end
749 end
732
750
733 def test_destroy_issues_and_destroy_time_entries
751 def test_destroy_issues_and_destroy_time_entries
734 @request.session[:user_id] = 2
752 @request.session[:user_id] = 2
735 post :destroy, :ids => [1, 3], :todo => 'destroy'
753 post :destroy, :ids => [1, 3], :todo => 'destroy'
736 assert_redirected_to 'projects/ecookbook/issues'
754 assert_redirected_to 'projects/ecookbook/issues'
737 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
755 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
738 assert_nil TimeEntry.find_by_id([1, 2])
756 assert_nil TimeEntry.find_by_id([1, 2])
739 end
757 end
740
758
741 def test_destroy_issues_and_assign_time_entries_to_project
759 def test_destroy_issues_and_assign_time_entries_to_project
742 @request.session[:user_id] = 2
760 @request.session[:user_id] = 2
743 post :destroy, :ids => [1, 3], :todo => 'nullify'
761 post :destroy, :ids => [1, 3], :todo => 'nullify'
744 assert_redirected_to 'projects/ecookbook/issues'
762 assert_redirected_to 'projects/ecookbook/issues'
745 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
763 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
746 assert_nil TimeEntry.find(1).issue_id
764 assert_nil TimeEntry.find(1).issue_id
747 assert_nil TimeEntry.find(2).issue_id
765 assert_nil TimeEntry.find(2).issue_id
748 end
766 end
749
767
750 def test_destroy_issues_and_reassign_time_entries_to_another_issue
768 def test_destroy_issues_and_reassign_time_entries_to_another_issue
751 @request.session[:user_id] = 2
769 @request.session[:user_id] = 2
752 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
770 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
753 assert_redirected_to 'projects/ecookbook/issues'
771 assert_redirected_to 'projects/ecookbook/issues'
754 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
772 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
755 assert_equal 2, TimeEntry.find(1).issue_id
773 assert_equal 2, TimeEntry.find(1).issue_id
756 assert_equal 2, TimeEntry.find(2).issue_id
774 assert_equal 2, TimeEntry.find(2).issue_id
757 end
775 end
758 end
776 end
@@ -1,46 +1,47
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
19
20 class TimeEntryTest < Test::Unit::TestCase
20 class TimeEntryTest < Test::Unit::TestCase
21 fixtures :issues, :projects, :users, :time_entries
21 fixtures :issues, :projects, :users, :time_entries
22
22
23 def test_hours_format
23 def test_hours_format
24 assertions = { "2" => 2.0,
24 assertions = { "2" => 2.0,
25 "21.1" => 21.1,
25 "21.1" => 21.1,
26 "2,1" => 2.1,
26 "2,1" => 2.1,
27 "1,5h" => 1.5,
27 "7:12" => 7.2,
28 "7:12" => 7.2,
28 "10h" => 10.0,
29 "10h" => 10.0,
29 "10 h" => 10.0,
30 "10 h" => 10.0,
30 "45m" => 0.75,
31 "45m" => 0.75,
31 "45 m" => 0.75,
32 "45 m" => 0.75,
32 "3h15" => 3.25,
33 "3h15" => 3.25,
33 "3h 15" => 3.25,
34 "3h 15" => 3.25,
34 "3 h 15" => 3.25,
35 "3 h 15" => 3.25,
35 "3 h 15m" => 3.25,
36 "3 h 15m" => 3.25,
36 "3 h 15 m" => 3.25,
37 "3 h 15 m" => 3.25,
37 "3 hours" => 3.0,
38 "3 hours" => 3.0,
38 "12min" => 0.2,
39 "12min" => 0.2,
39 }
40 }
40
41
41 assertions.each do |k, v|
42 assertions.each do |k, v|
42 t = TimeEntry.new(:hours => k)
43 t = TimeEntry.new(:hours => k)
43 assert_equal v, t.hours
44 assert_equal v, t.hours, "Converting #{k} failed:"
44 end
45 end
45 end
46 end
46 end
47 end
General Comments 0
You need to be logged in to leave comments. Login now