##// END OF EJS Templates
Fixes r2226: exporting an issue with attachments to PDF raises an error (#2492)....
Jean-Philippe Lang -
r2260:31b3ebf071bc
parent child
Show More
@@ -1,459 +1,461
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2009 Jean-Philippe Lang
2 # Copyright (C) 2006-2009 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 'iconv'
18 require 'iconv'
19 require 'rfpdf/chinese'
19 require 'rfpdf/chinese'
20
20
21 module Redmine
21 module Redmine
22 module Export
22 module Export
23 module PDF
23 module PDF
24 include ActionView::Helpers::NumberHelper
25
24 class IFPDF < FPDF
26 class IFPDF < FPDF
25 include GLoc
27 include GLoc
26 attr_accessor :footer_date
28 attr_accessor :footer_date
27
29
28 def initialize(lang)
30 def initialize(lang)
29 super()
31 super()
30 set_language_if_valid lang
32 set_language_if_valid lang
31 case current_language.to_s
33 case current_language.to_s
32 when 'ja'
34 when 'ja'
33 extend(PDF_Japanese)
35 extend(PDF_Japanese)
34 AddSJISFont()
36 AddSJISFont()
35 @font_for_content = 'SJIS'
37 @font_for_content = 'SJIS'
36 @font_for_footer = 'SJIS'
38 @font_for_footer = 'SJIS'
37 when 'zh'
39 when 'zh'
38 extend(PDF_Chinese)
40 extend(PDF_Chinese)
39 AddGBFont()
41 AddGBFont()
40 @font_for_content = 'GB'
42 @font_for_content = 'GB'
41 @font_for_footer = 'GB'
43 @font_for_footer = 'GB'
42 when 'zh-tw'
44 when 'zh-tw'
43 extend(PDF_Chinese)
45 extend(PDF_Chinese)
44 AddBig5Font()
46 AddBig5Font()
45 @font_for_content = 'Big5'
47 @font_for_content = 'Big5'
46 @font_for_footer = 'Big5'
48 @font_for_footer = 'Big5'
47 else
49 else
48 @font_for_content = 'Arial'
50 @font_for_content = 'Arial'
49 @font_for_footer = 'Helvetica'
51 @font_for_footer = 'Helvetica'
50 end
52 end
51 SetCreator(Redmine::Info.app_name)
53 SetCreator(Redmine::Info.app_name)
52 SetFont(@font_for_content)
54 SetFont(@font_for_content)
53 end
55 end
54
56
55 def SetFontStyle(style, size)
57 def SetFontStyle(style, size)
56 SetFont(@font_for_content, style, size)
58 SetFont(@font_for_content, style, size)
57 end
59 end
58
60
59 def SetTitle(txt)
61 def SetTitle(txt)
60 txt = begin
62 txt = begin
61 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
63 utf16txt = Iconv.conv('UTF-16BE', 'UTF-8', txt)
62 hextxt = "<FEFF" # FEFF is BOM
64 hextxt = "<FEFF" # FEFF is BOM
63 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
65 hextxt << utf16txt.unpack("C*").map {|x| sprintf("%02X",x) }.join
64 hextxt << ">"
66 hextxt << ">"
65 rescue
67 rescue
66 txt
68 txt
67 end || ''
69 end || ''
68 super(txt)
70 super(txt)
69 end
71 end
70
72
71 def textstring(s)
73 def textstring(s)
72 # Format a text string
74 # Format a text string
73 if s =~ /^</ # This means the string is hex-dumped.
75 if s =~ /^</ # This means the string is hex-dumped.
74 return s
76 return s
75 else
77 else
76 return '('+escape(s)+')'
78 return '('+escape(s)+')'
77 end
79 end
78 end
80 end
79
81
80 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
82 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
81 @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
83 @ic ||= Iconv.new(l(:general_pdf_encoding), 'UTF-8')
82 # these quotation marks are not correctly rendered in the pdf
84 # these quotation marks are not correctly rendered in the pdf
83 txt = txt.gsub(/[Ò€œÒ€�]/, '"') if txt
85 txt = txt.gsub(/[Ò€œÒ€�]/, '"') if txt
84 txt = begin
86 txt = begin
85 # 0x5c char handling
87 # 0x5c char handling
86 txtar = txt.split('\\')
88 txtar = txt.split('\\')
87 txtar << '' if txt[-1] == ?\\
89 txtar << '' if txt[-1] == ?\\
88 txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
90 txtar.collect {|x| @ic.iconv(x)}.join('\\').gsub(/\\/, "\\\\\\\\")
89 rescue
91 rescue
90 txt
92 txt
91 end || ''
93 end || ''
92 super w,h,txt,border,ln,align,fill,link
94 super w,h,txt,border,ln,align,fill,link
93 end
95 end
94
96
95 def Footer
97 def Footer
96 SetFont(@font_for_footer, 'I', 8)
98 SetFont(@font_for_footer, 'I', 8)
97 SetY(-15)
99 SetY(-15)
98 SetX(15)
100 SetX(15)
99 Cell(0, 5, @footer_date, 0, 0, 'L')
101 Cell(0, 5, @footer_date, 0, 0, 'L')
100 SetY(-15)
102 SetY(-15)
101 SetX(-30)
103 SetX(-30)
102 Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
104 Cell(0, 5, PageNo().to_s + '/{nb}', 0, 0, 'C')
103 end
105 end
104 end
106 end
105
107
106 # Returns a PDF string of a list of issues
108 # Returns a PDF string of a list of issues
107 def issues_to_pdf(issues, project)
109 def issues_to_pdf(issues, project)
108 pdf = IFPDF.new(current_language)
110 pdf = IFPDF.new(current_language)
109 title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
111 title = project ? "#{project} - #{l(:label_issue_plural)}" : "#{l(:label_issue_plural)}"
110 pdf.SetTitle(title)
112 pdf.SetTitle(title)
111 pdf.AliasNbPages
113 pdf.AliasNbPages
112 pdf.footer_date = format_date(Date.today)
114 pdf.footer_date = format_date(Date.today)
113 pdf.AddPage("L")
115 pdf.AddPage("L")
114 row_height = 7
116 row_height = 7
115
117
116 # title
118 # title
117 pdf.SetFontStyle('B',11)
119 pdf.SetFontStyle('B',11)
118 pdf.Cell(190,10, title)
120 pdf.Cell(190,10, title)
119 pdf.Ln
121 pdf.Ln
120
122
121 # headers
123 # headers
122 pdf.SetFontStyle('B',10)
124 pdf.SetFontStyle('B',10)
123 pdf.SetFillColor(230, 230, 230)
125 pdf.SetFillColor(230, 230, 230)
124 pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
126 pdf.Cell(15, row_height, "#", 0, 0, 'L', 1)
125 pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
127 pdf.Cell(30, row_height, l(:field_tracker), 0, 0, 'L', 1)
126 pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
128 pdf.Cell(30, row_height, l(:field_status), 0, 0, 'L', 1)
127 pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
129 pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
128 pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
130 pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
129 pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
131 pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
130 pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
132 pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
131 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
133 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
132 pdf.Ln
134 pdf.Ln
133 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
135 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
134 pdf.SetY(pdf.GetY() + 1)
136 pdf.SetY(pdf.GetY() + 1)
135
137
136 # rows
138 # rows
137 pdf.SetFontStyle('',9)
139 pdf.SetFontStyle('',9)
138 pdf.SetFillColor(255, 255, 255)
140 pdf.SetFillColor(255, 255, 255)
139 issues.each do |issue|
141 issues.each do |issue|
140 pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
142 pdf.Cell(15, row_height, issue.id.to_s, 0, 0, 'L', 1)
141 pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
143 pdf.Cell(30, row_height, issue.tracker.name, 0, 0, 'L', 1)
142 pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
144 pdf.Cell(30, row_height, issue.status.name, 0, 0, 'L', 1)
143 pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
145 pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
144 pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1)
146 pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.to_s : '', 0, 0, 'L', 1)
145 pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
147 pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
146 pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
148 pdf.MultiCell(0, row_height, (project == issue.project ? issue.subject : "#{issue.project} - #{issue.subject}"))
147 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
149 pdf.Line(10, pdf.GetY, 287, pdf.GetY)
148 pdf.SetY(pdf.GetY() + 1)
150 pdf.SetY(pdf.GetY() + 1)
149 end
151 end
150 pdf.Output
152 pdf.Output
151 end
153 end
152
154
153 # Returns a PDF string of a single issue
155 # Returns a PDF string of a single issue
154 def issue_to_pdf(issue)
156 def issue_to_pdf(issue)
155 pdf = IFPDF.new(current_language)
157 pdf = IFPDF.new(current_language)
156 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
158 pdf.SetTitle("#{issue.project} - ##{issue.tracker} #{issue.id}")
157 pdf.AliasNbPages
159 pdf.AliasNbPages
158 pdf.footer_date = format_date(Date.today)
160 pdf.footer_date = format_date(Date.today)
159 pdf.AddPage
161 pdf.AddPage
160
162
161 pdf.SetFontStyle('B',11)
163 pdf.SetFontStyle('B',11)
162 pdf.Cell(190,10, "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
164 pdf.Cell(190,10, "#{issue.project} - #{issue.tracker} # #{issue.id}: #{issue.subject}")
163 pdf.Ln
165 pdf.Ln
164
166
165 y0 = pdf.GetY
167 y0 = pdf.GetY
166
168
167 pdf.SetFontStyle('B',9)
169 pdf.SetFontStyle('B',9)
168 pdf.Cell(35,5, l(:field_status) + ":","LT")
170 pdf.Cell(35,5, l(:field_status) + ":","LT")
169 pdf.SetFontStyle('',9)
171 pdf.SetFontStyle('',9)
170 pdf.Cell(60,5, issue.status.to_s,"RT")
172 pdf.Cell(60,5, issue.status.to_s,"RT")
171 pdf.SetFontStyle('B',9)
173 pdf.SetFontStyle('B',9)
172 pdf.Cell(35,5, l(:field_priority) + ":","LT")
174 pdf.Cell(35,5, l(:field_priority) + ":","LT")
173 pdf.SetFontStyle('',9)
175 pdf.SetFontStyle('',9)
174 pdf.Cell(60,5, issue.priority.to_s,"RT")
176 pdf.Cell(60,5, issue.priority.to_s,"RT")
175 pdf.Ln
177 pdf.Ln
176
178
177 pdf.SetFontStyle('B',9)
179 pdf.SetFontStyle('B',9)
178 pdf.Cell(35,5, l(:field_author) + ":","L")
180 pdf.Cell(35,5, l(:field_author) + ":","L")
179 pdf.SetFontStyle('',9)
181 pdf.SetFontStyle('',9)
180 pdf.Cell(60,5, issue.author.to_s,"R")
182 pdf.Cell(60,5, issue.author.to_s,"R")
181 pdf.SetFontStyle('B',9)
183 pdf.SetFontStyle('B',9)
182 pdf.Cell(35,5, l(:field_category) + ":","L")
184 pdf.Cell(35,5, l(:field_category) + ":","L")
183 pdf.SetFontStyle('',9)
185 pdf.SetFontStyle('',9)
184 pdf.Cell(60,5, issue.category.to_s,"R")
186 pdf.Cell(60,5, issue.category.to_s,"R")
185 pdf.Ln
187 pdf.Ln
186
188
187 pdf.SetFontStyle('B',9)
189 pdf.SetFontStyle('B',9)
188 pdf.Cell(35,5, l(:field_created_on) + ":","L")
190 pdf.Cell(35,5, l(:field_created_on) + ":","L")
189 pdf.SetFontStyle('',9)
191 pdf.SetFontStyle('',9)
190 pdf.Cell(60,5, format_date(issue.created_on),"R")
192 pdf.Cell(60,5, format_date(issue.created_on),"R")
191 pdf.SetFontStyle('B',9)
193 pdf.SetFontStyle('B',9)
192 pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
194 pdf.Cell(35,5, l(:field_assigned_to) + ":","L")
193 pdf.SetFontStyle('',9)
195 pdf.SetFontStyle('',9)
194 pdf.Cell(60,5, issue.assigned_to.to_s,"R")
196 pdf.Cell(60,5, issue.assigned_to.to_s,"R")
195 pdf.Ln
197 pdf.Ln
196
198
197 pdf.SetFontStyle('B',9)
199 pdf.SetFontStyle('B',9)
198 pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
200 pdf.Cell(35,5, l(:field_updated_on) + ":","LB")
199 pdf.SetFontStyle('',9)
201 pdf.SetFontStyle('',9)
200 pdf.Cell(60,5, format_date(issue.updated_on),"RB")
202 pdf.Cell(60,5, format_date(issue.updated_on),"RB")
201 pdf.SetFontStyle('B',9)
203 pdf.SetFontStyle('B',9)
202 pdf.Cell(35,5, l(:field_due_date) + ":","LB")
204 pdf.Cell(35,5, l(:field_due_date) + ":","LB")
203 pdf.SetFontStyle('',9)
205 pdf.SetFontStyle('',9)
204 pdf.Cell(60,5, format_date(issue.due_date),"RB")
206 pdf.Cell(60,5, format_date(issue.due_date),"RB")
205 pdf.Ln
207 pdf.Ln
206
208
207 for custom_value in issue.custom_values
209 for custom_value in issue.custom_values
208 pdf.SetFontStyle('B',9)
210 pdf.SetFontStyle('B',9)
209 pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
211 pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
210 pdf.SetFontStyle('',9)
212 pdf.SetFontStyle('',9)
211 pdf.MultiCell(155,5, (show_value custom_value),"R")
213 pdf.MultiCell(155,5, (show_value custom_value),"R")
212 end
214 end
213
215
214 pdf.SetFontStyle('B',9)
216 pdf.SetFontStyle('B',9)
215 pdf.Cell(35,5, l(:field_subject) + ":","LTB")
217 pdf.Cell(35,5, l(:field_subject) + ":","LTB")
216 pdf.SetFontStyle('',9)
218 pdf.SetFontStyle('',9)
217 pdf.Cell(155,5, issue.subject,"RTB")
219 pdf.Cell(155,5, issue.subject,"RTB")
218 pdf.Ln
220 pdf.Ln
219
221
220 pdf.SetFontStyle('B',9)
222 pdf.SetFontStyle('B',9)
221 pdf.Cell(35,5, l(:field_description) + ":")
223 pdf.Cell(35,5, l(:field_description) + ":")
222 pdf.SetFontStyle('',9)
224 pdf.SetFontStyle('',9)
223 pdf.MultiCell(155,5, @issue.description,"BR")
225 pdf.MultiCell(155,5, @issue.description,"BR")
224
226
225 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
227 pdf.Line(pdf.GetX, y0, pdf.GetX, pdf.GetY)
226 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
228 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
227 pdf.Ln
229 pdf.Ln
228
230
229 if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
231 if issue.changesets.any? && User.current.allowed_to?(:view_changesets, issue.project)
230 pdf.SetFontStyle('B',9)
232 pdf.SetFontStyle('B',9)
231 pdf.Cell(190,5, l(:label_associated_revisions), "B")
233 pdf.Cell(190,5, l(:label_associated_revisions), "B")
232 pdf.Ln
234 pdf.Ln
233 for changeset in issue.changesets
235 for changeset in issue.changesets
234 pdf.SetFontStyle('B',8)
236 pdf.SetFontStyle('B',8)
235 pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
237 pdf.Cell(190,5, format_time(changeset.committed_on) + " - " + changeset.author.to_s)
236 pdf.Ln
238 pdf.Ln
237 unless changeset.comments.blank?
239 unless changeset.comments.blank?
238 pdf.SetFontStyle('',8)
240 pdf.SetFontStyle('',8)
239 pdf.MultiCell(190,5, changeset.comments)
241 pdf.MultiCell(190,5, changeset.comments)
240 end
242 end
241 pdf.Ln
243 pdf.Ln
242 end
244 end
243 end
245 end
244
246
245 pdf.SetFontStyle('B',9)
247 pdf.SetFontStyle('B',9)
246 pdf.Cell(190,5, l(:label_history), "B")
248 pdf.Cell(190,5, l(:label_history), "B")
247 pdf.Ln
249 pdf.Ln
248 for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
250 for journal in issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
249 pdf.SetFontStyle('B',8)
251 pdf.SetFontStyle('B',8)
250 pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
252 pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
251 pdf.Ln
253 pdf.Ln
252 pdf.SetFontStyle('I',8)
254 pdf.SetFontStyle('I',8)
253 for detail in journal.details
255 for detail in journal.details
254 pdf.Cell(190,5, "- " + show_detail(detail, true))
256 pdf.Cell(190,5, "- " + show_detail(detail, true))
255 pdf.Ln
257 pdf.Ln
256 end
258 end
257 if journal.notes?
259 if journal.notes?
258 pdf.SetFontStyle('',8)
260 pdf.SetFontStyle('',8)
259 pdf.MultiCell(190,5, journal.notes)
261 pdf.MultiCell(190,5, journal.notes)
260 end
262 end
261 pdf.Ln
263 pdf.Ln
262 end
264 end
263
265
264 if issue.attachments.any?
266 if issue.attachments.any?
265 pdf.SetFontStyle('B',9)
267 pdf.SetFontStyle('B',9)
266 pdf.Cell(190,5, l(:label_attachment_plural), "B")
268 pdf.Cell(190,5, l(:label_attachment_plural), "B")
267 pdf.Ln
269 pdf.Ln
268 for attachment in issue.attachments
270 for attachment in issue.attachments
269 pdf.SetFontStyle('',8)
271 pdf.SetFontStyle('',8)
270 pdf.Cell(80,5, attachment.filename)
272 pdf.Cell(80,5, attachment.filename)
271 pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
273 pdf.Cell(20,5, number_to_human_size(attachment.filesize),0,0,"R")
272 pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
274 pdf.Cell(25,5, format_date(attachment.created_on),0,0,"R")
273 pdf.Cell(65,5, attachment.author.name,0,0,"R")
275 pdf.Cell(65,5, attachment.author.name,0,0,"R")
274 pdf.Ln
276 pdf.Ln
275 end
277 end
276 end
278 end
277 pdf.Output
279 pdf.Output
278 end
280 end
279
281
280 # Returns a PDF string of a gantt chart
282 # Returns a PDF string of a gantt chart
281 def gantt_to_pdf(gantt, project)
283 def gantt_to_pdf(gantt, project)
282 pdf = IFPDF.new(current_language)
284 pdf = IFPDF.new(current_language)
283 pdf.SetTitle("#{l(:label_gantt)} #{project}")
285 pdf.SetTitle("#{l(:label_gantt)} #{project}")
284 pdf.AliasNbPages
286 pdf.AliasNbPages
285 pdf.footer_date = format_date(Date.today)
287 pdf.footer_date = format_date(Date.today)
286 pdf.AddPage("L")
288 pdf.AddPage("L")
287 pdf.SetFontStyle('B',12)
289 pdf.SetFontStyle('B',12)
288 pdf.SetX(15)
290 pdf.SetX(15)
289 pdf.Cell(70, 20, project.to_s)
291 pdf.Cell(70, 20, project.to_s)
290 pdf.Ln
292 pdf.Ln
291 pdf.SetFontStyle('B',9)
293 pdf.SetFontStyle('B',9)
292
294
293 subject_width = 70
295 subject_width = 70
294 header_heigth = 5
296 header_heigth = 5
295
297
296 headers_heigth = header_heigth
298 headers_heigth = header_heigth
297 show_weeks = false
299 show_weeks = false
298 show_days = false
300 show_days = false
299
301
300 if gantt.months < 7
302 if gantt.months < 7
301 show_weeks = true
303 show_weeks = true
302 headers_heigth = 2*header_heigth
304 headers_heigth = 2*header_heigth
303 if gantt.months < 3
305 if gantt.months < 3
304 show_days = true
306 show_days = true
305 headers_heigth = 3*header_heigth
307 headers_heigth = 3*header_heigth
306 end
308 end
307 end
309 end
308
310
309 g_width = 210
311 g_width = 210
310 zoom = (g_width) / (gantt.date_to - gantt.date_from + 1)
312 zoom = (g_width) / (gantt.date_to - gantt.date_from + 1)
311 g_height = 120
313 g_height = 120
312 t_height = g_height + headers_heigth
314 t_height = g_height + headers_heigth
313
315
314 y_start = pdf.GetY
316 y_start = pdf.GetY
315
317
316 # Months headers
318 # Months headers
317 month_f = gantt.date_from
319 month_f = gantt.date_from
318 left = subject_width
320 left = subject_width
319 height = header_heigth
321 height = header_heigth
320 gantt.months.times do
322 gantt.months.times do
321 width = ((month_f >> 1) - month_f) * zoom
323 width = ((month_f >> 1) - month_f) * zoom
322 pdf.SetY(y_start)
324 pdf.SetY(y_start)
323 pdf.SetX(left)
325 pdf.SetX(left)
324 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
326 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
325 left = left + width
327 left = left + width
326 month_f = month_f >> 1
328 month_f = month_f >> 1
327 end
329 end
328
330
329 # Weeks headers
331 # Weeks headers
330 if show_weeks
332 if show_weeks
331 left = subject_width
333 left = subject_width
332 height = header_heigth
334 height = header_heigth
333 if gantt.date_from.cwday == 1
335 if gantt.date_from.cwday == 1
334 # gantt.date_from is monday
336 # gantt.date_from is monday
335 week_f = gantt.date_from
337 week_f = gantt.date_from
336 else
338 else
337 # find next monday after gantt.date_from
339 # find next monday after gantt.date_from
338 week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1)
340 week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1)
339 width = (7 - gantt.date_from.cwday + 1) * zoom-1
341 width = (7 - gantt.date_from.cwday + 1) * zoom-1
340 pdf.SetY(y_start + header_heigth)
342 pdf.SetY(y_start + header_heigth)
341 pdf.SetX(left)
343 pdf.SetX(left)
342 pdf.Cell(width + 1, height, "", "LTR")
344 pdf.Cell(width + 1, height, "", "LTR")
343 left = left + width+1
345 left = left + width+1
344 end
346 end
345 while week_f <= gantt.date_to
347 while week_f <= gantt.date_to
346 width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom
348 width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom
347 pdf.SetY(y_start + header_heigth)
349 pdf.SetY(y_start + header_heigth)
348 pdf.SetX(left)
350 pdf.SetX(left)
349 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
351 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
350 left = left + width
352 left = left + width
351 week_f = week_f+7
353 week_f = week_f+7
352 end
354 end
353 end
355 end
354
356
355 # Days headers
357 # Days headers
356 if show_days
358 if show_days
357 left = subject_width
359 left = subject_width
358 height = header_heigth
360 height = header_heigth
359 wday = gantt.date_from.cwday
361 wday = gantt.date_from.cwday
360 pdf.SetFontStyle('B',7)
362 pdf.SetFontStyle('B',7)
361 (gantt.date_to - gantt.date_from + 1).to_i.times do
363 (gantt.date_to - gantt.date_from + 1).to_i.times do
362 width = zoom
364 width = zoom
363 pdf.SetY(y_start + 2 * header_heigth)
365 pdf.SetY(y_start + 2 * header_heigth)
364 pdf.SetX(left)
366 pdf.SetX(left)
365 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
367 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
366 left = left + width
368 left = left + width
367 wday = wday + 1
369 wday = wday + 1
368 wday = 1 if wday > 7
370 wday = 1 if wday > 7
369 end
371 end
370 end
372 end
371
373
372 pdf.SetY(y_start)
374 pdf.SetY(y_start)
373 pdf.SetX(15)
375 pdf.SetX(15)
374 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
376 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
375
377
376 # Tasks
378 # Tasks
377 top = headers_heigth + y_start
379 top = headers_heigth + y_start
378 pdf.SetFontStyle('B',7)
380 pdf.SetFontStyle('B',7)
379 gantt.events.each do |i|
381 gantt.events.each do |i|
380 pdf.SetY(top)
382 pdf.SetY(top)
381 pdf.SetX(15)
383 pdf.SetX(15)
382
384
383 if i.is_a? Issue
385 if i.is_a? Issue
384 pdf.Cell(subject_width-15, 5, "#{i.tracker} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
386 pdf.Cell(subject_width-15, 5, "#{i.tracker} #{i.id}: #{i.subject}".sub(/^(.{30}[^\s]*\s).*$/, '\1 (...)'), "LR")
385 else
387 else
386 pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
388 pdf.Cell(subject_width-15, 5, "#{l(:label_version)}: #{i.name}", "LR")
387 end
389 end
388
390
389 pdf.SetY(top)
391 pdf.SetY(top)
390 pdf.SetX(subject_width)
392 pdf.SetX(subject_width)
391 pdf.Cell(g_width, 5, "", "LR")
393 pdf.Cell(g_width, 5, "", "LR")
392
394
393 pdf.SetY(top+1.5)
395 pdf.SetY(top+1.5)
394
396
395 if i.is_a? Issue
397 if i.is_a? Issue
396 i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from )
398 i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from )
397 i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to )
399 i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to )
398
400
399 i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
401 i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
400 i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date )
402 i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date )
401 i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date )
403 i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date )
402
404
403 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
405 i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
404
406
405 i_left = ((i_start_date - gantt.date_from)*zoom)
407 i_left = ((i_start_date - gantt.date_from)*zoom)
406 i_width = ((i_end_date - i_start_date + 1)*zoom)
408 i_width = ((i_end_date - i_start_date + 1)*zoom)
407 d_width = ((i_done_date - i_start_date)*zoom)
409 d_width = ((i_done_date - i_start_date)*zoom)
408 l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
410 l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
409 l_width ||= 0
411 l_width ||= 0
410
412
411 pdf.SetX(subject_width + i_left)
413 pdf.SetX(subject_width + i_left)
412 pdf.SetFillColor(200,200,200)
414 pdf.SetFillColor(200,200,200)
413 pdf.Cell(i_width, 2, "", 0, 0, "", 1)
415 pdf.Cell(i_width, 2, "", 0, 0, "", 1)
414
416
415 if l_width > 0
417 if l_width > 0
416 pdf.SetY(top+1.5)
418 pdf.SetY(top+1.5)
417 pdf.SetX(subject_width + i_left)
419 pdf.SetX(subject_width + i_left)
418 pdf.SetFillColor(255,100,100)
420 pdf.SetFillColor(255,100,100)
419 pdf.Cell(l_width, 2, "", 0, 0, "", 1)
421 pdf.Cell(l_width, 2, "", 0, 0, "", 1)
420 end
422 end
421 if d_width > 0
423 if d_width > 0
422 pdf.SetY(top+1.5)
424 pdf.SetY(top+1.5)
423 pdf.SetX(subject_width + i_left)
425 pdf.SetX(subject_width + i_left)
424 pdf.SetFillColor(100,100,255)
426 pdf.SetFillColor(100,100,255)
425 pdf.Cell(d_width, 2, "", 0, 0, "", 1)
427 pdf.Cell(d_width, 2, "", 0, 0, "", 1)
426 end
428 end
427
429
428 pdf.SetY(top+1.5)
430 pdf.SetY(top+1.5)
429 pdf.SetX(subject_width + i_left + i_width)
431 pdf.SetX(subject_width + i_left + i_width)
430 pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%")
432 pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%")
431 else
433 else
432 i_left = ((i.start_date - gantt.date_from)*zoom)
434 i_left = ((i.start_date - gantt.date_from)*zoom)
433
435
434 pdf.SetX(subject_width + i_left)
436 pdf.SetX(subject_width + i_left)
435 pdf.SetFillColor(50,200,50)
437 pdf.SetFillColor(50,200,50)
436 pdf.Cell(2, 2, "", 0, 0, "", 1)
438 pdf.Cell(2, 2, "", 0, 0, "", 1)
437
439
438 pdf.SetY(top+1.5)
440 pdf.SetY(top+1.5)
439 pdf.SetX(subject_width + i_left + 3)
441 pdf.SetX(subject_width + i_left + 3)
440 pdf.Cell(30, 2, "#{i.name}")
442 pdf.Cell(30, 2, "#{i.name}")
441 end
443 end
442
444
443 top = top + 5
445 top = top + 5
444 pdf.SetDrawColor(200, 200, 200)
446 pdf.SetDrawColor(200, 200, 200)
445 pdf.Line(15, top, subject_width+g_width, top)
447 pdf.Line(15, top, subject_width+g_width, top)
446 if pdf.GetY() > 180
448 if pdf.GetY() > 180
447 pdf.AddPage("L")
449 pdf.AddPage("L")
448 top = 20
450 top = 20
449 pdf.Line(15, top, subject_width+g_width, top)
451 pdf.Line(15, top, subject_width+g_width, top)
450 end
452 end
451 pdf.SetDrawColor(0, 0, 0)
453 pdf.SetDrawColor(0, 0, 0)
452 end
454 end
453
455
454 pdf.Line(15, top, subject_width+g_width, top)
456 pdf.Line(15, top, subject_width+g_width, top)
455 pdf.Output
457 pdf.Output
456 end
458 end
457 end
459 end
458 end
460 end
459 end
461 end
@@ -1,777 +1,777
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 => 3, :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 :controller => 'issues', :action => 'show'
304 assert_redirected_to :controller => 'issues', :action => '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 :controller => 'issues', :action => 'show'
323 assert_redirected_to :controller => 'issues', :action => '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 issue = Issue.find_by_subject('This is a new issue with watchers')
355 issue = Issue.find_by_subject('This is a new issue with watchers')
356 assert_not_nil issue
356 assert_not_nil issue
357 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
357 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
358
358
359 # Watchers added
359 # Watchers added
360 assert_equal [2, 3], issue.watcher_user_ids.sort
360 assert_equal [2, 3], issue.watcher_user_ids.sort
361 assert issue.watched_by?(User.find(3))
361 assert issue.watched_by?(User.find(3))
362 # Watchers notified
362 # Watchers notified
363 mail = ActionMailer::Base.deliveries.last
363 mail = ActionMailer::Base.deliveries.last
364 assert_kind_of TMail::Mail, mail
364 assert_kind_of TMail::Mail, mail
365 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
365 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
366 end
366 end
367
367
368 def test_post_should_preserve_fields_values_on_validation_failure
368 def test_post_should_preserve_fields_values_on_validation_failure
369 @request.session[:user_id] = 2
369 @request.session[:user_id] = 2
370 post :new, :project_id => 1,
370 post :new, :project_id => 1,
371 :issue => {:tracker_id => 1,
371 :issue => {:tracker_id => 1,
372 # empty subject
372 # empty subject
373 :subject => '',
373 :subject => '',
374 :description => 'This is a description',
374 :description => 'This is a description',
375 :priority_id => 6,
375 :priority_id => 6,
376 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
376 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
377 assert_response :success
377 assert_response :success
378 assert_template 'new'
378 assert_template 'new'
379
379
380 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
380 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
381 :content => 'This is a description'
381 :content => 'This is a description'
382 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
382 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
383 :child => { :tag => 'option', :attributes => { :selected => 'selected',
383 :child => { :tag => 'option', :attributes => { :selected => 'selected',
384 :value => '6' },
384 :value => '6' },
385 :content => 'High' }
385 :content => 'High' }
386 # Custom fields
386 # Custom fields
387 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
387 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
388 :child => { :tag => 'option', :attributes => { :selected => 'selected',
388 :child => { :tag => 'option', :attributes => { :selected => 'selected',
389 :value => 'Oracle' },
389 :value => 'Oracle' },
390 :content => 'Oracle' }
390 :content => 'Oracle' }
391 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
391 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
392 :value => 'Value for field 2'}
392 :value => 'Value for field 2'}
393 end
393 end
394
394
395 def test_copy_issue
395 def test_copy_issue
396 @request.session[:user_id] = 2
396 @request.session[:user_id] = 2
397 get :new, :project_id => 1, :copy_from => 1
397 get :new, :project_id => 1, :copy_from => 1
398 assert_template 'new'
398 assert_template 'new'
399 assert_not_nil assigns(:issue)
399 assert_not_nil assigns(:issue)
400 orig = Issue.find(1)
400 orig = Issue.find(1)
401 assert_equal orig.subject, assigns(:issue).subject
401 assert_equal orig.subject, assigns(:issue).subject
402 end
402 end
403
403
404 def test_get_edit
404 def test_get_edit
405 @request.session[:user_id] = 2
405 @request.session[:user_id] = 2
406 get :edit, :id => 1
406 get :edit, :id => 1
407 assert_response :success
407 assert_response :success
408 assert_template 'edit'
408 assert_template 'edit'
409 assert_not_nil assigns(:issue)
409 assert_not_nil assigns(:issue)
410 assert_equal Issue.find(1), assigns(:issue)
410 assert_equal Issue.find(1), assigns(:issue)
411 end
411 end
412
412
413 def test_get_edit_with_params
413 def test_get_edit_with_params
414 @request.session[:user_id] = 2
414 @request.session[:user_id] = 2
415 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
415 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
416 assert_response :success
416 assert_response :success
417 assert_template 'edit'
417 assert_template 'edit'
418
418
419 issue = assigns(:issue)
419 issue = assigns(:issue)
420 assert_not_nil issue
420 assert_not_nil issue
421
421
422 assert_equal 5, issue.status_id
422 assert_equal 5, issue.status_id
423 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
423 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
424 :child => { :tag => 'option',
424 :child => { :tag => 'option',
425 :content => 'Closed',
425 :content => 'Closed',
426 :attributes => { :selected => 'selected' } }
426 :attributes => { :selected => 'selected' } }
427
427
428 assert_equal 7, issue.priority_id
428 assert_equal 7, issue.priority_id
429 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
429 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
430 :child => { :tag => 'option',
430 :child => { :tag => 'option',
431 :content => 'Urgent',
431 :content => 'Urgent',
432 :attributes => { :selected => 'selected' } }
432 :attributes => { :selected => 'selected' } }
433 end
433 end
434
434
435 def test_reply_to_issue
435 def test_reply_to_issue
436 @request.session[:user_id] = 2
436 @request.session[:user_id] = 2
437 get :reply, :id => 1
437 get :reply, :id => 1
438 assert_response :success
438 assert_response :success
439 assert_select_rjs :show, "update"
439 assert_select_rjs :show, "update"
440 end
440 end
441
441
442 def test_reply_to_note
442 def test_reply_to_note
443 @request.session[:user_id] = 2
443 @request.session[:user_id] = 2
444 get :reply, :id => 1, :journal_id => 2
444 get :reply, :id => 1, :journal_id => 2
445 assert_response :success
445 assert_response :success
446 assert_select_rjs :show, "update"
446 assert_select_rjs :show, "update"
447 end
447 end
448
448
449 def test_post_edit_without_custom_fields_param
449 def test_post_edit_without_custom_fields_param
450 @request.session[:user_id] = 2
450 @request.session[:user_id] = 2
451 ActionMailer::Base.deliveries.clear
451 ActionMailer::Base.deliveries.clear
452
452
453 issue = Issue.find(1)
453 issue = Issue.find(1)
454 assert_equal '125', issue.custom_value_for(2).value
454 assert_equal '125', issue.custom_value_for(2).value
455 old_subject = issue.subject
455 old_subject = issue.subject
456 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
456 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
457
457
458 assert_difference('Journal.count') do
458 assert_difference('Journal.count') do
459 assert_difference('JournalDetail.count', 2) do
459 assert_difference('JournalDetail.count', 2) do
460 post :edit, :id => 1, :issue => {:subject => new_subject,
460 post :edit, :id => 1, :issue => {:subject => new_subject,
461 :priority_id => '6',
461 :priority_id => '6',
462 :category_id => '1' # no change
462 :category_id => '1' # no change
463 }
463 }
464 end
464 end
465 end
465 end
466 assert_redirected_to 'issues/show/1'
466 assert_redirected_to 'issues/show/1'
467 issue.reload
467 issue.reload
468 assert_equal new_subject, issue.subject
468 assert_equal new_subject, issue.subject
469 # Make sure custom fields were not cleared
469 # Make sure custom fields were not cleared
470 assert_equal '125', issue.custom_value_for(2).value
470 assert_equal '125', issue.custom_value_for(2).value
471
471
472 mail = ActionMailer::Base.deliveries.last
472 mail = ActionMailer::Base.deliveries.last
473 assert_kind_of TMail::Mail, mail
473 assert_kind_of TMail::Mail, mail
474 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
474 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
475 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
475 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
476 end
476 end
477
477
478 def test_post_edit_with_custom_field_change
478 def test_post_edit_with_custom_field_change
479 @request.session[:user_id] = 2
479 @request.session[:user_id] = 2
480 issue = Issue.find(1)
480 issue = Issue.find(1)
481 assert_equal '125', issue.custom_value_for(2).value
481 assert_equal '125', issue.custom_value_for(2).value
482
482
483 assert_difference('Journal.count') do
483 assert_difference('Journal.count') do
484 assert_difference('JournalDetail.count', 3) do
484 assert_difference('JournalDetail.count', 3) do
485 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
485 post :edit, :id => 1, :issue => {:subject => 'Custom field change',
486 :priority_id => '6',
486 :priority_id => '6',
487 :category_id => '1', # no change
487 :category_id => '1', # no change
488 :custom_field_values => { '2' => 'New custom value' }
488 :custom_field_values => { '2' => 'New custom value' }
489 }
489 }
490 end
490 end
491 end
491 end
492 assert_redirected_to 'issues/show/1'
492 assert_redirected_to 'issues/show/1'
493 issue.reload
493 issue.reload
494 assert_equal 'New custom value', issue.custom_value_for(2).value
494 assert_equal 'New custom value', issue.custom_value_for(2).value
495
495
496 mail = ActionMailer::Base.deliveries.last
496 mail = ActionMailer::Base.deliveries.last
497 assert_kind_of TMail::Mail, mail
497 assert_kind_of TMail::Mail, mail
498 assert mail.body.include?("Searchable field changed from 125 to New custom value")
498 assert mail.body.include?("Searchable field changed from 125 to New custom value")
499 end
499 end
500
500
501 def test_post_edit_with_status_and_assignee_change
501 def test_post_edit_with_status_and_assignee_change
502 issue = Issue.find(1)
502 issue = Issue.find(1)
503 assert_equal 1, issue.status_id
503 assert_equal 1, issue.status_id
504 @request.session[:user_id] = 2
504 @request.session[:user_id] = 2
505 assert_difference('TimeEntry.count', 0) do
505 assert_difference('TimeEntry.count', 0) do
506 post :edit,
506 post :edit,
507 :id => 1,
507 :id => 1,
508 :issue => { :status_id => 2, :assigned_to_id => 3 },
508 :issue => { :status_id => 2, :assigned_to_id => 3 },
509 :notes => 'Assigned to dlopper',
509 :notes => 'Assigned to dlopper',
510 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
510 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
511 end
511 end
512 assert_redirected_to 'issues/show/1'
512 assert_redirected_to 'issues/show/1'
513 issue.reload
513 issue.reload
514 assert_equal 2, issue.status_id
514 assert_equal 2, issue.status_id
515 j = issue.journals.find(:first, :order => 'id DESC')
515 j = issue.journals.find(:first, :order => 'id DESC')
516 assert_equal 'Assigned to dlopper', j.notes
516 assert_equal 'Assigned to dlopper', j.notes
517 assert_equal 2, j.details.size
517 assert_equal 2, j.details.size
518
518
519 mail = ActionMailer::Base.deliveries.last
519 mail = ActionMailer::Base.deliveries.last
520 assert mail.body.include?("Status changed from New to Assigned")
520 assert mail.body.include?("Status changed from New to Assigned")
521 end
521 end
522
522
523 def test_post_edit_with_note_only
523 def test_post_edit_with_note_only
524 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
524 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
525 # anonymous user
525 # anonymous user
526 post :edit,
526 post :edit,
527 :id => 1,
527 :id => 1,
528 :notes => notes
528 :notes => notes
529 assert_redirected_to 'issues/show/1'
529 assert_redirected_to 'issues/show/1'
530 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
530 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
531 assert_equal notes, j.notes
531 assert_equal notes, j.notes
532 assert_equal 0, j.details.size
532 assert_equal 0, j.details.size
533 assert_equal User.anonymous, j.user
533 assert_equal User.anonymous, j.user
534
534
535 mail = ActionMailer::Base.deliveries.last
535 mail = ActionMailer::Base.deliveries.last
536 assert mail.body.include?(notes)
536 assert mail.body.include?(notes)
537 end
537 end
538
538
539 def test_post_edit_with_note_and_spent_time
539 def test_post_edit_with_note_and_spent_time
540 @request.session[:user_id] = 2
540 @request.session[:user_id] = 2
541 spent_hours_before = Issue.find(1).spent_hours
541 spent_hours_before = Issue.find(1).spent_hours
542 assert_difference('TimeEntry.count') do
542 assert_difference('TimeEntry.count') do
543 post :edit,
543 post :edit,
544 :id => 1,
544 :id => 1,
545 :notes => '2.5 hours added',
545 :notes => '2.5 hours added',
546 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
546 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
547 end
547 end
548 assert_redirected_to 'issues/show/1'
548 assert_redirected_to 'issues/show/1'
549
549
550 issue = Issue.find(1)
550 issue = Issue.find(1)
551
551
552 j = issue.journals.find(:first, :order => 'id DESC')
552 j = issue.journals.find(:first, :order => 'id DESC')
553 assert_equal '2.5 hours added', j.notes
553 assert_equal '2.5 hours added', j.notes
554 assert_equal 0, j.details.size
554 assert_equal 0, j.details.size
555
555
556 t = issue.time_entries.find(:first, :order => 'id DESC')
556 t = issue.time_entries.find(:first, :order => 'id DESC')
557 assert_not_nil t
557 assert_not_nil t
558 assert_equal 2.5, t.hours
558 assert_equal 2.5, t.hours
559 assert_equal spent_hours_before + 2.5, issue.spent_hours
559 assert_equal spent_hours_before + 2.5, issue.spent_hours
560 end
560 end
561
561
562 def test_post_edit_with_attachment_only
562 def test_post_edit_with_attachment_only
563 set_tmp_attachments_directory
563 set_tmp_attachments_directory
564
564
565 # Delete all fixtured journals, a race condition can occur causing the wrong
565 # Delete all fixtured journals, a race condition can occur causing the wrong
566 # journal to get fetched in the next find.
566 # journal to get fetched in the next find.
567 Journal.delete_all
567 Journal.delete_all
568
568
569 # anonymous user
569 # anonymous user
570 post :edit,
570 post :edit,
571 :id => 1,
571 :id => 1,
572 :notes => '',
572 :notes => '',
573 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
573 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
574 assert_redirected_to 'issues/show/1'
574 assert_redirected_to 'issues/show/1'
575 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
575 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
576 assert j.notes.blank?
576 assert j.notes.blank?
577 assert_equal 1, j.details.size
577 assert_equal 1, j.details.size
578 assert_equal 'testfile.txt', j.details.first.value
578 assert_equal 'testfile.txt', j.details.first.value
579 assert_equal User.anonymous, j.user
579 assert_equal User.anonymous, j.user
580
580
581 mail = ActionMailer::Base.deliveries.last
581 mail = ActionMailer::Base.deliveries.last
582 assert mail.body.include?('testfile.txt')
582 assert mail.body.include?('testfile.txt')
583 end
583 end
584
584
585 def test_post_edit_with_no_change
585 def test_post_edit_with_no_change
586 issue = Issue.find(1)
586 issue = Issue.find(1)
587 issue.journals.clear
587 issue.journals.clear
588 ActionMailer::Base.deliveries.clear
588 ActionMailer::Base.deliveries.clear
589
589
590 post :edit,
590 post :edit,
591 :id => 1,
591 :id => 1,
592 :notes => ''
592 :notes => ''
593 assert_redirected_to 'issues/show/1'
593 assert_redirected_to 'issues/show/1'
594
594
595 issue.reload
595 issue.reload
596 assert issue.journals.empty?
596 assert issue.journals.empty?
597 # No email should be sent
597 # No email should be sent
598 assert ActionMailer::Base.deliveries.empty?
598 assert ActionMailer::Base.deliveries.empty?
599 end
599 end
600
600
601 def test_post_edit_with_invalid_spent_time
601 def test_post_edit_with_invalid_spent_time
602 @request.session[:user_id] = 2
602 @request.session[:user_id] = 2
603 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
603 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
604
604
605 assert_no_difference('Journal.count') do
605 assert_no_difference('Journal.count') do
606 post :edit,
606 post :edit,
607 :id => 1,
607 :id => 1,
608 :notes => notes,
608 :notes => notes,
609 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
609 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
610 end
610 end
611 assert_response :success
611 assert_response :success
612 assert_template 'edit'
612 assert_template 'edit'
613
613
614 assert_tag :textarea, :attributes => { :name => 'notes' },
614 assert_tag :textarea, :attributes => { :name => 'notes' },
615 :content => notes
615 :content => notes
616 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
616 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
617 end
617 end
618
618
619 def test_bulk_edit
619 def test_bulk_edit
620 @request.session[:user_id] = 2
620 @request.session[:user_id] = 2
621 # update issues priority
621 # update issues priority
622 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
622 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
623 assert_response 302
623 assert_response 302
624 # check that the issues were updated
624 # check that the issues were updated
625 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
625 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
626 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
626 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
627 end
627 end
628
628
629 def test_bulk_unassign
629 def test_bulk_unassign
630 assert_not_nil Issue.find(2).assigned_to
630 assert_not_nil Issue.find(2).assigned_to
631 @request.session[:user_id] = 2
631 @request.session[:user_id] = 2
632 # unassign issues
632 # unassign issues
633 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
633 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
634 assert_response 302
634 assert_response 302
635 # check that the issues were updated
635 # check that the issues were updated
636 assert_nil Issue.find(2).assigned_to
636 assert_nil Issue.find(2).assigned_to
637 end
637 end
638
638
639 def test_move_one_issue_to_another_project
639 def test_move_one_issue_to_another_project
640 @request.session[:user_id] = 1
640 @request.session[:user_id] = 1
641 post :move, :id => 1, :new_project_id => 2
641 post :move, :id => 1, :new_project_id => 2
642 assert_redirected_to 'projects/ecookbook/issues'
642 assert_redirected_to 'projects/ecookbook/issues'
643 assert_equal 2, Issue.find(1).project_id
643 assert_equal 2, Issue.find(1).project_id
644 end
644 end
645
645
646 def test_bulk_move_to_another_project
646 def test_bulk_move_to_another_project
647 @request.session[:user_id] = 1
647 @request.session[:user_id] = 1
648 post :move, :ids => [1, 2], :new_project_id => 2
648 post :move, :ids => [1, 2], :new_project_id => 2
649 assert_redirected_to 'projects/ecookbook/issues'
649 assert_redirected_to 'projects/ecookbook/issues'
650 # Issues moved to project 2
650 # Issues moved to project 2
651 assert_equal 2, Issue.find(1).project_id
651 assert_equal 2, Issue.find(1).project_id
652 assert_equal 2, Issue.find(2).project_id
652 assert_equal 2, Issue.find(2).project_id
653 # No tracker change
653 # No tracker change
654 assert_equal 1, Issue.find(1).tracker_id
654 assert_equal 1, Issue.find(1).tracker_id
655 assert_equal 2, Issue.find(2).tracker_id
655 assert_equal 2, Issue.find(2).tracker_id
656 end
656 end
657
657
658 def test_bulk_move_to_another_tracker
658 def test_bulk_move_to_another_tracker
659 @request.session[:user_id] = 1
659 @request.session[:user_id] = 1
660 post :move, :ids => [1, 2], :new_tracker_id => 2
660 post :move, :ids => [1, 2], :new_tracker_id => 2
661 assert_redirected_to 'projects/ecookbook/issues'
661 assert_redirected_to 'projects/ecookbook/issues'
662 assert_equal 2, Issue.find(1).tracker_id
662 assert_equal 2, Issue.find(1).tracker_id
663 assert_equal 2, Issue.find(2).tracker_id
663 assert_equal 2, Issue.find(2).tracker_id
664 end
664 end
665
665
666 def test_context_menu_one_issue
666 def test_context_menu_one_issue
667 @request.session[:user_id] = 2
667 @request.session[:user_id] = 2
668 get :context_menu, :ids => [1]
668 get :context_menu, :ids => [1]
669 assert_response :success
669 assert_response :success
670 assert_template 'context_menu'
670 assert_template 'context_menu'
671 assert_tag :tag => 'a', :content => 'Edit',
671 assert_tag :tag => 'a', :content => 'Edit',
672 :attributes => { :href => '/issues/edit/1',
672 :attributes => { :href => '/issues/edit/1',
673 :class => 'icon-edit' }
673 :class => 'icon-edit' }
674 assert_tag :tag => 'a', :content => 'Closed',
674 assert_tag :tag => 'a', :content => 'Closed',
675 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
675 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
676 :class => '' }
676 :class => '' }
677 assert_tag :tag => 'a', :content => 'Immediate',
677 assert_tag :tag => 'a', :content => 'Immediate',
678 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
678 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;priority_id=8',
679 :class => '' }
679 :class => '' }
680 assert_tag :tag => 'a', :content => 'Dave Lopper',
680 assert_tag :tag => 'a', :content => 'Dave Lopper',
681 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
681 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1',
682 :class => '' }
682 :class => '' }
683 assert_tag :tag => 'a', :content => 'Copy',
683 assert_tag :tag => 'a', :content => 'Copy',
684 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
684 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
685 :class => 'icon-copy' }
685 :class => 'icon-copy' }
686 assert_tag :tag => 'a', :content => 'Move',
686 assert_tag :tag => 'a', :content => 'Move',
687 :attributes => { :href => '/issues/move?ids%5B%5D=1',
687 :attributes => { :href => '/issues/move?ids%5B%5D=1',
688 :class => 'icon-move' }
688 :class => 'icon-move' }
689 assert_tag :tag => 'a', :content => 'Delete',
689 assert_tag :tag => 'a', :content => 'Delete',
690 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
690 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
691 :class => 'icon-del' }
691 :class => 'icon-del' }
692 end
692 end
693
693
694 def test_context_menu_one_issue_by_anonymous
694 def test_context_menu_one_issue_by_anonymous
695 get :context_menu, :ids => [1]
695 get :context_menu, :ids => [1]
696 assert_response :success
696 assert_response :success
697 assert_template 'context_menu'
697 assert_template 'context_menu'
698 assert_tag :tag => 'a', :content => 'Delete',
698 assert_tag :tag => 'a', :content => 'Delete',
699 :attributes => { :href => '#',
699 :attributes => { :href => '#',
700 :class => 'icon-del disabled' }
700 :class => 'icon-del disabled' }
701 end
701 end
702
702
703 def test_context_menu_multiple_issues_of_same_project
703 def test_context_menu_multiple_issues_of_same_project
704 @request.session[:user_id] = 2
704 @request.session[:user_id] = 2
705 get :context_menu, :ids => [1, 2]
705 get :context_menu, :ids => [1, 2]
706 assert_response :success
706 assert_response :success
707 assert_template 'context_menu'
707 assert_template 'context_menu'
708 assert_tag :tag => 'a', :content => 'Edit',
708 assert_tag :tag => 'a', :content => 'Edit',
709 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
709 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
710 :class => 'icon-edit' }
710 :class => 'icon-edit' }
711 assert_tag :tag => 'a', :content => 'Immediate',
711 assert_tag :tag => 'a', :content => 'Immediate',
712 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
712 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2&amp;priority_id=8',
713 :class => '' }
713 :class => '' }
714 assert_tag :tag => 'a', :content => 'Dave Lopper',
714 assert_tag :tag => 'a', :content => 'Dave Lopper',
715 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
715 :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&amp;ids%5B%5D=1&amp;ids%5B%5D=2',
716 :class => '' }
716 :class => '' }
717 assert_tag :tag => 'a', :content => 'Move',
717 assert_tag :tag => 'a', :content => 'Move',
718 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
718 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
719 :class => 'icon-move' }
719 :class => 'icon-move' }
720 assert_tag :tag => 'a', :content => 'Delete',
720 assert_tag :tag => 'a', :content => 'Delete',
721 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
721 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
722 :class => 'icon-del' }
722 :class => 'icon-del' }
723 end
723 end
724
724
725 def test_context_menu_multiple_issues_of_different_project
725 def test_context_menu_multiple_issues_of_different_project
726 @request.session[:user_id] = 2
726 @request.session[:user_id] = 2
727 get :context_menu, :ids => [1, 2, 4]
727 get :context_menu, :ids => [1, 2, 4]
728 assert_response :success
728 assert_response :success
729 assert_template 'context_menu'
729 assert_template 'context_menu'
730 assert_tag :tag => 'a', :content => 'Delete',
730 assert_tag :tag => 'a', :content => 'Delete',
731 :attributes => { :href => '#',
731 :attributes => { :href => '#',
732 :class => 'icon-del disabled' }
732 :class => 'icon-del disabled' }
733 end
733 end
734
734
735 def test_destroy_issue_with_no_time_entries
735 def test_destroy_issue_with_no_time_entries
736 assert_nil TimeEntry.find_by_issue_id(2)
736 assert_nil TimeEntry.find_by_issue_id(2)
737 @request.session[:user_id] = 2
737 @request.session[:user_id] = 2
738 post :destroy, :id => 2
738 post :destroy, :id => 2
739 assert_redirected_to 'projects/ecookbook/issues'
739 assert_redirected_to 'projects/ecookbook/issues'
740 assert_nil Issue.find_by_id(2)
740 assert_nil Issue.find_by_id(2)
741 end
741 end
742
742
743 def test_destroy_issues_with_time_entries
743 def test_destroy_issues_with_time_entries
744 @request.session[:user_id] = 2
744 @request.session[:user_id] = 2
745 post :destroy, :ids => [1, 3]
745 post :destroy, :ids => [1, 3]
746 assert_response :success
746 assert_response :success
747 assert_template 'destroy'
747 assert_template 'destroy'
748 assert_not_nil assigns(:hours)
748 assert_not_nil assigns(:hours)
749 assert Issue.find_by_id(1) && Issue.find_by_id(3)
749 assert Issue.find_by_id(1) && Issue.find_by_id(3)
750 end
750 end
751
751
752 def test_destroy_issues_and_destroy_time_entries
752 def test_destroy_issues_and_destroy_time_entries
753 @request.session[:user_id] = 2
753 @request.session[:user_id] = 2
754 post :destroy, :ids => [1, 3], :todo => 'destroy'
754 post :destroy, :ids => [1, 3], :todo => 'destroy'
755 assert_redirected_to 'projects/ecookbook/issues'
755 assert_redirected_to 'projects/ecookbook/issues'
756 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
756 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
757 assert_nil TimeEntry.find_by_id([1, 2])
757 assert_nil TimeEntry.find_by_id([1, 2])
758 end
758 end
759
759
760 def test_destroy_issues_and_assign_time_entries_to_project
760 def test_destroy_issues_and_assign_time_entries_to_project
761 @request.session[:user_id] = 2
761 @request.session[:user_id] = 2
762 post :destroy, :ids => [1, 3], :todo => 'nullify'
762 post :destroy, :ids => [1, 3], :todo => 'nullify'
763 assert_redirected_to 'projects/ecookbook/issues'
763 assert_redirected_to 'projects/ecookbook/issues'
764 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
764 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
765 assert_nil TimeEntry.find(1).issue_id
765 assert_nil TimeEntry.find(1).issue_id
766 assert_nil TimeEntry.find(2).issue_id
766 assert_nil TimeEntry.find(2).issue_id
767 end
767 end
768
768
769 def test_destroy_issues_and_reassign_time_entries_to_another_issue
769 def test_destroy_issues_and_reassign_time_entries_to_another_issue
770 @request.session[:user_id] = 2
770 @request.session[:user_id] = 2
771 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
771 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
772 assert_redirected_to 'projects/ecookbook/issues'
772 assert_redirected_to 'projects/ecookbook/issues'
773 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
773 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
774 assert_equal 2, TimeEntry.find(1).issue_id
774 assert_equal 2, TimeEntry.find(1).issue_id
775 assert_equal 2, TimeEntry.find(2).issue_id
775 assert_equal 2, TimeEntry.find(2).issue_id
776 end
776 end
777 end
777 end
General Comments 0
You need to be logged in to leave comments. Login now