##// END OF EJS Templates
File viewer for attached text files....
Jean-Philippe Lang -
r1506:80a7486f95a3
parent child
Show More
@@ -0,0 +1,15
1 <h2><%=h @attachment.filename %></h2>
2
3 <div class="attachments">
4 <p><%= h("#{@attachment.description} - ") unless @attachment.description.blank? %>
5 <span class="author"><%= @attachment.author %>, <%= format_time(@attachment.created_on) %></span></p>
6 <p><%= link_to l(:button_download), {:controller => 'attachments', :action => 'download', :id => @attachment } -%>
7 <span class="size">(<%= number_to_human_size @attachment.filesize %>)</span></p>
8
9 </div>
10 &nbsp;
11 <%= render :partial => 'common/file', :locals => {:content => @content, :filename => @attachment.filename} %>
12
13 <% content_for :header_tags do -%>
14 <%= stylesheet_link_tag "scm" -%>
15 <% end -%>
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,13
1 Index: trunk/app/controllers/issues_controller.rb
2 ===================================================================
3 --- trunk/app/controllers/issues_controller.rb (rοΏ½vision 1483)
4 +++ trunk/app/controllers/issues_controller.rb (rοΏ½vision 1484)
5 @@ -149,7 +149,7 @@
6 attach_files(@issue, params[:attachments])
7 flash[:notice] = l(:notice_successful_create)
8 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
9 - redirect_to :controller => 'issues', :action => 'show', :id => @issue, :project_id => @project
10 + redirect_to :controller => 'issues', :action => 'show', :id => @issue
11 return
12 end
13 end
@@ -0,0 +1,10
1 # The Greeter class
2 class Greeter
3 def initialize(name)
4 @name = name.capitalize
5 end
6
7 def salute
8 puts "Hello #{@name}!"
9 end
10 end
@@ -0,0 +1,59
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'attachments_controller'
20
21 # Re-raise errors caught by the controller.
22 class AttachmentsController; def rescue_action(e) raise e end; end
23
24
25 class AttachmentsControllerTest < Test::Unit::TestCase
26 fixtures :users, :projects, :issues, :attachments
27
28 def setup
29 @controller = AttachmentsController.new
30 @request = ActionController::TestRequest.new
31 @response = ActionController::TestResponse.new
32 Attachment.storage_path = "#{RAILS_ROOT}/test/fixtures/files"
33 User.current = nil
34 end
35
36 def test_show_diff
37 get :show, :id => 5
38 assert_response :success
39 assert_template 'diff'
40 end
41
42 def test_show_text_file
43 get :show, :id => 4
44 assert_response :success
45 assert_template 'file'
46 end
47
48 def test_show_other
49 get :show, :id => 6
50 assert_response :success
51 assert_equal 'application/octet-stream', @response.content_type
52 end
53
54 def test_download_text_file
55 get :download, :id => 4
56 assert_response :success
57 assert_equal 'application/x-ruby', @response.content_type
58 end
59 end
@@ -1,46 +1,49
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class AttachmentsController < ApplicationController
18 class AttachmentsController < ApplicationController
19 layout 'base'
19 layout 'base'
20 before_filter :find_project, :check_project_privacy
20 before_filter :find_project, :check_project_privacy
21
21
22 def show
22 def show
23 if @attachment.is_diff?
23 if @attachment.is_diff?
24 @diff = File.new(@attachment.diskfile, "rb").read
24 @diff = File.new(@attachment.diskfile, "rb").read
25 render :action => 'diff'
25 render :action => 'diff'
26 else
26 elsif @attachment.is_text?
27 @content = File.new(@attachment.diskfile, "rb").read
28 render :action => 'file'
29 elsif
27 download
30 download
28 end
31 end
29 end
32 end
30
33
31 def download
34 def download
32 # images are sent inline
35 # images are sent inline
33 send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
36 send_file @attachment.diskfile, :filename => filename_for_content_disposition(@attachment.filename),
34 :type => @attachment.content_type,
37 :type => @attachment.content_type,
35 :disposition => (@attachment.image? ? 'inline' : 'attachment')
38 :disposition => (@attachment.image? ? 'inline' : 'attachment')
36 end
39 end
37
40
38 private
41 private
39 def find_project
42 def find_project
40 @attachment = Attachment.find(params[:id])
43 @attachment = Attachment.find(params[:id])
41 render_404 and return false unless File.readable?(@attachment.diskfile)
44 #render_404 and return false unless File.readable?(@attachment.diskfile)
42 @project = @attachment.project
45 @project = @attachment.project
43 rescue
46 #rescue
44 render_404
47 # render_404
45 end
48 end
46 end
49 end
@@ -1,517 +1,525
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'coderay'
19 require 'coderay/helpers/file_type'
20
18 module ApplicationHelper
21 module ApplicationHelper
19 include Redmine::WikiFormatting::Macros::Definitions
22 include Redmine::WikiFormatting::Macros::Definitions
20
23
21 def current_role
24 def current_role
22 @current_role ||= User.current.role_for_project(@project)
25 @current_role ||= User.current.role_for_project(@project)
23 end
26 end
24
27
25 # Return true if user is authorized for controller/action, otherwise false
28 # Return true if user is authorized for controller/action, otherwise false
26 def authorize_for(controller, action)
29 def authorize_for(controller, action)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
30 User.current.allowed_to?({:controller => controller, :action => action}, @project)
28 end
31 end
29
32
30 # Display a link if user is authorized
33 # Display a link if user is authorized
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
34 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
35 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
33 end
36 end
34
37
35 # Display a link to user's account page
38 # Display a link to user's account page
36 def link_to_user(user)
39 def link_to_user(user)
37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
40 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
38 end
41 end
39
42
40 def link_to_issue(issue, options={})
43 def link_to_issue(issue, options={})
41 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
44 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
42 end
45 end
43
46
44 def toggle_link(name, id, options={})
47 def toggle_link(name, id, options={})
45 onclick = "Element.toggle('#{id}'); "
48 onclick = "Element.toggle('#{id}'); "
46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
49 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
47 onclick << "return false;"
50 onclick << "return false;"
48 link_to(name, "#", :onclick => onclick)
51 link_to(name, "#", :onclick => onclick)
49 end
52 end
50
53
51 def show_and_goto_link(name, id, options={})
54 def show_and_goto_link(name, id, options={})
52 onclick = "Element.show('#{id}'); "
55 onclick = "Element.show('#{id}'); "
53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
56 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
54 onclick << "Element.scrollTo('#{id}'); "
57 onclick << "Element.scrollTo('#{id}'); "
55 onclick << "return false;"
58 onclick << "return false;"
56 link_to(name, "#", options.merge(:onclick => onclick))
59 link_to(name, "#", options.merge(:onclick => onclick))
57 end
60 end
58
61
59 def image_to_function(name, function, html_options = {})
62 def image_to_function(name, function, html_options = {})
60 html_options.symbolize_keys!
63 html_options.symbolize_keys!
61 tag(:input, html_options.merge({
64 tag(:input, html_options.merge({
62 :type => "image", :src => image_path(name),
65 :type => "image", :src => image_path(name),
63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
66 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
64 }))
67 }))
65 end
68 end
66
69
67 def prompt_to_remote(name, text, param, url, html_options = {})
70 def prompt_to_remote(name, text, param, url, html_options = {})
68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
71 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
69 link_to name, {}, html_options
72 link_to name, {}, html_options
70 end
73 end
71
74
72 def format_date(date)
75 def format_date(date)
73 return nil unless date
76 return nil unless date
74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
77 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
78 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
76 date.strftime(@date_format)
79 date.strftime(@date_format)
77 end
80 end
78
81
79 def format_time(time, include_date = true)
82 def format_time(time, include_date = true)
80 return nil unless time
83 return nil unless time
81 time = time.to_time if time.is_a?(String)
84 time = time.to_time if time.is_a?(String)
82 zone = User.current.time_zone
85 zone = User.current.time_zone
83 if time.utc?
86 if time.utc?
84 local = zone ? zone.adjust(time) : time.getlocal
87 local = zone ? zone.adjust(time) : time.getlocal
85 else
88 else
86 local = zone ? zone.adjust(time.getutc) : time
89 local = zone ? zone.adjust(time.getutc) : time
87 end
90 end
88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
91 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
92 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
93 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
91 end
94 end
92
95
93 # Truncates and returns the string as a single line
96 # Truncates and returns the string as a single line
94 def truncate_single_line(string, *args)
97 def truncate_single_line(string, *args)
95 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
98 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
96 end
99 end
97
100
98 def html_hours(text)
101 def html_hours(text)
99 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
102 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
100 end
103 end
101
104
102 def authoring(created, author)
105 def authoring(created, author)
103 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
106 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
104 l(:label_added_time_by, author || 'Anonymous', time_tag)
107 l(:label_added_time_by, author || 'Anonymous', time_tag)
105 end
108 end
106
109
107 def l_or_humanize(s)
110 def l_or_humanize(s)
108 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
111 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
109 end
112 end
110
113
111 def day_name(day)
114 def day_name(day)
112 l(:general_day_names).split(',')[day-1]
115 l(:general_day_names).split(',')[day-1]
113 end
116 end
114
117
115 def month_name(month)
118 def month_name(month)
116 l(:actionview_datehelper_select_month_names).split(',')[month-1]
119 l(:actionview_datehelper_select_month_names).split(',')[month-1]
117 end
120 end
118
121
122 def syntax_highlight(name, content)
123 type = CodeRay::FileType[name]
124 type ? CodeRay.scan(content, type).html : h(content)
125 end
126
119 def pagination_links_full(paginator, count=nil, options={})
127 def pagination_links_full(paginator, count=nil, options={})
120 page_param = options.delete(:page_param) || :page
128 page_param = options.delete(:page_param) || :page
121 url_param = params.dup
129 url_param = params.dup
122 # don't reuse params if filters are present
130 # don't reuse params if filters are present
123 url_param.clear if url_param.has_key?(:set_filter)
131 url_param.clear if url_param.has_key?(:set_filter)
124
132
125 html = ''
133 html = ''
126 html << link_to_remote(('&#171; ' + l(:label_previous)),
134 html << link_to_remote(('&#171; ' + l(:label_previous)),
127 {:update => 'content',
135 {:update => 'content',
128 :url => url_param.merge(page_param => paginator.current.previous),
136 :url => url_param.merge(page_param => paginator.current.previous),
129 :complete => 'window.scrollTo(0,0)'},
137 :complete => 'window.scrollTo(0,0)'},
130 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
138 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
131
139
132 html << (pagination_links_each(paginator, options) do |n|
140 html << (pagination_links_each(paginator, options) do |n|
133 link_to_remote(n.to_s,
141 link_to_remote(n.to_s,
134 {:url => {:params => url_param.merge(page_param => n)},
142 {:url => {:params => url_param.merge(page_param => n)},
135 :update => 'content',
143 :update => 'content',
136 :complete => 'window.scrollTo(0,0)'},
144 :complete => 'window.scrollTo(0,0)'},
137 {:href => url_for(:params => url_param.merge(page_param => n))})
145 {:href => url_for(:params => url_param.merge(page_param => n))})
138 end || '')
146 end || '')
139
147
140 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
148 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
141 {:update => 'content',
149 {:update => 'content',
142 :url => url_param.merge(page_param => paginator.current.next),
150 :url => url_param.merge(page_param => paginator.current.next),
143 :complete => 'window.scrollTo(0,0)'},
151 :complete => 'window.scrollTo(0,0)'},
144 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
152 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
145
153
146 unless count.nil?
154 unless count.nil?
147 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
155 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
148 end
156 end
149
157
150 html
158 html
151 end
159 end
152
160
153 def per_page_links(selected=nil)
161 def per_page_links(selected=nil)
154 url_param = params.dup
162 url_param = params.dup
155 url_param.clear if url_param.has_key?(:set_filter)
163 url_param.clear if url_param.has_key?(:set_filter)
156
164
157 links = Setting.per_page_options_array.collect do |n|
165 links = Setting.per_page_options_array.collect do |n|
158 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
166 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
159 {:href => url_for(url_param.merge(:per_page => n))})
167 {:href => url_for(url_param.merge(:per_page => n))})
160 end
168 end
161 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
169 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
162 end
170 end
163
171
164 def breadcrumb(*args)
172 def breadcrumb(*args)
165 content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
173 content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
166 end
174 end
167
175
168 def html_title(*args)
176 def html_title(*args)
169 if args.empty?
177 if args.empty?
170 title = []
178 title = []
171 title << @project.name if @project
179 title << @project.name if @project
172 title += @html_title if @html_title
180 title += @html_title if @html_title
173 title << Setting.app_title
181 title << Setting.app_title
174 title.compact.join(' - ')
182 title.compact.join(' - ')
175 else
183 else
176 @html_title ||= []
184 @html_title ||= []
177 @html_title += args
185 @html_title += args
178 end
186 end
179 end
187 end
180
188
181 def accesskey(s)
189 def accesskey(s)
182 Redmine::AccessKeys.key_for s
190 Redmine::AccessKeys.key_for s
183 end
191 end
184
192
185 # Formats text according to system settings.
193 # Formats text according to system settings.
186 # 2 ways to call this method:
194 # 2 ways to call this method:
187 # * with a String: textilizable(text, options)
195 # * with a String: textilizable(text, options)
188 # * with an object and one of its attribute: textilizable(issue, :description, options)
196 # * with an object and one of its attribute: textilizable(issue, :description, options)
189 def textilizable(*args)
197 def textilizable(*args)
190 options = args.last.is_a?(Hash) ? args.pop : {}
198 options = args.last.is_a?(Hash) ? args.pop : {}
191 case args.size
199 case args.size
192 when 1
200 when 1
193 obj = nil
201 obj = nil
194 text = args.shift
202 text = args.shift
195 when 2
203 when 2
196 obj = args.shift
204 obj = args.shift
197 text = obj.send(args.shift).to_s
205 text = obj.send(args.shift).to_s
198 else
206 else
199 raise ArgumentError, 'invalid arguments to textilizable'
207 raise ArgumentError, 'invalid arguments to textilizable'
200 end
208 end
201 return '' if text.blank?
209 return '' if text.blank?
202
210
203 only_path = options.delete(:only_path) == false ? false : true
211 only_path = options.delete(:only_path) == false ? false : true
204
212
205 # when using an image link, try to use an attachment, if possible
213 # when using an image link, try to use an attachment, if possible
206 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
214 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
207
215
208 if attachments
216 if attachments
209 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
217 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
210 style = $1
218 style = $1
211 filename = $6
219 filename = $6
212 rf = Regexp.new(filename, Regexp::IGNORECASE)
220 rf = Regexp.new(filename, Regexp::IGNORECASE)
213 # search for the picture in attachments
221 # search for the picture in attachments
214 if found = attachments.detect { |att| att.filename =~ rf }
222 if found = attachments.detect { |att| att.filename =~ rf }
215 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
223 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
216 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
224 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
217 alt = desc.blank? ? nil : "(#{desc})"
225 alt = desc.blank? ? nil : "(#{desc})"
218 "!#{style}#{image_url}#{alt}!"
226 "!#{style}#{image_url}#{alt}!"
219 else
227 else
220 "!#{style}#{filename}!"
228 "!#{style}#{filename}!"
221 end
229 end
222 end
230 end
223 end
231 end
224
232
225 text = (Setting.text_formatting == 'textile') ?
233 text = (Setting.text_formatting == 'textile') ?
226 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
234 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
227 simple_format(auto_link(h(text)))
235 simple_format(auto_link(h(text)))
228
236
229 # different methods for formatting wiki links
237 # different methods for formatting wiki links
230 case options[:wiki_links]
238 case options[:wiki_links]
231 when :local
239 when :local
232 # used for local links to html files
240 # used for local links to html files
233 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
241 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
234 when :anchor
242 when :anchor
235 # used for single-file wiki export
243 # used for single-file wiki export
236 format_wiki_link = Proc.new {|project, title| "##{title}" }
244 format_wiki_link = Proc.new {|project, title| "##{title}" }
237 else
245 else
238 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
246 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
239 end
247 end
240
248
241 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
249 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
242
250
243 # Wiki links
251 # Wiki links
244 #
252 #
245 # Examples:
253 # Examples:
246 # [[mypage]]
254 # [[mypage]]
247 # [[mypage|mytext]]
255 # [[mypage|mytext]]
248 # wiki links can refer other project wikis, using project name or identifier:
256 # wiki links can refer other project wikis, using project name or identifier:
249 # [[project:]] -> wiki starting page
257 # [[project:]] -> wiki starting page
250 # [[project:|mytext]]
258 # [[project:|mytext]]
251 # [[project:mypage]]
259 # [[project:mypage]]
252 # [[project:mypage|mytext]]
260 # [[project:mypage|mytext]]
253 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
261 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
254 link_project = project
262 link_project = project
255 esc, all, page, title = $1, $2, $3, $5
263 esc, all, page, title = $1, $2, $3, $5
256 if esc.nil?
264 if esc.nil?
257 if page =~ /^([^\:]+)\:(.*)$/
265 if page =~ /^([^\:]+)\:(.*)$/
258 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
266 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
259 page = $2
267 page = $2
260 title ||= $1 if page.blank?
268 title ||= $1 if page.blank?
261 end
269 end
262
270
263 if link_project && link_project.wiki
271 if link_project && link_project.wiki
264 # check if page exists
272 # check if page exists
265 wiki_page = link_project.wiki.find_page(page)
273 wiki_page = link_project.wiki.find_page(page)
266 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
274 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
267 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
275 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
268 else
276 else
269 # project or wiki doesn't exist
277 # project or wiki doesn't exist
270 title || page
278 title || page
271 end
279 end
272 else
280 else
273 all
281 all
274 end
282 end
275 end
283 end
276
284
277 # Redmine links
285 # Redmine links
278 #
286 #
279 # Examples:
287 # Examples:
280 # Issues:
288 # Issues:
281 # #52 -> Link to issue #52
289 # #52 -> Link to issue #52
282 # Changesets:
290 # Changesets:
283 # r52 -> Link to revision 52
291 # r52 -> Link to revision 52
284 # commit:a85130f -> Link to scmid starting with a85130f
292 # commit:a85130f -> Link to scmid starting with a85130f
285 # Documents:
293 # Documents:
286 # document#17 -> Link to document with id 17
294 # document#17 -> Link to document with id 17
287 # document:Greetings -> Link to the document with title "Greetings"
295 # document:Greetings -> Link to the document with title "Greetings"
288 # document:"Some document" -> Link to the document with title "Some document"
296 # document:"Some document" -> Link to the document with title "Some document"
289 # Versions:
297 # Versions:
290 # version#3 -> Link to version with id 3
298 # version#3 -> Link to version with id 3
291 # version:1.0.0 -> Link to version named "1.0.0"
299 # version:1.0.0 -> Link to version named "1.0.0"
292 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
300 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
293 # Attachments:
301 # Attachments:
294 # attachment:file.zip -> Link to the attachment of the current object named file.zip
302 # attachment:file.zip -> Link to the attachment of the current object named file.zip
295 # Source files:
303 # Source files:
296 # source:some/file -> Link to the file located at /some/file in the project's repository
304 # source:some/file -> Link to the file located at /some/file in the project's repository
297 # source:some/file@52 -> Link to the file's revision 52
305 # source:some/file@52 -> Link to the file's revision 52
298 # source:some/file#L120 -> Link to line 120 of the file
306 # source:some/file#L120 -> Link to line 120 of the file
299 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
307 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
300 # export:some/file -> Force the download of the file
308 # export:some/file -> Force the download of the file
301 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
309 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
302 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
310 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
303 link = nil
311 link = nil
304 if esc.nil?
312 if esc.nil?
305 if prefix.nil? && sep == 'r'
313 if prefix.nil? && sep == 'r'
306 if project && (changeset = project.changesets.find_by_revision(oid))
314 if project && (changeset = project.changesets.find_by_revision(oid))
307 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
315 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
308 :class => 'changeset',
316 :class => 'changeset',
309 :title => truncate_single_line(changeset.comments, 100))
317 :title => truncate_single_line(changeset.comments, 100))
310 end
318 end
311 elsif sep == '#'
319 elsif sep == '#'
312 oid = oid.to_i
320 oid = oid.to_i
313 case prefix
321 case prefix
314 when nil
322 when nil
315 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
323 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
316 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
324 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
317 :class => (issue.closed? ? 'issue closed' : 'issue'),
325 :class => (issue.closed? ? 'issue closed' : 'issue'),
318 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
326 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
319 link = content_tag('del', link) if issue.closed?
327 link = content_tag('del', link) if issue.closed?
320 end
328 end
321 when 'document'
329 when 'document'
322 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
330 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
323 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
331 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
324 :class => 'document'
332 :class => 'document'
325 end
333 end
326 when 'version'
334 when 'version'
327 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
335 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
328 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
336 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
329 :class => 'version'
337 :class => 'version'
330 end
338 end
331 end
339 end
332 elsif sep == ':'
340 elsif sep == ':'
333 # removes the double quotes if any
341 # removes the double quotes if any
334 name = oid.gsub(%r{^"(.*)"$}, "\\1")
342 name = oid.gsub(%r{^"(.*)"$}, "\\1")
335 case prefix
343 case prefix
336 when 'document'
344 when 'document'
337 if project && document = project.documents.find_by_title(name)
345 if project && document = project.documents.find_by_title(name)
338 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
346 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
339 :class => 'document'
347 :class => 'document'
340 end
348 end
341 when 'version'
349 when 'version'
342 if project && version = project.versions.find_by_name(name)
350 if project && version = project.versions.find_by_name(name)
343 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
351 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
344 :class => 'version'
352 :class => 'version'
345 end
353 end
346 when 'commit'
354 when 'commit'
347 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
355 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
348 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
356 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
349 :class => 'changeset',
357 :class => 'changeset',
350 :title => truncate_single_line(changeset.comments, 100)
358 :title => truncate_single_line(changeset.comments, 100)
351 end
359 end
352 when 'source', 'export'
360 when 'source', 'export'
353 if project && project.repository
361 if project && project.repository
354 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
362 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
355 path, rev, anchor = $1, $3, $5
363 path, rev, anchor = $1, $3, $5
356 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
364 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
357 :rev => rev,
365 :rev => rev,
358 :anchor => anchor,
366 :anchor => anchor,
359 :format => (prefix == 'export' ? 'raw' : nil)},
367 :format => (prefix == 'export' ? 'raw' : nil)},
360 :class => (prefix == 'export' ? 'source download' : 'source')
368 :class => (prefix == 'export' ? 'source download' : 'source')
361 end
369 end
362 when 'attachment'
370 when 'attachment'
363 if attachments && attachment = attachments.detect {|a| a.filename == name }
371 if attachments && attachment = attachments.detect {|a| a.filename == name }
364 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
372 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
365 :class => 'attachment'
373 :class => 'attachment'
366 end
374 end
367 end
375 end
368 end
376 end
369 end
377 end
370 leading + (link || "#{prefix}#{sep}#{oid}")
378 leading + (link || "#{prefix}#{sep}#{oid}")
371 end
379 end
372
380
373 text
381 text
374 end
382 end
375
383
376 # Same as Rails' simple_format helper without using paragraphs
384 # Same as Rails' simple_format helper without using paragraphs
377 def simple_format_without_paragraph(text)
385 def simple_format_without_paragraph(text)
378 text.to_s.
386 text.to_s.
379 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
387 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
380 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
388 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
381 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
389 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
382 end
390 end
383
391
384 def error_messages_for(object_name, options = {})
392 def error_messages_for(object_name, options = {})
385 options = options.symbolize_keys
393 options = options.symbolize_keys
386 object = instance_variable_get("@#{object_name}")
394 object = instance_variable_get("@#{object_name}")
387 if object && !object.errors.empty?
395 if object && !object.errors.empty?
388 # build full_messages here with controller current language
396 # build full_messages here with controller current language
389 full_messages = []
397 full_messages = []
390 object.errors.each do |attr, msg|
398 object.errors.each do |attr, msg|
391 next if msg.nil?
399 next if msg.nil?
392 msg = msg.first if msg.is_a? Array
400 msg = msg.first if msg.is_a? Array
393 if attr == "base"
401 if attr == "base"
394 full_messages << l(msg)
402 full_messages << l(msg)
395 else
403 else
396 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
404 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
397 end
405 end
398 end
406 end
399 # retrieve custom values error messages
407 # retrieve custom values error messages
400 if object.errors[:custom_values]
408 if object.errors[:custom_values]
401 object.custom_values.each do |v|
409 object.custom_values.each do |v|
402 v.errors.each do |attr, msg|
410 v.errors.each do |attr, msg|
403 next if msg.nil?
411 next if msg.nil?
404 msg = msg.first if msg.is_a? Array
412 msg = msg.first if msg.is_a? Array
405 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
413 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
406 end
414 end
407 end
415 end
408 end
416 end
409 content_tag("div",
417 content_tag("div",
410 content_tag(
418 content_tag(
411 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
419 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
412 ) +
420 ) +
413 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
421 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
414 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
422 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
415 )
423 )
416 else
424 else
417 ""
425 ""
418 end
426 end
419 end
427 end
420
428
421 def lang_options_for_select(blank=true)
429 def lang_options_for_select(blank=true)
422 (blank ? [["(auto)", ""]] : []) +
430 (blank ? [["(auto)", ""]] : []) +
423 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
431 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
424 end
432 end
425
433
426 def label_tag_for(name, option_tags = nil, options = {})
434 def label_tag_for(name, option_tags = nil, options = {})
427 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
435 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
428 content_tag("label", label_text)
436 content_tag("label", label_text)
429 end
437 end
430
438
431 def labelled_tabular_form_for(name, object, options, &proc)
439 def labelled_tabular_form_for(name, object, options, &proc)
432 options[:html] ||= {}
440 options[:html] ||= {}
433 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
441 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
434 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
442 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
435 end
443 end
436
444
437 def back_url_hidden_field_tag
445 def back_url_hidden_field_tag
438 hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER'])
446 hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER'])
439 end
447 end
440
448
441 def check_all_links(form_name)
449 def check_all_links(form_name)
442 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
450 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
443 " | " +
451 " | " +
444 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
452 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
445 end
453 end
446
454
447 def progress_bar(pcts, options={})
455 def progress_bar(pcts, options={})
448 pcts = [pcts, pcts] unless pcts.is_a?(Array)
456 pcts = [pcts, pcts] unless pcts.is_a?(Array)
449 pcts[1] = pcts[1] - pcts[0]
457 pcts[1] = pcts[1] - pcts[0]
450 pcts << (100 - pcts[1] - pcts[0])
458 pcts << (100 - pcts[1] - pcts[0])
451 width = options[:width] || '100px;'
459 width = options[:width] || '100px;'
452 legend = options[:legend] || ''
460 legend = options[:legend] || ''
453 content_tag('table',
461 content_tag('table',
454 content_tag('tr',
462 content_tag('tr',
455 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
463 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
456 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
464 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
457 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
465 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
458 ), :class => 'progress', :style => "width: #{width};") +
466 ), :class => 'progress', :style => "width: #{width};") +
459 content_tag('p', legend, :class => 'pourcent')
467 content_tag('p', legend, :class => 'pourcent')
460 end
468 end
461
469
462 def context_menu_link(name, url, options={})
470 def context_menu_link(name, url, options={})
463 options[:class] ||= ''
471 options[:class] ||= ''
464 if options.delete(:selected)
472 if options.delete(:selected)
465 options[:class] << ' icon-checked disabled'
473 options[:class] << ' icon-checked disabled'
466 options[:disabled] = true
474 options[:disabled] = true
467 end
475 end
468 if options.delete(:disabled)
476 if options.delete(:disabled)
469 options.delete(:method)
477 options.delete(:method)
470 options.delete(:confirm)
478 options.delete(:confirm)
471 options.delete(:onclick)
479 options.delete(:onclick)
472 options[:class] << ' disabled'
480 options[:class] << ' disabled'
473 url = '#'
481 url = '#'
474 end
482 end
475 link_to name, url, options
483 link_to name, url, options
476 end
484 end
477
485
478 def calendar_for(field_id)
486 def calendar_for(field_id)
479 include_calendar_headers_tags
487 include_calendar_headers_tags
480 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
488 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
481 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
489 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
482 end
490 end
483
491
484 def include_calendar_headers_tags
492 def include_calendar_headers_tags
485 unless @calendar_headers_tags_included
493 unless @calendar_headers_tags_included
486 @calendar_headers_tags_included = true
494 @calendar_headers_tags_included = true
487 content_for :header_tags do
495 content_for :header_tags do
488 javascript_include_tag('calendar/calendar') +
496 javascript_include_tag('calendar/calendar') +
489 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
497 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
490 javascript_include_tag('calendar/calendar-setup') +
498 javascript_include_tag('calendar/calendar-setup') +
491 stylesheet_link_tag('calendar')
499 stylesheet_link_tag('calendar')
492 end
500 end
493 end
501 end
494 end
502 end
495
503
496 def wikitoolbar_for(field_id)
504 def wikitoolbar_for(field_id)
497 return '' unless Setting.text_formatting == 'textile'
505 return '' unless Setting.text_formatting == 'textile'
498
506
499 help_link = l(:setting_text_formatting) + ': ' +
507 help_link = l(:setting_text_formatting) + ': ' +
500 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
508 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
501 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
509 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
502
510
503 javascript_include_tag('jstoolbar/jstoolbar') +
511 javascript_include_tag('jstoolbar/jstoolbar') +
504 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
512 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
505 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
513 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
506 end
514 end
507
515
508 def content_for(name, content = nil, &block)
516 def content_for(name, content = nil, &block)
509 @has_content ||= {}
517 @has_content ||= {}
510 @has_content[name] = true
518 @has_content[name] = true
511 super(name, content, &block)
519 super(name, content, &block)
512 end
520 end
513
521
514 def has_content?(name)
522 def has_content?(name)
515 (@has_content && @has_content[name]) || false
523 (@has_content && @has_content[name]) || false
516 end
524 end
517 end
525 end
@@ -1,105 +1,98
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 'coderay'
19 require 'coderay/helpers/file_type'
20 require 'iconv'
18 require 'iconv'
21
19
22 module RepositoriesHelper
20 module RepositoriesHelper
23 def syntax_highlight(name, content)
24 type = CodeRay::FileType[name]
25 type ? CodeRay.scan(content, type).html : h(content)
26 end
27
28 def format_revision(txt)
21 def format_revision(txt)
29 txt.to_s[0,8]
22 txt.to_s[0,8]
30 end
23 end
31
24
32 def to_utf8(str)
25 def to_utf8(str)
33 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
26 return str if /\A[\r\n\t\x20-\x7e]*\Z/n.match(str) # for us-ascii
34 @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
27 @encodings ||= Setting.repositories_encodings.split(',').collect(&:strip)
35 @encodings.each do |encoding|
28 @encodings.each do |encoding|
36 begin
29 begin
37 return Iconv.conv('UTF-8', encoding, str)
30 return Iconv.conv('UTF-8', encoding, str)
38 rescue Iconv::Failure
31 rescue Iconv::Failure
39 # do nothing here and try the next encoding
32 # do nothing here and try the next encoding
40 end
33 end
41 end
34 end
42 str
35 str
43 end
36 end
44
37
45 def repository_field_tags(form, repository)
38 def repository_field_tags(form, repository)
46 method = repository.class.name.demodulize.underscore + "_field_tags"
39 method = repository.class.name.demodulize.underscore + "_field_tags"
47 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
40 send(method, form, repository) if repository.is_a?(Repository) && respond_to?(method)
48 end
41 end
49
42
50 def scm_select_tag(repository)
43 def scm_select_tag(repository)
51 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
44 scm_options = [["--- #{l(:actionview_instancetag_blank_option)} ---", '']]
52 REDMINE_SUPPORTED_SCM.each do |scm|
45 REDMINE_SUPPORTED_SCM.each do |scm|
53 scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
46 scm_options << ["Repository::#{scm}".constantize.scm_name, scm] if Setting.enabled_scm.include?(scm) || (repository && repository.class.name.demodulize == scm)
54 end
47 end
55
48
56 select_tag('repository_scm',
49 select_tag('repository_scm',
57 options_for_select(scm_options, repository.class.name.demodulize),
50 options_for_select(scm_options, repository.class.name.demodulize),
58 :disabled => (repository && !repository.new_record?),
51 :disabled => (repository && !repository.new_record?),
59 :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
52 :onchange => remote_function(:url => { :controller => 'repositories', :action => 'edit', :id => @project }, :method => :get, :with => "Form.serialize(this.form)")
60 )
53 )
61 end
54 end
62
55
63 def with_leading_slash(path)
56 def with_leading_slash(path)
64 path.to_s.starts_with?('/') ? path : "/#{path}"
57 path.to_s.starts_with?('/') ? path : "/#{path}"
65 end
58 end
66
59
67 def without_leading_slash(path)
60 def without_leading_slash(path)
68 path.gsub(%r{^/+}, '')
61 path.gsub(%r{^/+}, '')
69 end
62 end
70
63
71 def subversion_field_tags(form, repository)
64 def subversion_field_tags(form, repository)
72 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
65 content_tag('p', form.text_field(:url, :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)) +
73 '<br />(http://, https://, svn://, file:///)') +
66 '<br />(http://, https://, svn://, file:///)') +
74 content_tag('p', form.text_field(:login, :size => 30)) +
67 content_tag('p', form.text_field(:login, :size => 30)) +
75 content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
68 content_tag('p', form.password_field(:password, :size => 30, :name => 'ignore',
76 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
69 :value => ((repository.new_record? || repository.password.blank?) ? '' : ('x'*15)),
77 :onfocus => "this.value=''; this.name='repository[password]';",
70 :onfocus => "this.value=''; this.name='repository[password]';",
78 :onchange => "this.name='repository[password]';"))
71 :onchange => "this.name='repository[password]';"))
79 end
72 end
80
73
81 def darcs_field_tags(form, repository)
74 def darcs_field_tags(form, repository)
82 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
75 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
83 end
76 end
84
77
85 def mercurial_field_tags(form, repository)
78 def mercurial_field_tags(form, repository)
86 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
79 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
87 end
80 end
88
81
89 def git_field_tags(form, repository)
82 def git_field_tags(form, repository)
90 content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
83 content_tag('p', form.text_field(:url, :label => 'Path to .git directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
91 end
84 end
92
85
93 def cvs_field_tags(form, repository)
86 def cvs_field_tags(form, repository)
94 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
87 content_tag('p', form.text_field(:root_url, :label => 'CVSROOT', :size => 60, :required => true, :disabled => !repository.new_record?)) +
95 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
88 content_tag('p', form.text_field(:url, :label => 'Module', :size => 30, :required => true, :disabled => !repository.new_record?))
96 end
89 end
97
90
98 def bazaar_field_tags(form, repository)
91 def bazaar_field_tags(form, repository)
99 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
92 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.new_record?)))
100 end
93 end
101
94
102 def filesystem_field_tags(form, repository)
95 def filesystem_field_tags(form, repository)
103 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
96 content_tag('p', form.text_field(:url, :label => 'Root directory', :size => 60, :required => true, :disabled => (repository && !repository.root_url.blank?)))
104 end
97 end
105 end
98 end
@@ -1,118 +1,122
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require "digest/md5"
18 require "digest/md5"
19
19
20 class Attachment < ActiveRecord::Base
20 class Attachment < ActiveRecord::Base
21 belongs_to :container, :polymorphic => true
21 belongs_to :container, :polymorphic => true
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
22 belongs_to :author, :class_name => "User", :foreign_key => "author_id"
23
23
24 validates_presence_of :container, :filename, :author
24 validates_presence_of :container, :filename, :author
25 validates_length_of :filename, :maximum => 255
25 validates_length_of :filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
26 validates_length_of :disk_filename, :maximum => 255
27
27
28 acts_as_event :title => :filename,
28 acts_as_event :title => :filename,
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
29 :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id}}
30
30
31 cattr_accessor :storage_path
31 cattr_accessor :storage_path
32 @@storage_path = "#{RAILS_ROOT}/files"
32 @@storage_path = "#{RAILS_ROOT}/files"
33
33
34 def validate
34 def validate
35 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
35 errors.add_to_base :too_long if self.filesize > Setting.attachment_max_size.to_i.kilobytes
36 end
36 end
37
37
38 def file=(incoming_file)
38 def file=(incoming_file)
39 unless incoming_file.nil?
39 unless incoming_file.nil?
40 @temp_file = incoming_file
40 @temp_file = incoming_file
41 if @temp_file.size > 0
41 if @temp_file.size > 0
42 self.filename = sanitize_filename(@temp_file.original_filename)
42 self.filename = sanitize_filename(@temp_file.original_filename)
43 self.disk_filename = Attachment.disk_filename(filename)
43 self.disk_filename = Attachment.disk_filename(filename)
44 self.content_type = @temp_file.content_type.to_s.chomp
44 self.content_type = @temp_file.content_type.to_s.chomp
45 self.filesize = @temp_file.size
45 self.filesize = @temp_file.size
46 end
46 end
47 end
47 end
48 end
48 end
49
49
50 def file
50 def file
51 nil
51 nil
52 end
52 end
53
53
54 # Copy temp file to its final location
54 # Copy temp file to its final location
55 def before_save
55 def before_save
56 if @temp_file && (@temp_file.size > 0)
56 if @temp_file && (@temp_file.size > 0)
57 logger.debug("saving '#{self.diskfile}'")
57 logger.debug("saving '#{self.diskfile}'")
58 File.open(diskfile, "wb") do |f|
58 File.open(diskfile, "wb") do |f|
59 f.write(@temp_file.read)
59 f.write(@temp_file.read)
60 end
60 end
61 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
61 self.digest = Digest::MD5.hexdigest(File.read(diskfile))
62 end
62 end
63 # Don't save the content type if it's longer than the authorized length
63 # Don't save the content type if it's longer than the authorized length
64 if self.content_type && self.content_type.length > 255
64 if self.content_type && self.content_type.length > 255
65 self.content_type = nil
65 self.content_type = nil
66 end
66 end
67 end
67 end
68
68
69 # Deletes file on the disk
69 # Deletes file on the disk
70 def after_destroy
70 def after_destroy
71 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
71 File.delete(diskfile) if !filename.blank? && File.exist?(diskfile)
72 end
72 end
73
73
74 # Returns file's location on disk
74 # Returns file's location on disk
75 def diskfile
75 def diskfile
76 "#{@@storage_path}/#{self.disk_filename}"
76 "#{@@storage_path}/#{self.disk_filename}"
77 end
77 end
78
78
79 def increment_download
79 def increment_download
80 increment!(:downloads)
80 increment!(:downloads)
81 end
81 end
82
82
83 def project
83 def project
84 container.project
84 container.project
85 end
85 end
86
86
87 def image?
87 def image?
88 self.filename =~ /\.(jpe?g|gif|png)$/i
88 self.filename =~ /\.(jpe?g|gif|png)$/i
89 end
89 end
90
90
91 def is_text?
92 Redmine::MimeType.is_type?('text', filename)
93 end
94
91 def is_diff?
95 def is_diff?
92 self.filename =~ /\.(patch|diff)$/i
96 self.filename =~ /\.(patch|diff)$/i
93 end
97 end
94
98
95 private
99 private
96 def sanitize_filename(value)
100 def sanitize_filename(value)
97 # get only the filename, not the whole path
101 # get only the filename, not the whole path
98 just_filename = value.gsub(/^.*(\\|\/)/, '')
102 just_filename = value.gsub(/^.*(\\|\/)/, '')
99 # NOTE: File.basename doesn't work right with Windows paths on Unix
103 # NOTE: File.basename doesn't work right with Windows paths on Unix
100 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
104 # INCORRECT: just_filename = File.basename(value.gsub('\\\\', '/'))
101
105
102 # Finally, replace all non alphanumeric, hyphens or periods with underscore
106 # Finally, replace all non alphanumeric, hyphens or periods with underscore
103 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
107 @filename = just_filename.gsub(/[^\w\.\-]/,'_')
104 end
108 end
105
109
106 # Returns an ASCII or hashed filename
110 # Returns an ASCII or hashed filename
107 def self.disk_filename(filename)
111 def self.disk_filename(filename)
108 df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
112 df = DateTime.now.strftime("%y%m%d%H%M%S") + "_"
109 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
113 if filename =~ %r{^[a-zA-Z0-9_\.\-]*$}
110 df << filename
114 df << filename
111 else
115 else
112 df << Digest::MD5.hexdigest(filename)
116 df << Digest::MD5.hexdigest(filename)
113 # keep the extension if any
117 # keep the extension if any
114 df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
118 df << $1 if filename =~ %r{(\.[a-zA-Z0-9]+)$}
115 end
119 end
116 df
120 df
117 end
121 end
118 end
122 end
@@ -1,39 +1,75
1 ---
1 ---
2 attachments_001:
2 attachments_001:
3 created_on: 2006-07-19 21:07:27 +02:00
3 created_on: 2006-07-19 21:07:27 +02:00
4 downloads: 0
4 downloads: 0
5 content_type: text/plain
5 content_type: text/plain
6 disk_filename: 060719210727_error281.txt
6 disk_filename: 060719210727_error281.txt
7 container_id: 3
7 container_id: 3
8 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
8 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
9 id: 1
9 id: 1
10 container_type: Issue
10 container_type: Issue
11 filesize: 28
11 filesize: 28
12 filename: error281.txt
12 filename: error281.txt
13 author_id: 2
13 author_id: 2
14 attachments_002:
14 attachments_002:
15 created_on: 2006-07-19 21:07:27 +02:00
15 created_on: 2006-07-19 21:07:27 +02:00
16 downloads: 0
16 downloads: 0
17 content_type: text/plain
17 content_type: text/plain
18 disk_filename: 060719210727_document.txt
18 disk_filename: 060719210727_document.txt
19 container_id: 1
19 container_id: 1
20 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
20 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
21 id: 2
21 id: 2
22 container_type: Document
22 container_type: Document
23 filesize: 28
23 filesize: 28
24 filename: document.txt
24 filename: document.txt
25 author_id: 2
25 author_id: 2
26 attachments_003:
26 attachments_003:
27 created_on: 2006-07-19 21:07:27 +02:00
27 created_on: 2006-07-19 21:07:27 +02:00
28 downloads: 0
28 downloads: 0
29 content_type: image/gif
29 content_type: image/gif
30 disk_filename: 060719210727_logo.gif
30 disk_filename: 060719210727_logo.gif
31 container_id: 4
31 container_id: 4
32 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
32 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
33 id: 3
33 id: 3
34 container_type: WikiPage
34 container_type: WikiPage
35 filesize: 280
35 filesize: 280
36 filename: logo.gif
36 filename: logo.gif
37 description: This is a logo
37 description: This is a logo
38 author_id: 2
38 author_id: 2
39 No newline at end of file
39 attachments_004:
40 created_on: 2006-07-19 21:07:27 +02:00
41 container_type: Issue
42 container_id: 3
43 downloads: 0
44 disk_filename: 060719210727_source.rb
45 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
46 id: 4
47 filesize: 153
48 filename: source.rb
49 author_id: 2
50 description: This is a Ruby source file
51 content_type: application/x-ruby
52 attachments_005:
53 created_on: 2006-07-19 21:07:27 +02:00
54 container_type: Issue
55 container_id: 3
56 downloads: 0
57 disk_filename: 060719210727_changeset.diff
58 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
59 id: 5
60 filesize: 687
61 filename: changeset.diff
62 author_id: 2
63 content_type: text/x-diff
64 attachments_006:
65 created_on: 2006-07-19 21:07:27 +02:00
66 container_type: Issue
67 container_id: 3
68 downloads: 0
69 disk_filename: 060719210727_archive.zip
70 digest: b91e08d0cf966d5c6ff411bd8c4cc3a2
71 id: 6
72 filesize: 157
73 filename: archive.zip
74 author_id: 2
75 content_type: application/octet-stream
@@ -1,64 +1,66
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 require 'documents_controller'
19 require 'documents_controller'
20
20
21 # Re-raise errors caught by the controller.
21 # Re-raise errors caught by the controller.
22 class DocumentsController; def rescue_action(e) raise e end; end
22 class DocumentsController; def rescue_action(e) raise e end; end
23
23
24 class DocumentsControllerTest < Test::Unit::TestCase
24 class DocumentsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :members, :enabled_modules, :documents, :enumerations
25 fixtures :projects, :users, :roles, :members, :enabled_modules, :documents, :enumerations
26
26
27 def setup
27 def setup
28 @controller = DocumentsController.new
28 @controller = DocumentsController.new
29 @request = ActionController::TestRequest.new
29 @request = ActionController::TestRequest.new
30 @response = ActionController::TestResponse.new
30 @response = ActionController::TestResponse.new
31 User.current = nil
31 User.current = nil
32 end
32 end
33
33
34 def test_index
34 def test_index
35 get :index, :project_id => 'ecookbook'
35 get :index, :project_id => 'ecookbook'
36 assert_response :success
36 assert_response :success
37 assert_template 'index'
37 assert_template 'index'
38 assert_not_nil assigns(:grouped)
38 assert_not_nil assigns(:grouped)
39 end
39 end
40
40
41 def test_new_with_one_attachment
41 def test_new_with_one_attachment
42 @request.session[:user_id] = 2
42 @request.session[:user_id] = 2
43 set_tmp_attachments_directory
44
43 post :new, :project_id => 'ecookbook',
45 post :new, :project_id => 'ecookbook',
44 :document => { :title => 'DocumentsControllerTest#test_post_new',
46 :document => { :title => 'DocumentsControllerTest#test_post_new',
45 :description => 'This is a new document',
47 :description => 'This is a new document',
46 :category_id => 2},
48 :category_id => 2},
47 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
49 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
48
50
49 assert_redirected_to 'projects/ecookbook/documents'
51 assert_redirected_to 'projects/ecookbook/documents'
50
52
51 document = Document.find_by_title('DocumentsControllerTest#test_post_new')
53 document = Document.find_by_title('DocumentsControllerTest#test_post_new')
52 assert_not_nil document
54 assert_not_nil document
53 assert_equal Enumeration.find(2), document.category
55 assert_equal Enumeration.find(2), document.category
54 assert_equal 1, document.attachments.size
56 assert_equal 1, document.attachments.size
55 assert_equal 'testfile.txt', document.attachments.first.filename
57 assert_equal 'testfile.txt', document.attachments.first.filename
56 end
58 end
57
59
58 def test_destroy
60 def test_destroy
59 @request.session[:user_id] = 2
61 @request.session[:user_id] = 2
60 post :destroy, :id => 1
62 post :destroy, :id => 1
61 assert_redirected_to 'projects/ecookbook/documents'
63 assert_redirected_to 'projects/ecookbook/documents'
62 assert_nil Document.find_by_id(1)
64 assert_nil Document.find_by_id(1)
63 end
65 end
64 end
66 end
@@ -1,561 +1,563
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19 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 :trackers,
31 :trackers,
32 :projects_trackers,
32 :projects_trackers,
33 :issue_categories,
33 :issue_categories,
34 :enabled_modules,
34 :enabled_modules,
35 :enumerations,
35 :enumerations,
36 :attachments,
36 :attachments,
37 :workflows,
37 :workflows,
38 :custom_fields,
38 :custom_fields,
39 :custom_values,
39 :custom_values,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details
43 :journal_details
44
44
45 def setup
45 def setup
46 @controller = IssuesController.new
46 @controller = IssuesController.new
47 @request = ActionController::TestRequest.new
47 @request = ActionController::TestRequest.new
48 @response = ActionController::TestResponse.new
48 @response = ActionController::TestResponse.new
49 User.current = nil
49 User.current = nil
50 end
50 end
51
51
52 def test_index
52 def test_index
53 get :index
53 get :index
54 assert_response :success
54 assert_response :success
55 assert_template 'index.rhtml'
55 assert_template 'index.rhtml'
56 assert_not_nil assigns(:issues)
56 assert_not_nil assigns(:issues)
57 assert_nil assigns(:project)
57 assert_nil assigns(:project)
58 assert_tag :tag => 'a', :content => /Can't print recipes/
58 assert_tag :tag => 'a', :content => /Can't print recipes/
59 assert_tag :tag => 'a', :content => /Subproject issue/
59 assert_tag :tag => 'a', :content => /Subproject issue/
60 # private projects hidden
60 # private projects hidden
61 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
61 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
62 assert_no_tag :tag => 'a', :content => /Issue on project 2/
62 assert_no_tag :tag => 'a', :content => /Issue on project 2/
63 end
63 end
64
64
65 def test_index_with_project
65 def test_index_with_project
66 Setting.display_subprojects_issues = 0
66 Setting.display_subprojects_issues = 0
67 get :index, :project_id => 1
67 get :index, :project_id => 1
68 assert_response :success
68 assert_response :success
69 assert_template 'index.rhtml'
69 assert_template 'index.rhtml'
70 assert_not_nil assigns(:issues)
70 assert_not_nil assigns(:issues)
71 assert_tag :tag => 'a', :content => /Can't print recipes/
71 assert_tag :tag => 'a', :content => /Can't print recipes/
72 assert_no_tag :tag => 'a', :content => /Subproject issue/
72 assert_no_tag :tag => 'a', :content => /Subproject issue/
73 end
73 end
74
74
75 def test_index_with_project_and_subprojects
75 def test_index_with_project_and_subprojects
76 Setting.display_subprojects_issues = 1
76 Setting.display_subprojects_issues = 1
77 get :index, :project_id => 1
77 get :index, :project_id => 1
78 assert_response :success
78 assert_response :success
79 assert_template 'index.rhtml'
79 assert_template 'index.rhtml'
80 assert_not_nil assigns(:issues)
80 assert_not_nil assigns(:issues)
81 assert_tag :tag => 'a', :content => /Can't print recipes/
81 assert_tag :tag => 'a', :content => /Can't print recipes/
82 assert_tag :tag => 'a', :content => /Subproject issue/
82 assert_tag :tag => 'a', :content => /Subproject issue/
83 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
83 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
84 end
84 end
85
85
86 def test_index_with_project_and_subprojects_should_show_private_subprojects
86 def test_index_with_project_and_subprojects_should_show_private_subprojects
87 @request.session[:user_id] = 2
87 @request.session[:user_id] = 2
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_tag :tag => 'a', :content => /Issue of a private subproject/
95 assert_tag :tag => 'a', :content => /Issue of a private subproject/
96 end
96 end
97
97
98 def test_index_with_project_and_filter
98 def test_index_with_project_and_filter
99 get :index, :project_id => 1, :set_filter => 1
99 get :index, :project_id => 1, :set_filter => 1
100 assert_response :success
100 assert_response :success
101 assert_template 'index.rhtml'
101 assert_template 'index.rhtml'
102 assert_not_nil assigns(:issues)
102 assert_not_nil assigns(:issues)
103 end
103 end
104
104
105 def test_index_csv_with_project
105 def test_index_csv_with_project
106 get :index, :format => 'csv'
106 get :index, :format => 'csv'
107 assert_response :success
107 assert_response :success
108 assert_not_nil assigns(:issues)
108 assert_not_nil assigns(:issues)
109 assert_equal 'text/csv', @response.content_type
109 assert_equal 'text/csv', @response.content_type
110
110
111 get :index, :project_id => 1, :format => 'csv'
111 get :index, :project_id => 1, :format => 'csv'
112 assert_response :success
112 assert_response :success
113 assert_not_nil assigns(:issues)
113 assert_not_nil assigns(:issues)
114 assert_equal 'text/csv', @response.content_type
114 assert_equal 'text/csv', @response.content_type
115 end
115 end
116
116
117 def test_index_pdf
117 def test_index_pdf
118 get :index, :format => 'pdf'
118 get :index, :format => 'pdf'
119 assert_response :success
119 assert_response :success
120 assert_not_nil assigns(:issues)
120 assert_not_nil assigns(:issues)
121 assert_equal 'application/pdf', @response.content_type
121 assert_equal 'application/pdf', @response.content_type
122
122
123 get :index, :project_id => 1, :format => 'pdf'
123 get :index, :project_id => 1, :format => 'pdf'
124 assert_response :success
124 assert_response :success
125 assert_not_nil assigns(:issues)
125 assert_not_nil assigns(:issues)
126 assert_equal 'application/pdf', @response.content_type
126 assert_equal 'application/pdf', @response.content_type
127 end
127 end
128
128
129 def test_changes
129 def test_changes
130 get :changes, :project_id => 1
130 get :changes, :project_id => 1
131 assert_response :success
131 assert_response :success
132 assert_not_nil assigns(:journals)
132 assert_not_nil assigns(:journals)
133 assert_equal 'application/atom+xml', @response.content_type
133 assert_equal 'application/atom+xml', @response.content_type
134 end
134 end
135
135
136 def test_show_by_anonymous
136 def test_show_by_anonymous
137 get :show, :id => 1
137 get :show, :id => 1
138 assert_response :success
138 assert_response :success
139 assert_template 'show.rhtml'
139 assert_template 'show.rhtml'
140 assert_not_nil assigns(:issue)
140 assert_not_nil assigns(:issue)
141 assert_equal Issue.find(1), assigns(:issue)
141 assert_equal Issue.find(1), assigns(:issue)
142
142
143 # anonymous role is allowed to add a note
143 # anonymous role is allowed to add a note
144 assert_tag :tag => 'form',
144 assert_tag :tag => 'form',
145 :descendant => { :tag => 'fieldset',
145 :descendant => { :tag => 'fieldset',
146 :child => { :tag => 'legend',
146 :child => { :tag => 'legend',
147 :content => /Notes/ } }
147 :content => /Notes/ } }
148 end
148 end
149
149
150 def test_show_by_manager
150 def test_show_by_manager
151 @request.session[:user_id] = 2
151 @request.session[:user_id] = 2
152 get :show, :id => 1
152 get :show, :id => 1
153 assert_response :success
153 assert_response :success
154
154
155 assert_tag :tag => 'form',
155 assert_tag :tag => 'form',
156 :descendant => { :tag => 'fieldset',
156 :descendant => { :tag => 'fieldset',
157 :child => { :tag => 'legend',
157 :child => { :tag => 'legend',
158 :content => /Change properties/ } },
158 :content => /Change properties/ } },
159 :descendant => { :tag => 'fieldset',
159 :descendant => { :tag => 'fieldset',
160 :child => { :tag => 'legend',
160 :child => { :tag => 'legend',
161 :content => /Log time/ } },
161 :content => /Log time/ } },
162 :descendant => { :tag => 'fieldset',
162 :descendant => { :tag => 'fieldset',
163 :child => { :tag => 'legend',
163 :child => { :tag => 'legend',
164 :content => /Notes/ } }
164 :content => /Notes/ } }
165 end
165 end
166
166
167 def test_get_new
167 def test_get_new
168 @request.session[:user_id] = 2
168 @request.session[:user_id] = 2
169 get :new, :project_id => 1, :tracker_id => 1
169 get :new, :project_id => 1, :tracker_id => 1
170 assert_response :success
170 assert_response :success
171 assert_template 'new'
171 assert_template 'new'
172
172
173 assert_tag :tag => 'input', :attributes => { :name => 'custom_fields[2]',
173 assert_tag :tag => 'input', :attributes => { :name => 'custom_fields[2]',
174 :value => 'Default string' }
174 :value => 'Default string' }
175 end
175 end
176
176
177 def test_get_new_without_tracker_id
177 def test_get_new_without_tracker_id
178 @request.session[:user_id] = 2
178 @request.session[:user_id] = 2
179 get :new, :project_id => 1
179 get :new, :project_id => 1
180 assert_response :success
180 assert_response :success
181 assert_template 'new'
181 assert_template 'new'
182
182
183 issue = assigns(:issue)
183 issue = assigns(:issue)
184 assert_not_nil issue
184 assert_not_nil issue
185 assert_equal Project.find(1).trackers.first, issue.tracker
185 assert_equal Project.find(1).trackers.first, issue.tracker
186 end
186 end
187
187
188 def test_update_new_form
188 def test_update_new_form
189 @request.session[:user_id] = 2
189 @request.session[:user_id] = 2
190 xhr :post, :new, :project_id => 1,
190 xhr :post, :new, :project_id => 1,
191 :issue => {:tracker_id => 2,
191 :issue => {:tracker_id => 2,
192 :subject => 'This is the test_new issue',
192 :subject => 'This is the test_new issue',
193 :description => 'This is the description',
193 :description => 'This is the description',
194 :priority_id => 5}
194 :priority_id => 5}
195 assert_response :success
195 assert_response :success
196 assert_template 'new'
196 assert_template 'new'
197 end
197 end
198
198
199 def test_post_new
199 def test_post_new
200 @request.session[:user_id] = 2
200 @request.session[:user_id] = 2
201 post :new, :project_id => 1,
201 post :new, :project_id => 1,
202 :issue => {:tracker_id => 1,
202 :issue => {:tracker_id => 1,
203 :subject => 'This is the test_new issue',
203 :subject => 'This is the test_new issue',
204 :description => 'This is the description',
204 :description => 'This is the description',
205 :priority_id => 5,
205 :priority_id => 5,
206 :estimated_hours => ''},
206 :estimated_hours => ''},
207 :custom_fields => {'2' => 'Value for field 2'}
207 :custom_fields => {'2' => 'Value for field 2'}
208 assert_redirected_to 'issues/show'
208 assert_redirected_to 'issues/show'
209
209
210 issue = Issue.find_by_subject('This is the test_new issue')
210 issue = Issue.find_by_subject('This is the test_new issue')
211 assert_not_nil issue
211 assert_not_nil issue
212 assert_equal 2, issue.author_id
212 assert_equal 2, issue.author_id
213 assert_nil issue.estimated_hours
213 assert_nil issue.estimated_hours
214 v = issue.custom_values.find_by_custom_field_id(2)
214 v = issue.custom_values.find_by_custom_field_id(2)
215 assert_not_nil v
215 assert_not_nil v
216 assert_equal 'Value for field 2', v.value
216 assert_equal 'Value for field 2', v.value
217 end
217 end
218
218
219 def test_post_new_without_custom_fields_param
219 def test_post_new_without_custom_fields_param
220 @request.session[:user_id] = 2
220 @request.session[:user_id] = 2
221 post :new, :project_id => 1,
221 post :new, :project_id => 1,
222 :issue => {:tracker_id => 1,
222 :issue => {:tracker_id => 1,
223 :subject => 'This is the test_new issue',
223 :subject => 'This is the test_new issue',
224 :description => 'This is the description',
224 :description => 'This is the description',
225 :priority_id => 5}
225 :priority_id => 5}
226 assert_redirected_to 'issues/show'
226 assert_redirected_to 'issues/show'
227 end
227 end
228
228
229 def test_copy_issue
229 def test_copy_issue
230 @request.session[:user_id] = 2
230 @request.session[:user_id] = 2
231 get :new, :project_id => 1, :copy_from => 1
231 get :new, :project_id => 1, :copy_from => 1
232 assert_template 'new'
232 assert_template 'new'
233 assert_not_nil assigns(:issue)
233 assert_not_nil assigns(:issue)
234 orig = Issue.find(1)
234 orig = Issue.find(1)
235 assert_equal orig.subject, assigns(:issue).subject
235 assert_equal orig.subject, assigns(:issue).subject
236 end
236 end
237
237
238 def test_get_edit
238 def test_get_edit
239 @request.session[:user_id] = 2
239 @request.session[:user_id] = 2
240 get :edit, :id => 1
240 get :edit, :id => 1
241 assert_response :success
241 assert_response :success
242 assert_template 'edit'
242 assert_template 'edit'
243 assert_not_nil assigns(:issue)
243 assert_not_nil assigns(:issue)
244 assert_equal Issue.find(1), assigns(:issue)
244 assert_equal Issue.find(1), assigns(:issue)
245 end
245 end
246
246
247 def test_get_edit_with_params
247 def test_get_edit_with_params
248 @request.session[:user_id] = 2
248 @request.session[:user_id] = 2
249 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
249 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 }
250 assert_response :success
250 assert_response :success
251 assert_template 'edit'
251 assert_template 'edit'
252
252
253 issue = assigns(:issue)
253 issue = assigns(:issue)
254 assert_not_nil issue
254 assert_not_nil issue
255
255
256 assert_equal 5, issue.status_id
256 assert_equal 5, issue.status_id
257 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
257 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
258 :child => { :tag => 'option',
258 :child => { :tag => 'option',
259 :content => 'Closed',
259 :content => 'Closed',
260 :attributes => { :selected => 'selected' } }
260 :attributes => { :selected => 'selected' } }
261
261
262 assert_equal 7, issue.priority_id
262 assert_equal 7, issue.priority_id
263 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
263 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
264 :child => { :tag => 'option',
264 :child => { :tag => 'option',
265 :content => 'Urgent',
265 :content => 'Urgent',
266 :attributes => { :selected => 'selected' } }
266 :attributes => { :selected => 'selected' } }
267 end
267 end
268
268
269 def test_reply_to_issue
269 def test_reply_to_issue
270 @request.session[:user_id] = 2
270 @request.session[:user_id] = 2
271 get :reply, :id => 1
271 get :reply, :id => 1
272 assert_response :success
272 assert_response :success
273 assert_select_rjs :show, "update"
273 assert_select_rjs :show, "update"
274 end
274 end
275
275
276 def test_reply_to_note
276 def test_reply_to_note
277 @request.session[:user_id] = 2
277 @request.session[:user_id] = 2
278 get :reply, :id => 1, :journal_id => 2
278 get :reply, :id => 1, :journal_id => 2
279 assert_response :success
279 assert_response :success
280 assert_select_rjs :show, "update"
280 assert_select_rjs :show, "update"
281 end
281 end
282
282
283 def test_post_edit
283 def test_post_edit
284 @request.session[:user_id] = 2
284 @request.session[:user_id] = 2
285 ActionMailer::Base.deliveries.clear
285 ActionMailer::Base.deliveries.clear
286
286
287 issue = Issue.find(1)
287 issue = Issue.find(1)
288 old_subject = issue.subject
288 old_subject = issue.subject
289 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
289 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
290
290
291 post :edit, :id => 1, :issue => {:subject => new_subject}
291 post :edit, :id => 1, :issue => {:subject => new_subject}
292 assert_redirected_to 'issues/show/1'
292 assert_redirected_to 'issues/show/1'
293 issue.reload
293 issue.reload
294 assert_equal new_subject, issue.subject
294 assert_equal new_subject, issue.subject
295
295
296 mail = ActionMailer::Base.deliveries.last
296 mail = ActionMailer::Base.deliveries.last
297 assert_kind_of TMail::Mail, mail
297 assert_kind_of TMail::Mail, mail
298 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
298 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
299 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
299 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
300 end
300 end
301
301
302 def test_post_edit_with_status_and_assignee_change
302 def test_post_edit_with_status_and_assignee_change
303 issue = Issue.find(1)
303 issue = Issue.find(1)
304 assert_equal 1, issue.status_id
304 assert_equal 1, issue.status_id
305 @request.session[:user_id] = 2
305 @request.session[:user_id] = 2
306 assert_difference('TimeEntry.count', 0) do
306 assert_difference('TimeEntry.count', 0) do
307 post :edit,
307 post :edit,
308 :id => 1,
308 :id => 1,
309 :issue => { :status_id => 2, :assigned_to_id => 3 },
309 :issue => { :status_id => 2, :assigned_to_id => 3 },
310 :notes => 'Assigned to dlopper',
310 :notes => 'Assigned to dlopper',
311 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
311 :time_entry => { :hours => '', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
312 end
312 end
313 assert_redirected_to 'issues/show/1'
313 assert_redirected_to 'issues/show/1'
314 issue.reload
314 issue.reload
315 assert_equal 2, issue.status_id
315 assert_equal 2, issue.status_id
316 j = issue.journals.find(:first, :order => 'id DESC')
316 j = issue.journals.find(:first, :order => 'id DESC')
317 assert_equal 'Assigned to dlopper', j.notes
317 assert_equal 'Assigned to dlopper', j.notes
318 assert_equal 2, j.details.size
318 assert_equal 2, j.details.size
319
319
320 mail = ActionMailer::Base.deliveries.last
320 mail = ActionMailer::Base.deliveries.last
321 assert mail.body.include?("Status changed from New to Assigned")
321 assert mail.body.include?("Status changed from New to Assigned")
322 end
322 end
323
323
324 def test_post_edit_with_note_only
324 def test_post_edit_with_note_only
325 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
325 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
326 # anonymous user
326 # anonymous user
327 post :edit,
327 post :edit,
328 :id => 1,
328 :id => 1,
329 :notes => notes
329 :notes => notes
330 assert_redirected_to 'issues/show/1'
330 assert_redirected_to 'issues/show/1'
331 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
331 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
332 assert_equal notes, j.notes
332 assert_equal notes, j.notes
333 assert_equal 0, j.details.size
333 assert_equal 0, j.details.size
334 assert_equal User.anonymous, j.user
334 assert_equal User.anonymous, j.user
335
335
336 mail = ActionMailer::Base.deliveries.last
336 mail = ActionMailer::Base.deliveries.last
337 assert mail.body.include?(notes)
337 assert mail.body.include?(notes)
338 end
338 end
339
339
340 def test_post_edit_with_note_and_spent_time
340 def test_post_edit_with_note_and_spent_time
341 @request.session[:user_id] = 2
341 @request.session[:user_id] = 2
342 spent_hours_before = Issue.find(1).spent_hours
342 spent_hours_before = Issue.find(1).spent_hours
343 assert_difference('TimeEntry.count') do
343 assert_difference('TimeEntry.count') do
344 post :edit,
344 post :edit,
345 :id => 1,
345 :id => 1,
346 :notes => '2.5 hours added',
346 :notes => '2.5 hours added',
347 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
347 :time_entry => { :hours => '2.5', :comments => '', :activity_id => Enumeration.get_values('ACTI').first }
348 end
348 end
349 assert_redirected_to 'issues/show/1'
349 assert_redirected_to 'issues/show/1'
350
350
351 issue = Issue.find(1)
351 issue = Issue.find(1)
352
352
353 j = issue.journals.find(:first, :order => 'id DESC')
353 j = issue.journals.find(:first, :order => 'id DESC')
354 assert_equal '2.5 hours added', j.notes
354 assert_equal '2.5 hours added', j.notes
355 assert_equal 0, j.details.size
355 assert_equal 0, j.details.size
356
356
357 t = issue.time_entries.find(:first, :order => 'id DESC')
357 t = issue.time_entries.find(:first, :order => 'id DESC')
358 assert_not_nil t
358 assert_not_nil t
359 assert_equal 2.5, t.hours
359 assert_equal 2.5, t.hours
360 assert_equal spent_hours_before + 2.5, issue.spent_hours
360 assert_equal spent_hours_before + 2.5, issue.spent_hours
361 end
361 end
362
362
363 def test_post_edit_with_attachment_only
363 def test_post_edit_with_attachment_only
364 set_tmp_attachments_directory
365
364 # anonymous user
366 # anonymous user
365 post :edit,
367 post :edit,
366 :id => 1,
368 :id => 1,
367 :notes => '',
369 :notes => '',
368 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
370 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain')}}
369 assert_redirected_to 'issues/show/1'
371 assert_redirected_to 'issues/show/1'
370 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
372 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
371 assert j.notes.blank?
373 assert j.notes.blank?
372 assert_equal 1, j.details.size
374 assert_equal 1, j.details.size
373 assert_equal 'testfile.txt', j.details.first.value
375 assert_equal 'testfile.txt', j.details.first.value
374 assert_equal User.anonymous, j.user
376 assert_equal User.anonymous, j.user
375
377
376 mail = ActionMailer::Base.deliveries.last
378 mail = ActionMailer::Base.deliveries.last
377 assert mail.body.include?('testfile.txt')
379 assert mail.body.include?('testfile.txt')
378 end
380 end
379
381
380 def test_post_edit_with_no_change
382 def test_post_edit_with_no_change
381 issue = Issue.find(1)
383 issue = Issue.find(1)
382 issue.journals.clear
384 issue.journals.clear
383 ActionMailer::Base.deliveries.clear
385 ActionMailer::Base.deliveries.clear
384
386
385 post :edit,
387 post :edit,
386 :id => 1,
388 :id => 1,
387 :notes => ''
389 :notes => ''
388 assert_redirected_to 'issues/show/1'
390 assert_redirected_to 'issues/show/1'
389
391
390 issue.reload
392 issue.reload
391 assert issue.journals.empty?
393 assert issue.journals.empty?
392 # No email should be sent
394 # No email should be sent
393 assert ActionMailer::Base.deliveries.empty?
395 assert ActionMailer::Base.deliveries.empty?
394 end
396 end
395
397
396 def test_bulk_edit
398 def test_bulk_edit
397 @request.session[:user_id] = 2
399 @request.session[:user_id] = 2
398 # update issues priority
400 # update issues priority
399 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
401 post :bulk_edit, :ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
400 assert_response 302
402 assert_response 302
401 # check that the issues were updated
403 # check that the issues were updated
402 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
404 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
403 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
405 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
404 end
406 end
405
407
406 def test_bulk_unassign
408 def test_bulk_unassign
407 assert_not_nil Issue.find(2).assigned_to
409 assert_not_nil Issue.find(2).assigned_to
408 @request.session[:user_id] = 2
410 @request.session[:user_id] = 2
409 # unassign issues
411 # unassign issues
410 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
412 post :bulk_edit, :ids => [1, 2], :notes => 'Bulk unassigning', :assigned_to_id => 'none'
411 assert_response 302
413 assert_response 302
412 # check that the issues were updated
414 # check that the issues were updated
413 assert_nil Issue.find(2).assigned_to
415 assert_nil Issue.find(2).assigned_to
414 end
416 end
415
417
416 def test_move_one_issue_to_another_project
418 def test_move_one_issue_to_another_project
417 @request.session[:user_id] = 1
419 @request.session[:user_id] = 1
418 post :move, :id => 1, :new_project_id => 2
420 post :move, :id => 1, :new_project_id => 2
419 assert_redirected_to 'projects/ecookbook/issues'
421 assert_redirected_to 'projects/ecookbook/issues'
420 assert_equal 2, Issue.find(1).project_id
422 assert_equal 2, Issue.find(1).project_id
421 end
423 end
422
424
423 def test_bulk_move_to_another_project
425 def test_bulk_move_to_another_project
424 @request.session[:user_id] = 1
426 @request.session[:user_id] = 1
425 post :move, :ids => [1, 2], :new_project_id => 2
427 post :move, :ids => [1, 2], :new_project_id => 2
426 assert_redirected_to 'projects/ecookbook/issues'
428 assert_redirected_to 'projects/ecookbook/issues'
427 # Issues moved to project 2
429 # Issues moved to project 2
428 assert_equal 2, Issue.find(1).project_id
430 assert_equal 2, Issue.find(1).project_id
429 assert_equal 2, Issue.find(2).project_id
431 assert_equal 2, Issue.find(2).project_id
430 # No tracker change
432 # No tracker change
431 assert_equal 1, Issue.find(1).tracker_id
433 assert_equal 1, Issue.find(1).tracker_id
432 assert_equal 2, Issue.find(2).tracker_id
434 assert_equal 2, Issue.find(2).tracker_id
433 end
435 end
434
436
435 def test_bulk_move_to_another_tracker
437 def test_bulk_move_to_another_tracker
436 @request.session[:user_id] = 1
438 @request.session[:user_id] = 1
437 post :move, :ids => [1, 2], :new_tracker_id => 2
439 post :move, :ids => [1, 2], :new_tracker_id => 2
438 assert_redirected_to 'projects/ecookbook/issues'
440 assert_redirected_to 'projects/ecookbook/issues'
439 assert_equal 2, Issue.find(1).tracker_id
441 assert_equal 2, Issue.find(1).tracker_id
440 assert_equal 2, Issue.find(2).tracker_id
442 assert_equal 2, Issue.find(2).tracker_id
441 end
443 end
442
444
443 def test_context_menu_one_issue
445 def test_context_menu_one_issue
444 @request.session[:user_id] = 2
446 @request.session[:user_id] = 2
445 get :context_menu, :ids => [1]
447 get :context_menu, :ids => [1]
446 assert_response :success
448 assert_response :success
447 assert_template 'context_menu'
449 assert_template 'context_menu'
448 assert_tag :tag => 'a', :content => 'Edit',
450 assert_tag :tag => 'a', :content => 'Edit',
449 :attributes => { :href => '/issues/edit/1',
451 :attributes => { :href => '/issues/edit/1',
450 :class => 'icon-edit' }
452 :class => 'icon-edit' }
451 assert_tag :tag => 'a', :content => 'Closed',
453 assert_tag :tag => 'a', :content => 'Closed',
452 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
454 :attributes => { :href => '/issues/edit/1?issue%5Bstatus_id%5D=5',
453 :class => '' }
455 :class => '' }
454 assert_tag :tag => 'a', :content => 'Immediate',
456 assert_tag :tag => 'a', :content => 'Immediate',
455 :attributes => { :href => '/issues/edit/1?issue%5Bpriority_id%5D=8',
457 :attributes => { :href => '/issues/edit/1?issue%5Bpriority_id%5D=8',
456 :class => '' }
458 :class => '' }
457 assert_tag :tag => 'a', :content => 'Dave Lopper',
459 assert_tag :tag => 'a', :content => 'Dave Lopper',
458 :attributes => { :href => '/issues/edit/1?issue%5Bassigned_to_id%5D=3',
460 :attributes => { :href => '/issues/edit/1?issue%5Bassigned_to_id%5D=3',
459 :class => '' }
461 :class => '' }
460 assert_tag :tag => 'a', :content => 'Copy',
462 assert_tag :tag => 'a', :content => 'Copy',
461 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
463 :attributes => { :href => '/projects/ecookbook/issues/new?copy_from=1',
462 :class => 'icon-copy' }
464 :class => 'icon-copy' }
463 assert_tag :tag => 'a', :content => 'Move',
465 assert_tag :tag => 'a', :content => 'Move',
464 :attributes => { :href => '/issues/move?ids%5B%5D=1',
466 :attributes => { :href => '/issues/move?ids%5B%5D=1',
465 :class => 'icon-move' }
467 :class => 'icon-move' }
466 assert_tag :tag => 'a', :content => 'Delete',
468 assert_tag :tag => 'a', :content => 'Delete',
467 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
469 :attributes => { :href => '/issues/destroy?ids%5B%5D=1',
468 :class => 'icon-del' }
470 :class => 'icon-del' }
469 end
471 end
470
472
471 def test_context_menu_one_issue_by_anonymous
473 def test_context_menu_one_issue_by_anonymous
472 get :context_menu, :ids => [1]
474 get :context_menu, :ids => [1]
473 assert_response :success
475 assert_response :success
474 assert_template 'context_menu'
476 assert_template 'context_menu'
475 assert_tag :tag => 'a', :content => 'Delete',
477 assert_tag :tag => 'a', :content => 'Delete',
476 :attributes => { :href => '#',
478 :attributes => { :href => '#',
477 :class => 'icon-del disabled' }
479 :class => 'icon-del disabled' }
478 end
480 end
479
481
480 def test_context_menu_multiple_issues_of_same_project
482 def test_context_menu_multiple_issues_of_same_project
481 @request.session[:user_id] = 2
483 @request.session[:user_id] = 2
482 get :context_menu, :ids => [1, 2]
484 get :context_menu, :ids => [1, 2]
483 assert_response :success
485 assert_response :success
484 assert_template 'context_menu'
486 assert_template 'context_menu'
485 assert_tag :tag => 'a', :content => 'Edit',
487 assert_tag :tag => 'a', :content => 'Edit',
486 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
488 :attributes => { :href => '/issues/bulk_edit?ids%5B%5D=1&amp;ids%5B%5D=2',
487 :class => 'icon-edit' }
489 :class => 'icon-edit' }
488 assert_tag :tag => 'a', :content => 'Move',
490 assert_tag :tag => 'a', :content => 'Move',
489 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
491 :attributes => { :href => '/issues/move?ids%5B%5D=1&amp;ids%5B%5D=2',
490 :class => 'icon-move' }
492 :class => 'icon-move' }
491 assert_tag :tag => 'a', :content => 'Delete',
493 assert_tag :tag => 'a', :content => 'Delete',
492 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
494 :attributes => { :href => '/issues/destroy?ids%5B%5D=1&amp;ids%5B%5D=2',
493 :class => 'icon-del' }
495 :class => 'icon-del' }
494 end
496 end
495
497
496 def test_context_menu_multiple_issues_of_different_project
498 def test_context_menu_multiple_issues_of_different_project
497 @request.session[:user_id] = 2
499 @request.session[:user_id] = 2
498 get :context_menu, :ids => [1, 2, 4]
500 get :context_menu, :ids => [1, 2, 4]
499 assert_response :success
501 assert_response :success
500 assert_template 'context_menu'
502 assert_template 'context_menu'
501 assert_tag :tag => 'a', :content => 'Delete',
503 assert_tag :tag => 'a', :content => 'Delete',
502 :attributes => { :href => '#',
504 :attributes => { :href => '#',
503 :class => 'icon-del disabled' }
505 :class => 'icon-del disabled' }
504 end
506 end
505
507
506 def test_destroy_issue_with_no_time_entries
508 def test_destroy_issue_with_no_time_entries
507 assert_nil TimeEntry.find_by_issue_id(2)
509 assert_nil TimeEntry.find_by_issue_id(2)
508 @request.session[:user_id] = 2
510 @request.session[:user_id] = 2
509 post :destroy, :id => 2
511 post :destroy, :id => 2
510 assert_redirected_to 'projects/ecookbook/issues'
512 assert_redirected_to 'projects/ecookbook/issues'
511 assert_nil Issue.find_by_id(2)
513 assert_nil Issue.find_by_id(2)
512 end
514 end
513
515
514 def test_destroy_issues_with_time_entries
516 def test_destroy_issues_with_time_entries
515 @request.session[:user_id] = 2
517 @request.session[:user_id] = 2
516 post :destroy, :ids => [1, 3]
518 post :destroy, :ids => [1, 3]
517 assert_response :success
519 assert_response :success
518 assert_template 'destroy'
520 assert_template 'destroy'
519 assert_not_nil assigns(:hours)
521 assert_not_nil assigns(:hours)
520 assert Issue.find_by_id(1) && Issue.find_by_id(3)
522 assert Issue.find_by_id(1) && Issue.find_by_id(3)
521 end
523 end
522
524
523 def test_destroy_issues_and_destroy_time_entries
525 def test_destroy_issues_and_destroy_time_entries
524 @request.session[:user_id] = 2
526 @request.session[:user_id] = 2
525 post :destroy, :ids => [1, 3], :todo => 'destroy'
527 post :destroy, :ids => [1, 3], :todo => 'destroy'
526 assert_redirected_to 'projects/ecookbook/issues'
528 assert_redirected_to 'projects/ecookbook/issues'
527 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
529 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
528 assert_nil TimeEntry.find_by_id([1, 2])
530 assert_nil TimeEntry.find_by_id([1, 2])
529 end
531 end
530
532
531 def test_destroy_issues_and_assign_time_entries_to_project
533 def test_destroy_issues_and_assign_time_entries_to_project
532 @request.session[:user_id] = 2
534 @request.session[:user_id] = 2
533 post :destroy, :ids => [1, 3], :todo => 'nullify'
535 post :destroy, :ids => [1, 3], :todo => 'nullify'
534 assert_redirected_to 'projects/ecookbook/issues'
536 assert_redirected_to 'projects/ecookbook/issues'
535 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
537 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
536 assert_nil TimeEntry.find(1).issue_id
538 assert_nil TimeEntry.find(1).issue_id
537 assert_nil TimeEntry.find(2).issue_id
539 assert_nil TimeEntry.find(2).issue_id
538 end
540 end
539
541
540 def test_destroy_issues_and_reassign_time_entries_to_another_issue
542 def test_destroy_issues_and_reassign_time_entries_to_another_issue
541 @request.session[:user_id] = 2
543 @request.session[:user_id] = 2
542 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
544 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
543 assert_redirected_to 'projects/ecookbook/issues'
545 assert_redirected_to 'projects/ecookbook/issues'
544 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
546 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
545 assert_equal 2, TimeEntry.find(1).issue_id
547 assert_equal 2, TimeEntry.find(1).issue_id
546 assert_equal 2, TimeEntry.find(2).issue_id
548 assert_equal 2, TimeEntry.find(2).issue_id
547 end
549 end
548
550
549 def test_destroy_attachment
551 def test_destroy_attachment
550 issue = Issue.find(3)
552 issue = Issue.find(3)
551 a = issue.attachments.size
553 a = issue.attachments.size
552 @request.session[:user_id] = 2
554 @request.session[:user_id] = 2
553 post :destroy_attachment, :id => 3, :attachment_id => 1
555 post :destroy_attachment, :id => 3, :attachment_id => 1
554 assert_redirected_to 'issues/show/3'
556 assert_redirected_to 'issues/show/3'
555 assert_nil Attachment.find_by_id(1)
557 assert_nil Attachment.find_by_id(1)
556 issue.reload
558 issue.reload
557 assert_equal((a-1), issue.attachments.size)
559 assert_equal((a-1), issue.attachments.size)
558 j = issue.journals.find(:first, :order => 'created_on DESC')
560 j = issue.journals.find(:first, :order => 'created_on DESC')
559 assert_equal 'attachment', j.details.first.property
561 assert_equal 'attachment', j.details.first.property
560 end
562 end
561 end
563 end
@@ -1,71 +1,89
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
1 require "#{File.dirname(__FILE__)}/../test_helper"
18 require "#{File.dirname(__FILE__)}/../test_helper"
2
19
3 class IssuesTest < ActionController::IntegrationTest
20 class IssuesTest < ActionController::IntegrationTest
4 fixtures :projects,
21 fixtures :projects,
5 :users,
22 :users,
6 :trackers,
23 :trackers,
7 :projects_trackers,
24 :projects_trackers,
8 :issue_statuses,
25 :issue_statuses,
9 :issues,
26 :issues,
10 :enumerations,
27 :enumerations,
11 :custom_fields,
28 :custom_fields,
12 :custom_values,
29 :custom_values,
13 :custom_fields_trackers
30 :custom_fields_trackers
14
31
15 # create an issue
32 # create an issue
16 def test_add_issue
33 def test_add_issue
17 log_user('jsmith', 'jsmith')
34 log_user('jsmith', 'jsmith')
18 get 'projects/1/issues/new', :tracker_id => '1'
35 get 'projects/1/issues/new', :tracker_id => '1'
19 assert_response :success
36 assert_response :success
20 assert_template 'issues/new'
37 assert_template 'issues/new'
21
38
22 post 'projects/1/issues/new', :tracker_id => "1",
39 post 'projects/1/issues/new', :tracker_id => "1",
23 :issue => { :start_date => "2006-12-26",
40 :issue => { :start_date => "2006-12-26",
24 :priority_id => "3",
41 :priority_id => "3",
25 :subject => "new test issue",
42 :subject => "new test issue",
26 :category_id => "",
43 :category_id => "",
27 :description => "new issue",
44 :description => "new issue",
28 :done_ratio => "0",
45 :done_ratio => "0",
29 :due_date => "",
46 :due_date => "",
30 :assigned_to_id => "" },
47 :assigned_to_id => "" },
31 :custom_fields => {'2' => 'Value for field 2'}
48 :custom_fields => {'2' => 'Value for field 2'}
32 # find created issue
49 # find created issue
33 issue = Issue.find_by_subject("new test issue")
50 issue = Issue.find_by_subject("new test issue")
34 assert_kind_of Issue, issue
51 assert_kind_of Issue, issue
35
52
36 # check redirection
53 # check redirection
37 assert_redirected_to "issues/show"
54 assert_redirected_to "issues/show"
38 follow_redirect!
55 follow_redirect!
39 assert_equal issue, assigns(:issue)
56 assert_equal issue, assigns(:issue)
40
57
41 # check issue attributes
58 # check issue attributes
42 assert_equal 'jsmith', issue.author.login
59 assert_equal 'jsmith', issue.author.login
43 assert_equal 1, issue.project.id
60 assert_equal 1, issue.project.id
44 assert_equal 1, issue.status.id
61 assert_equal 1, issue.status.id
45 end
62 end
46
63
47 # add then remove 2 attachments to an issue
64 # add then remove 2 attachments to an issue
48 def test_issue_attachements
65 def test_issue_attachements
49 log_user('jsmith', 'jsmith')
66 log_user('jsmith', 'jsmith')
67 set_tmp_attachments_directory
50
68
51 post 'issues/edit/1',
69 post 'issues/edit/1',
52 :notes => 'Some notes',
70 :notes => 'Some notes',
53 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
71 :attachments => {'1' => {'file' => test_uploaded_file('testfile.txt', 'text/plain'), 'description' => 'This is an attachment'}}
54 assert_redirected_to "issues/show/1"
72 assert_redirected_to "issues/show/1"
55
73
56 # make sure attachment was saved
74 # make sure attachment was saved
57 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
75 attachment = Issue.find(1).attachments.find_by_filename("testfile.txt")
58 assert_kind_of Attachment, attachment
76 assert_kind_of Attachment, attachment
59 assert_equal Issue.find(1), attachment.container
77 assert_equal Issue.find(1), attachment.container
60 assert_equal 'This is an attachment', attachment.description
78 assert_equal 'This is an attachment', attachment.description
61 # verify the size of the attachment stored in db
79 # verify the size of the attachment stored in db
62 #assert_equal file_data_1.length, attachment.filesize
80 #assert_equal file_data_1.length, attachment.filesize
63 # verify that the attachment was written to disk
81 # verify that the attachment was written to disk
64 assert File.exist?(attachment.diskfile)
82 assert File.exist?(attachment.diskfile)
65
83
66 # remove the attachments
84 # remove the attachments
67 Issue.find(1).attachments.each(&:destroy)
85 Issue.find(1).attachments.each(&:destroy)
68 assert_equal 0, Issue.find(1).attachments.length
86 assert_equal 0, Issue.find(1).attachments.length
69 end
87 end
70
88
71 end
89 end
@@ -1,77 +1,83
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 ENV["RAILS_ENV"] ||= "test"
18 ENV["RAILS_ENV"] ||= "test"
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
19 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
20 require 'test_help'
20 require 'test_help'
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
21 require File.expand_path(File.dirname(__FILE__) + '/helper_testcase')
22
22
23 class Test::Unit::TestCase
23 class Test::Unit::TestCase
24 # Transactional fixtures accelerate your tests by wrapping each test method
24 # Transactional fixtures accelerate your tests by wrapping each test method
25 # in a transaction that's rolled back on completion. This ensures that the
25 # in a transaction that's rolled back on completion. This ensures that the
26 # test database remains unchanged so your fixtures don't have to be reloaded
26 # test database remains unchanged so your fixtures don't have to be reloaded
27 # between every test method. Fewer database queries means faster tests.
27 # between every test method. Fewer database queries means faster tests.
28 #
28 #
29 # Read Mike Clark's excellent walkthrough at
29 # Read Mike Clark's excellent walkthrough at
30 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
30 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
31 #
31 #
32 # Every Active Record database supports transactions except MyISAM tables
32 # Every Active Record database supports transactions except MyISAM tables
33 # in MySQL. Turn off transactional fixtures in this case; however, if you
33 # in MySQL. Turn off transactional fixtures in this case; however, if you
34 # don't care one way or the other, switching from MyISAM to InnoDB tables
34 # don't care one way or the other, switching from MyISAM to InnoDB tables
35 # is recommended.
35 # is recommended.
36 self.use_transactional_fixtures = true
36 self.use_transactional_fixtures = true
37
37
38 # Instantiated fixtures are slow, but give you @david where otherwise you
38 # Instantiated fixtures are slow, but give you @david where otherwise you
39 # would need people(:david). If you don't want to migrate your existing
39 # would need people(:david). If you don't want to migrate your existing
40 # test cases which use the @david style and don't mind the speed hit (each
40 # test cases which use the @david style and don't mind the speed hit (each
41 # instantiated fixtures translates to a database query per test method),
41 # instantiated fixtures translates to a database query per test method),
42 # then set this back to true.
42 # then set this back to true.
43 self.use_instantiated_fixtures = false
43 self.use_instantiated_fixtures = false
44
44
45 # Add more helper methods to be used by all tests here...
45 # Add more helper methods to be used by all tests here...
46
46
47 def log_user(login, password)
47 def log_user(login, password)
48 get "/account/login"
48 get "/account/login"
49 assert_equal nil, session[:user_id]
49 assert_equal nil, session[:user_id]
50 assert_response :success
50 assert_response :success
51 assert_template "account/login"
51 assert_template "account/login"
52 post "/account/login", :username => login, :password => password
52 post "/account/login", :username => login, :password => password
53 assert_redirected_to "my/page"
53 assert_redirected_to "my/page"
54 assert_equal login, User.find(session[:user_id]).login
54 assert_equal login, User.find(session[:user_id]).login
55 end
55 end
56
56
57 def test_uploaded_file(name, mime)
57 def test_uploaded_file(name, mime)
58 ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + "/files/#{name}", mime)
58 ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + "/files/#{name}", mime)
59 end
59 end
60
61 # Use a temporary directory for attachment related tests
62 def set_tmp_attachments_directory
63 Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
64 Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
65 end
60 end
66 end
61
67
62
68
63 # ActionController::TestUploadedFile bug
69 # ActionController::TestUploadedFile bug
64 # see http://dev.rubyonrails.org/ticket/4635
70 # see http://dev.rubyonrails.org/ticket/4635
65 class String
71 class String
66 def original_filename
72 def original_filename
67 "testfile.txt"
73 "testfile.txt"
68 end
74 end
69
75
70 def content_type
76 def content_type
71 "text/plain"
77 "text/plain"
72 end
78 end
73
79
74 def read
80 def read
75 self.to_s
81 self.to_s
76 end
82 end
77 end
83 end
General Comments 0
You need to be logged in to leave comments. Login now