##// END OF EJS Templates
Reset locale to "en" before helpers tests....
Jean-Philippe Lang -
r15850:09f4a7d3f68c
parent child
Show More
@@ -1,404 +1,407
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 if ENV["COVERAGE"]
18 if ENV["COVERAGE"]
19 require 'simplecov'
19 require 'simplecov'
20 require File.expand_path(File.dirname(__FILE__) + "/coverage/html_formatter")
20 require File.expand_path(File.dirname(__FILE__) + "/coverage/html_formatter")
21 SimpleCov.formatter = Redmine::Coverage::HtmlFormatter
21 SimpleCov.formatter = Redmine::Coverage::HtmlFormatter
22 SimpleCov.start 'rails'
22 SimpleCov.start 'rails'
23 end
23 end
24
24
25 $redmine_test_ldap_server = ENV['REDMINE_TEST_LDAP_SERVER'] || '127.0.0.1'
25 $redmine_test_ldap_server = ENV['REDMINE_TEST_LDAP_SERVER'] || '127.0.0.1'
26
26
27 ENV["RAILS_ENV"] = "test"
27 ENV["RAILS_ENV"] = "test"
28 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
28 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
29 require 'rails/test_help'
29 require 'rails/test_help'
30 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
30 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
31
31
32 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
32 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
33 include ObjectHelpers
33 include ObjectHelpers
34
34
35 require 'net/ldap'
35 require 'net/ldap'
36 require 'mocha/setup'
36 require 'mocha/setup'
37
37
38 Redmine::SudoMode.disable!
38 Redmine::SudoMode.disable!
39
39
40 class ActionView::TestCase
40 class ActionView::TestCase
41 helper :application
41 helper :application
42 include ApplicationHelper
42 include ApplicationHelper
43 end
43 end
44
44
45 class ActiveSupport::TestCase
45 class ActiveSupport::TestCase
46 include ActionDispatch::TestProcess
46 include ActionDispatch::TestProcess
47
47
48 self.use_transactional_fixtures = true
48 self.use_transactional_fixtures = true
49 self.use_instantiated_fixtures = false
49 self.use_instantiated_fixtures = false
50
50
51 def uploaded_test_file(name, mime)
51 def uploaded_test_file(name, mime)
52 fixture_file_upload("files/#{name}", mime, true)
52 fixture_file_upload("files/#{name}", mime, true)
53 end
53 end
54
54
55 # Mock out a file
55 # Mock out a file
56 def self.mock_file
56 def self.mock_file
57 file = 'a_file.png'
57 file = 'a_file.png'
58 file.stubs(:size).returns(32)
58 file.stubs(:size).returns(32)
59 file.stubs(:original_filename).returns('a_file.png')
59 file.stubs(:original_filename).returns('a_file.png')
60 file.stubs(:content_type).returns('image/png')
60 file.stubs(:content_type).returns('image/png')
61 file.stubs(:read).returns(false)
61 file.stubs(:read).returns(false)
62 file
62 file
63 end
63 end
64
64
65 def mock_file
65 def mock_file
66 self.class.mock_file
66 self.class.mock_file
67 end
67 end
68
68
69 def mock_file_with_options(options={})
69 def mock_file_with_options(options={})
70 file = ''
70 file = ''
71 file.stubs(:size).returns(32)
71 file.stubs(:size).returns(32)
72 original_filename = options[:original_filename] || nil
72 original_filename = options[:original_filename] || nil
73 file.stubs(:original_filename).returns(original_filename)
73 file.stubs(:original_filename).returns(original_filename)
74 content_type = options[:content_type] || nil
74 content_type = options[:content_type] || nil
75 file.stubs(:content_type).returns(content_type)
75 file.stubs(:content_type).returns(content_type)
76 file.stubs(:read).returns(false)
76 file.stubs(:read).returns(false)
77 file
77 file
78 end
78 end
79
79
80 # Use a temporary directory for attachment related tests
80 # Use a temporary directory for attachment related tests
81 def set_tmp_attachments_directory
81 def set_tmp_attachments_directory
82 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
82 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
83 unless File.directory?("#{Rails.root}/tmp/test/attachments")
83 unless File.directory?("#{Rails.root}/tmp/test/attachments")
84 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
84 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
85 end
85 end
86 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
86 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
87 end
87 end
88
88
89 def set_fixtures_attachments_directory
89 def set_fixtures_attachments_directory
90 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
90 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
91 end
91 end
92
92
93 def with_settings(options, &block)
93 def with_settings(options, &block)
94 saved_settings = options.keys.inject({}) do |h, k|
94 saved_settings = options.keys.inject({}) do |h, k|
95 h[k] = case Setting[k]
95 h[k] = case Setting[k]
96 when Symbol, false, true, nil
96 when Symbol, false, true, nil
97 Setting[k]
97 Setting[k]
98 else
98 else
99 Setting[k].dup
99 Setting[k].dup
100 end
100 end
101 h
101 h
102 end
102 end
103 options.each {|k, v| Setting[k] = v}
103 options.each {|k, v| Setting[k] = v}
104 yield
104 yield
105 ensure
105 ensure
106 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
106 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
107 end
107 end
108
108
109 # Yields the block with user as the current user
109 # Yields the block with user as the current user
110 def with_current_user(user, &block)
110 def with_current_user(user, &block)
111 saved_user = User.current
111 saved_user = User.current
112 User.current = user
112 User.current = user
113 yield
113 yield
114 ensure
114 ensure
115 User.current = saved_user
115 User.current = saved_user
116 end
116 end
117
117
118 def with_locale(locale, &block)
118 def with_locale(locale, &block)
119 saved_localed = ::I18n.locale
119 saved_localed = ::I18n.locale
120 ::I18n.locale = locale
120 ::I18n.locale = locale
121 yield
121 yield
122 ensure
122 ensure
123 ::I18n.locale = saved_localed
123 ::I18n.locale = saved_localed
124 end
124 end
125
125
126 def self.ldap_configured?
126 def self.ldap_configured?
127 @test_ldap = Net::LDAP.new(:host => $redmine_test_ldap_server, :port => 389)
127 @test_ldap = Net::LDAP.new(:host => $redmine_test_ldap_server, :port => 389)
128 return @test_ldap.bind
128 return @test_ldap.bind
129 rescue Exception => e
129 rescue Exception => e
130 # LDAP is not listening
130 # LDAP is not listening
131 return nil
131 return nil
132 end
132 end
133
133
134 def self.convert_installed?
134 def self.convert_installed?
135 Redmine::Thumbnail.convert_available?
135 Redmine::Thumbnail.convert_available?
136 end
136 end
137
137
138 def convert_installed?
138 def convert_installed?
139 self.class.convert_installed?
139 self.class.convert_installed?
140 end
140 end
141
141
142 # Returns the path to the test +vendor+ repository
142 # Returns the path to the test +vendor+ repository
143 def self.repository_path(vendor)
143 def self.repository_path(vendor)
144 path = Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
144 path = Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
145 # Unlike ruby, JRuby returns Rails.root with backslashes under Windows
145 # Unlike ruby, JRuby returns Rails.root with backslashes under Windows
146 path.tr("\\", "/")
146 path.tr("\\", "/")
147 end
147 end
148
148
149 # Returns the url of the subversion test repository
149 # Returns the url of the subversion test repository
150 def self.subversion_repository_url
150 def self.subversion_repository_url
151 path = repository_path('subversion')
151 path = repository_path('subversion')
152 path = '/' + path unless path.starts_with?('/')
152 path = '/' + path unless path.starts_with?('/')
153 "file://#{path}"
153 "file://#{path}"
154 end
154 end
155
155
156 # Returns true if the +vendor+ test repository is configured
156 # Returns true if the +vendor+ test repository is configured
157 def self.repository_configured?(vendor)
157 def self.repository_configured?(vendor)
158 File.directory?(repository_path(vendor))
158 File.directory?(repository_path(vendor))
159 end
159 end
160
160
161 def repository_path_hash(arr)
161 def repository_path_hash(arr)
162 hs = {}
162 hs = {}
163 hs[:path] = arr.join("/")
163 hs[:path] = arr.join("/")
164 hs[:param] = arr.join("/")
164 hs[:param] = arr.join("/")
165 hs
165 hs
166 end
166 end
167
167
168 def sqlite?
168 def sqlite?
169 ActiveRecord::Base.connection.adapter_name =~ /sqlite/i
169 ActiveRecord::Base.connection.adapter_name =~ /sqlite/i
170 end
170 end
171
171
172 def mysql?
172 def mysql?
173 ActiveRecord::Base.connection.adapter_name =~ /mysql/i
173 ActiveRecord::Base.connection.adapter_name =~ /mysql/i
174 end
174 end
175
175
176 def postgresql?
176 def postgresql?
177 ActiveRecord::Base.connection.adapter_name =~ /postgresql/i
177 ActiveRecord::Base.connection.adapter_name =~ /postgresql/i
178 end
178 end
179
179
180 def quoted_date(date)
180 def quoted_date(date)
181 date = Date.parse(date) if date.is_a?(String)
181 date = Date.parse(date) if date.is_a?(String)
182 ActiveRecord::Base.connection.quoted_date(date)
182 ActiveRecord::Base.connection.quoted_date(date)
183 end
183 end
184
184
185 # Asserts that a new record for the given class is created
185 # Asserts that a new record for the given class is created
186 # and returns it
186 # and returns it
187 def new_record(klass, &block)
187 def new_record(klass, &block)
188 new_records(klass, 1, &block).first
188 new_records(klass, 1, &block).first
189 end
189 end
190
190
191 # Asserts that count new records for the given class are created
191 # Asserts that count new records for the given class are created
192 # and returns them as an array order by object id
192 # and returns them as an array order by object id
193 def new_records(klass, count, &block)
193 def new_records(klass, count, &block)
194 assert_difference "#{klass}.count", count do
194 assert_difference "#{klass}.count", count do
195 yield
195 yield
196 end
196 end
197 klass.order(:id => :desc).limit(count).to_a.reverse
197 klass.order(:id => :desc).limit(count).to_a.reverse
198 end
198 end
199
199
200 def assert_save(object)
200 def assert_save(object)
201 saved = object.save
201 saved = object.save
202 message = "#{object.class} could not be saved"
202 message = "#{object.class} could not be saved"
203 errors = object.errors.full_messages.map {|m| "- #{m}"}
203 errors = object.errors.full_messages.map {|m| "- #{m}"}
204 message << ":\n#{errors.join("\n")}" if errors.any?
204 message << ":\n#{errors.join("\n")}" if errors.any?
205 assert_equal true, saved, message
205 assert_equal true, saved, message
206 end
206 end
207
207
208 def assert_select_error(arg)
208 def assert_select_error(arg)
209 assert_select '#errorExplanation', :text => arg
209 assert_select '#errorExplanation', :text => arg
210 end
210 end
211
211
212 def assert_include(expected, s, message=nil)
212 def assert_include(expected, s, message=nil)
213 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
213 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
214 end
214 end
215
215
216 def assert_not_include(expected, s, message=nil)
216 def assert_not_include(expected, s, message=nil)
217 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
217 assert !s.include?(expected), (message || "\"#{expected}\" found in \"#{s}\"")
218 end
218 end
219
219
220 def assert_select_in(text, *args, &block)
220 def assert_select_in(text, *args, &block)
221 d = Nokogiri::HTML(CGI::unescapeHTML(String.new(text))).root
221 d = Nokogiri::HTML(CGI::unescapeHTML(String.new(text))).root
222 assert_select(d, *args, &block)
222 assert_select(d, *args, &block)
223 end
223 end
224
224
225 def assert_select_email(*args, &block)
225 def assert_select_email(*args, &block)
226 email = ActionMailer::Base.deliveries.last
226 email = ActionMailer::Base.deliveries.last
227 assert_not_nil email
227 assert_not_nil email
228 html_body = email.parts.detect {|part| part.content_type.include?('text/html')}.try(&:body)
228 html_body = email.parts.detect {|part| part.content_type.include?('text/html')}.try(&:body)
229 assert_not_nil html_body
229 assert_not_nil html_body
230 assert_select_in html_body.encoded, *args, &block
230 assert_select_in html_body.encoded, *args, &block
231 end
231 end
232
232
233 def assert_mail_body_match(expected, mail, message=nil)
233 def assert_mail_body_match(expected, mail, message=nil)
234 if expected.is_a?(String)
234 if expected.is_a?(String)
235 assert_include expected, mail_body(mail), message
235 assert_include expected, mail_body(mail), message
236 else
236 else
237 assert_match expected, mail_body(mail), message
237 assert_match expected, mail_body(mail), message
238 end
238 end
239 end
239 end
240
240
241 def assert_mail_body_no_match(expected, mail, message=nil)
241 def assert_mail_body_no_match(expected, mail, message=nil)
242 if expected.is_a?(String)
242 if expected.is_a?(String)
243 assert_not_include expected, mail_body(mail), message
243 assert_not_include expected, mail_body(mail), message
244 else
244 else
245 assert_no_match expected, mail_body(mail), message
245 assert_no_match expected, mail_body(mail), message
246 end
246 end
247 end
247 end
248
248
249 def mail_body(mail)
249 def mail_body(mail)
250 mail.parts.first.body.encoded
250 mail.parts.first.body.encoded
251 end
251 end
252
252
253 # Returns the lft value for a new root issue
253 # Returns the lft value for a new root issue
254 def new_issue_lft
254 def new_issue_lft
255 1
255 1
256 end
256 end
257 end
257 end
258
258
259 module Redmine
259 module Redmine
260 class RoutingTest < ActionDispatch::IntegrationTest
260 class RoutingTest < ActionDispatch::IntegrationTest
261 def should_route(arg)
261 def should_route(arg)
262 arg = arg.dup
262 arg = arg.dup
263 request = arg.keys.detect {|key| key.is_a?(String)}
263 request = arg.keys.detect {|key| key.is_a?(String)}
264 raise ArgumentError unless request
264 raise ArgumentError unless request
265 options = arg.slice!(request)
265 options = arg.slice!(request)
266
266
267 raise ArgumentError unless request =~ /\A(GET|POST|PUT|PATCH|DELETE)\s+(.+)\z/
267 raise ArgumentError unless request =~ /\A(GET|POST|PUT|PATCH|DELETE)\s+(.+)\z/
268 method, path = $1.downcase.to_sym, $2
268 method, path = $1.downcase.to_sym, $2
269
269
270 raise ArgumentError unless arg.values.first =~ /\A(.+)#(.+)\z/
270 raise ArgumentError unless arg.values.first =~ /\A(.+)#(.+)\z/
271 controller, action = $1, $2
271 controller, action = $1, $2
272
272
273 assert_routing(
273 assert_routing(
274 {:method => method, :path => path},
274 {:method => method, :path => path},
275 options.merge(:controller => controller, :action => action)
275 options.merge(:controller => controller, :action => action)
276 )
276 )
277 end
277 end
278 end
278 end
279
279
280 class HelperTest < ActionView::TestCase
280 class HelperTest < ActionView::TestCase
281 include Redmine::I18n
282
281 def setup
283 def setup
282 super
284 super
283 User.current = nil
285 User.current = nil
286 ::I18n.locale = 'en'
284 end
287 end
285 end
288 end
286
289
287 class ControllerTest < ActionController::TestCase
290 class ControllerTest < ActionController::TestCase
288 # Returns the issues that are displayed in the list in the same order
291 # Returns the issues that are displayed in the list in the same order
289 def issues_in_list
292 def issues_in_list
290 ids = css_select('tr.issue td.id').map(&:text).map(&:to_i)
293 ids = css_select('tr.issue td.id').map(&:text).map(&:to_i)
291 Issue.where(:id => ids).sort_by {|issue| ids.index(issue.id)}
294 Issue.where(:id => ids).sort_by {|issue| ids.index(issue.id)}
292 end
295 end
293
296
294 # Return the columns that are displayed in the list
297 # Return the columns that are displayed in the list
295 def columns_in_issues_list
298 def columns_in_issues_list
296 css_select('table.issues thead th:not(.checkbox)').map(&:text)
299 css_select('table.issues thead th:not(.checkbox)').map(&:text)
297 end
300 end
298
301
299 # Verifies that the query filters match the expected filters
302 # Verifies that the query filters match the expected filters
300 def assert_query_filters(expected_filters)
303 def assert_query_filters(expected_filters)
301 response.body =~ /initFilters\(\);\s*((addFilter\(.+\);\s*)*)/
304 response.body =~ /initFilters\(\);\s*((addFilter\(.+\);\s*)*)/
302 filter_init = $1.to_s
305 filter_init = $1.to_s
303
306
304 expected_filters.each do |field, operator, values|
307 expected_filters.each do |field, operator, values|
305 s = "addFilter(#{field.to_json}, #{operator.to_json}, #{Array(values).to_json});"
308 s = "addFilter(#{field.to_json}, #{operator.to_json}, #{Array(values).to_json});"
306 assert_include s, filter_init
309 assert_include s, filter_init
307 end
310 end
308 assert_equal expected_filters.size, filter_init.scan("addFilter").size, "filters counts don't match"
311 assert_equal expected_filters.size, filter_init.scan("addFilter").size, "filters counts don't match"
309 end
312 end
310
313
311 def process(method, path, parameters={}, session={}, flash={})
314 def process(method, path, parameters={}, session={}, flash={})
312 if parameters.key?(:params) || parameters.key?(:session)
315 if parameters.key?(:params) || parameters.key?(:session)
313 raise ArgumentError if session.present?
316 raise ArgumentError if session.present?
314 super method, path, parameters[:params], parameters[:session], parameters.except(:params, :session)
317 super method, path, parameters[:params], parameters[:session], parameters.except(:params, :session)
315 else
318 else
316 super
319 super
317 end
320 end
318 end
321 end
319 end
322 end
320
323
321 class IntegrationTest < ActionDispatch::IntegrationTest
324 class IntegrationTest < ActionDispatch::IntegrationTest
322 def log_user(login, password)
325 def log_user(login, password)
323 User.anonymous
326 User.anonymous
324 get "/login"
327 get "/login"
325 assert_nil session[:user_id]
328 assert_nil session[:user_id]
326 assert_response :success
329 assert_response :success
327
330
328 post "/login", :username => login, :password => password
331 post "/login", :username => login, :password => password
329 assert_equal login, User.find(session[:user_id]).login
332 assert_equal login, User.find(session[:user_id]).login
330 end
333 end
331
334
332 def credentials(user, password=nil)
335 def credentials(user, password=nil)
333 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
336 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
334 end
337 end
335 end
338 end
336
339
337 module ApiTest
340 module ApiTest
338 API_FORMATS = %w(json xml).freeze
341 API_FORMATS = %w(json xml).freeze
339
342
340 # Base class for API tests
343 # Base class for API tests
341 class Base < Redmine::IntegrationTest
344 class Base < Redmine::IntegrationTest
342 def setup
345 def setup
343 Setting.rest_api_enabled = '1'
346 Setting.rest_api_enabled = '1'
344 end
347 end
345
348
346 def teardown
349 def teardown
347 Setting.rest_api_enabled = '0'
350 Setting.rest_api_enabled = '0'
348 end
351 end
349
352
350 # Uploads content using the XML API and returns the attachment token
353 # Uploads content using the XML API and returns the attachment token
351 def xml_upload(content, credentials)
354 def xml_upload(content, credentials)
352 upload('xml', content, credentials)
355 upload('xml', content, credentials)
353 end
356 end
354
357
355 # Uploads content using the JSON API and returns the attachment token
358 # Uploads content using the JSON API and returns the attachment token
356 def json_upload(content, credentials)
359 def json_upload(content, credentials)
357 upload('json', content, credentials)
360 upload('json', content, credentials)
358 end
361 end
359
362
360 def upload(format, content, credentials)
363 def upload(format, content, credentials)
361 set_tmp_attachments_directory
364 set_tmp_attachments_directory
362 assert_difference 'Attachment.count' do
365 assert_difference 'Attachment.count' do
363 post "/uploads.#{format}", content, {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials)
366 post "/uploads.#{format}", content, {"CONTENT_TYPE" => 'application/octet-stream'}.merge(credentials)
364 assert_response :created
367 assert_response :created
365 end
368 end
366 data = response_data
369 data = response_data
367 assert_kind_of Hash, data['upload']
370 assert_kind_of Hash, data['upload']
368 token = data['upload']['token']
371 token = data['upload']['token']
369 assert_not_nil token
372 assert_not_nil token
370 token
373 token
371 end
374 end
372
375
373 # Parses the response body based on its content type
376 # Parses the response body based on its content type
374 def response_data
377 def response_data
375 unless response.content_type.to_s =~ /^application\/(.+)/
378 unless response.content_type.to_s =~ /^application\/(.+)/
376 raise "Unexpected response type: #{response.content_type}"
379 raise "Unexpected response type: #{response.content_type}"
377 end
380 end
378 format = $1
381 format = $1
379 case format
382 case format
380 when 'xml'
383 when 'xml'
381 Hash.from_xml(response.body)
384 Hash.from_xml(response.body)
382 when 'json'
385 when 'json'
383 ActiveSupport::JSON.decode(response.body)
386 ActiveSupport::JSON.decode(response.body)
384 else
387 else
385 raise "Unknown response format: #{format}"
388 raise "Unknown response format: #{format}"
386 end
389 end
387 end
390 end
388 end
391 end
389
392
390 class Routing < Redmine::RoutingTest
393 class Routing < Redmine::RoutingTest
391 def should_route(arg)
394 def should_route(arg)
392 arg = arg.dup
395 arg = arg.dup
393 request = arg.keys.detect {|key| key.is_a?(String)}
396 request = arg.keys.detect {|key| key.is_a?(String)}
394 raise ArgumentError unless request
397 raise ArgumentError unless request
395 options = arg.slice!(request)
398 options = arg.slice!(request)
396
399
397 API_FORMATS.each do |format|
400 API_FORMATS.each do |format|
398 format_request = request.sub /$/, ".#{format}"
401 format_request = request.sub /$/, ".#{format}"
399 super options.merge(format_request => arg[request], :format => format)
402 super options.merge(format_request => arg[request], :format => format)
400 end
403 end
401 end
404 end
402 end
405 end
403 end
406 end
404 end
407 end
@@ -1,103 +1,102
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class ActivitiesHelperTest < Redmine::HelperTest
20 class ActivitiesHelperTest < Redmine::HelperTest
21 include ActivitiesHelper
21 include ActivitiesHelper
22 include Redmine::I18n
23
22
24 class MockEvent
23 class MockEvent
25 attr_reader :event_datetime, :event_group, :name
24 attr_reader :event_datetime, :event_group, :name
26
25
27 def initialize(group=nil)
26 def initialize(group=nil)
28 @@count ||= 0
27 @@count ||= 0
29 @name = "e#{@@count}"
28 @name = "e#{@@count}"
30 @event_datetime = Time.now + @@count.hours
29 @event_datetime = Time.now + @@count.hours
31 @event_group = group || self
30 @event_group = group || self
32 @@count += 1
31 @@count += 1
33 end
32 end
34
33
35 def self.clear
34 def self.clear
36 @@count = 0
35 @@count = 0
37 end
36 end
38 end
37 end
39
38
40 def setup
39 def setup
41 super
40 super
42 MockEvent.clear
41 MockEvent.clear
43 end
42 end
44
43
45 def test_sort_activity_events_should_sort_by_datetime
44 def test_sort_activity_events_should_sort_by_datetime
46 events = []
45 events = []
47 events << MockEvent.new
46 events << MockEvent.new
48 events << MockEvent.new
47 events << MockEvent.new
49 events << MockEvent.new
48 events << MockEvent.new
50
49
51 assert_equal [
50 assert_equal [
52 ['e2', false],
51 ['e2', false],
53 ['e1', false],
52 ['e1', false],
54 ['e0', false]
53 ['e0', false]
55 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
54 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
56 end
55 end
57
56
58 def test_sort_activity_events_should_group_events
57 def test_sort_activity_events_should_group_events
59 events = []
58 events = []
60 events << MockEvent.new
59 events << MockEvent.new
61 events << MockEvent.new(events[0])
60 events << MockEvent.new(events[0])
62 events << MockEvent.new(events[0])
61 events << MockEvent.new(events[0])
63
62
64 assert_equal [
63 assert_equal [
65 ['e2', false],
64 ['e2', false],
66 ['e1', true],
65 ['e1', true],
67 ['e0', true]
66 ['e0', true]
68 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
67 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
69 end
68 end
70
69
71 def test_sort_activity_events_with_group_not_in_set_should_group_events
70 def test_sort_activity_events_with_group_not_in_set_should_group_events
72 e = MockEvent.new
71 e = MockEvent.new
73 events = []
72 events = []
74 events << MockEvent.new(e)
73 events << MockEvent.new(e)
75 events << MockEvent.new(e)
74 events << MockEvent.new(e)
76
75
77 assert_equal [
76 assert_equal [
78 ['e2', false],
77 ['e2', false],
79 ['e1', true]
78 ['e1', true]
80 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
79 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
81 end
80 end
82
81
83 def test_sort_activity_events_should_sort_by_datetime_and_group
82 def test_sort_activity_events_should_sort_by_datetime_and_group
84 events = []
83 events = []
85 events << MockEvent.new
84 events << MockEvent.new
86 events << MockEvent.new
85 events << MockEvent.new
87 events << MockEvent.new
86 events << MockEvent.new
88 events << MockEvent.new(events[1])
87 events << MockEvent.new(events[1])
89 events << MockEvent.new(events[2])
88 events << MockEvent.new(events[2])
90 events << MockEvent.new
89 events << MockEvent.new
91 events << MockEvent.new(events[2])
90 events << MockEvent.new(events[2])
92
91
93 assert_equal [
92 assert_equal [
94 ['e6', false],
93 ['e6', false],
95 ['e4', true],
94 ['e4', true],
96 ['e2', true],
95 ['e2', true],
97 ['e5', false],
96 ['e5', false],
98 ['e3', false],
97 ['e3', false],
99 ['e1', true],
98 ['e1', true],
100 ['e0', false]
99 ['e0', false]
101 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
100 ], sort_activity_events(events).map {|event, grouped| [event.name, grouped]}
102 end
101 end
103 end
102 end
@@ -1,1576 +1,1575
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../../test_helper', __FILE__)
20 require File.expand_path('../../../test_helper', __FILE__)
21
21
22 class ApplicationHelperTest < Redmine::HelperTest
22 class ApplicationHelperTest < Redmine::HelperTest
23 include Redmine::I18n
24 include ERB::Util
23 include ERB::Util
25 include Rails.application.routes.url_helpers
24 include Rails.application.routes.url_helpers
26
25
27 fixtures :projects, :enabled_modules,
26 fixtures :projects, :enabled_modules,
28 :users, :email_addresses,
27 :users, :email_addresses,
29 :members, :member_roles, :roles,
28 :members, :member_roles, :roles,
30 :repositories, :changesets,
29 :repositories, :changesets,
31 :projects_trackers,
30 :projects_trackers,
32 :trackers, :issue_statuses, :issues, :versions, :documents,
31 :trackers, :issue_statuses, :issues, :versions, :documents,
33 :wikis, :wiki_pages, :wiki_contents,
32 :wikis, :wiki_pages, :wiki_contents,
34 :boards, :messages, :news,
33 :boards, :messages, :news,
35 :attachments, :enumerations
34 :attachments, :enumerations
36
35
37 def setup
36 def setup
38 super
37 super
39 set_tmp_attachments_directory
38 set_tmp_attachments_directory
40 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82".force_encoding('UTF-8')
39 @russian_test = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82".force_encoding('UTF-8')
41 end
40 end
42
41
43 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
42 test "#link_to_if_authorized for authorized user should allow using the :controller and :action for the target link" do
44 User.current = User.find_by_login('admin')
43 User.current = User.find_by_login('admin')
45
44
46 @project = Issue.first.project # Used by helper
45 @project = Issue.first.project # Used by helper
47 response = link_to_if_authorized('By controller/actionr',
46 response = link_to_if_authorized('By controller/actionr',
48 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
47 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
49 assert_match /href/, response
48 assert_match /href/, response
50 end
49 end
51
50
52 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
51 test "#link_to_if_authorized for unauthorized user should display nothing if user isn't authorized" do
53 User.current = User.find_by_login('dlopper')
52 User.current = User.find_by_login('dlopper')
54 @project = Project.find('private-child')
53 @project = Project.find('private-child')
55 issue = @project.issues.first
54 issue = @project.issues.first
56 assert !issue.visible?
55 assert !issue.visible?
57
56
58 response = link_to_if_authorized('Never displayed',
57 response = link_to_if_authorized('Never displayed',
59 {:controller => 'issues', :action => 'show', :id => issue})
58 {:controller => 'issues', :action => 'show', :id => issue})
60 assert_nil response
59 assert_nil response
61 end
60 end
62
61
63 def test_auto_links
62 def test_auto_links
64 to_test = {
63 to_test = {
65 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
64 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
66 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
65 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
67 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
66 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
68 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
67 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
69 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
68 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
70 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
69 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
71 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
70 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
72 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
71 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
73 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
72 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
74 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
73 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
75 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
74 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
76 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
75 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
77 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
76 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
78 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
77 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
79 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
78 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
80 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
79 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
81 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
80 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
82 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
81 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
83 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
82 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
84 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
83 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
85 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
84 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
86 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
85 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
87 # two exclamation marks
86 # two exclamation marks
88 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
87 'http://example.net/path!602815048C7B5C20!302.html' => '<a class="external" href="http://example.net/path!602815048C7B5C20!302.html">http://example.net/path!602815048C7B5C20!302.html</a>',
89 # escaping
88 # escaping
90 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
89 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
91 # wrap in angle brackets
90 # wrap in angle brackets
92 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
91 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;',
93 # invalid urls
92 # invalid urls
94 'http://' => 'http://',
93 'http://' => 'http://',
95 'www.' => 'www.',
94 'www.' => 'www.',
96 'test-www.bar.com' => 'test-www.bar.com',
95 'test-www.bar.com' => 'test-www.bar.com',
97 }
96 }
98 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
97 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
99 end
98 end
100
99
101 def test_auto_links_with_non_ascii_characters
100 def test_auto_links_with_non_ascii_characters
102 to_test = {
101 to_test = {
103 "http://foo.bar/#{@russian_test}" =>
102 "http://foo.bar/#{@russian_test}" =>
104 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
103 %|<a class="external" href="http://foo.bar/#{@russian_test}">http://foo.bar/#{@russian_test}</a>|
105 }
104 }
106 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
105 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
107 end
106 end
108
107
109 def test_auto_mailto
108 def test_auto_mailto
110 to_test = {
109 to_test = {
111 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
110 'test@foo.bar' => '<a class="email" href="mailto:test@foo.bar">test@foo.bar</a>',
112 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
111 'test@www.foo.bar' => '<a class="email" href="mailto:test@www.foo.bar">test@www.foo.bar</a>',
113 }
112 }
114 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
113 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
115 end
114 end
116
115
117 def test_inline_images
116 def test_inline_images
118 to_test = {
117 to_test = {
119 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
118 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
120 'floating !>http://foo.bar/image.jpg!' => 'floating <span style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></span>',
119 'floating !>http://foo.bar/image.jpg!' => 'floating <span style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></span>',
121 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
120 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
122 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
121 'with style !{width:100px;height:100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height:100px;" alt="" />',
123 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
122 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
124 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
123 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
125 }
124 }
126 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
125 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
127 end
126 end
128
127
129 def test_inline_images_inside_tags
128 def test_inline_images_inside_tags
130 raw = <<-RAW
129 raw = <<-RAW
131 h1. !foo.png! Heading
130 h1. !foo.png! Heading
132
131
133 Centered image:
132 Centered image:
134
133
135 p=. !bar.gif!
134 p=. !bar.gif!
136 RAW
135 RAW
137
136
138 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
137 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
139 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
138 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
140 end
139 end
141
140
142 def test_attached_images
141 def test_attached_images
143 to_test = {
142 to_test = {
144 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
143 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
145 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
144 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" />',
146 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
145 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
147 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
146 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
148 # link image
147 # link image
149 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
148 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3/logo.gif" title="This is a logo" alt="This is a logo" /></a>',
150 }
149 }
151 attachments = Attachment.all
150 attachments = Attachment.all
152 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
151 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
153 end
152 end
154
153
155 def test_attached_images_with_textile_and_non_ascii_filename
154 def test_attached_images_with_textile_and_non_ascii_filename
156 attachment = Attachment.generate!(:filename => 'cafΓ©.jpg')
155 attachment = Attachment.generate!(:filename => 'cafΓ©.jpg')
157 with_settings :text_formatting => 'textile' do
156 with_settings :text_formatting => 'textile' do
158 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="" />),
157 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="" />),
159 textilizable("!cafΓ©.jpg!)", :attachments => [attachment])
158 textilizable("!cafΓ©.jpg!)", :attachments => [attachment])
160 end
159 end
161 end
160 end
162
161
163 def test_attached_images_with_markdown_and_non_ascii_filename
162 def test_attached_images_with_markdown_and_non_ascii_filename
164 skip unless Object.const_defined?(:Redcarpet)
163 skip unless Object.const_defined?(:Redcarpet)
165
164
166 attachment = Attachment.generate!(:filename => 'cafΓ©.jpg')
165 attachment = Attachment.generate!(:filename => 'cafΓ©.jpg')
167 with_settings :text_formatting => 'markdown' do
166 with_settings :text_formatting => 'markdown' do
168 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="" />),
167 assert_include %(<img src="/attachments/download/#{attachment.id}/caf%C3%A9.jpg" alt="" />),
169 textilizable("![](cafΓ©.jpg)", :attachments => [attachment])
168 textilizable("![](cafΓ©.jpg)", :attachments => [attachment])
170 end
169 end
171 end
170 end
172
171
173 def test_attached_images_filename_extension
172 def test_attached_images_filename_extension
174 set_tmp_attachments_directory
173 set_tmp_attachments_directory
175 a1 = Attachment.new(
174 a1 = Attachment.new(
176 :container => Issue.find(1),
175 :container => Issue.find(1),
177 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
176 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
178 :author => User.find(1))
177 :author => User.find(1))
179 assert a1.save
178 assert a1.save
180 assert_equal "testtest.JPG", a1.filename
179 assert_equal "testtest.JPG", a1.filename
181 assert_equal "image/jpeg", a1.content_type
180 assert_equal "image/jpeg", a1.content_type
182 assert a1.image?
181 assert a1.image?
183
182
184 a2 = Attachment.new(
183 a2 = Attachment.new(
185 :container => Issue.find(1),
184 :container => Issue.find(1),
186 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
185 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
187 :author => User.find(1))
186 :author => User.find(1))
188 assert a2.save
187 assert a2.save
189 assert_equal "testtest.jpeg", a2.filename
188 assert_equal "testtest.jpeg", a2.filename
190 assert_equal "image/jpeg", a2.content_type
189 assert_equal "image/jpeg", a2.content_type
191 assert a2.image?
190 assert a2.image?
192
191
193 a3 = Attachment.new(
192 a3 = Attachment.new(
194 :container => Issue.find(1),
193 :container => Issue.find(1),
195 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
194 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
196 :author => User.find(1))
195 :author => User.find(1))
197 assert a3.save
196 assert a3.save
198 assert_equal "testtest.JPE", a3.filename
197 assert_equal "testtest.JPE", a3.filename
199 assert_equal "image/jpeg", a3.content_type
198 assert_equal "image/jpeg", a3.content_type
200 assert a3.image?
199 assert a3.image?
201
200
202 a4 = Attachment.new(
201 a4 = Attachment.new(
203 :container => Issue.find(1),
202 :container => Issue.find(1),
204 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
203 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
205 :author => User.find(1))
204 :author => User.find(1))
206 assert a4.save
205 assert a4.save
207 assert_equal "Testtest.BMP", a4.filename
206 assert_equal "Testtest.BMP", a4.filename
208 assert_equal "image/x-ms-bmp", a4.content_type
207 assert_equal "image/x-ms-bmp", a4.content_type
209 assert a4.image?
208 assert a4.image?
210
209
211 to_test = {
210 to_test = {
212 'Inline image: !testtest.jpg!' =>
211 'Inline image: !testtest.jpg!' =>
213 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
212 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '/testtest.JPG" alt="" />',
214 'Inline image: !testtest.jpeg!' =>
213 'Inline image: !testtest.jpeg!' =>
215 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
214 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testtest.jpeg" alt="" />',
216 'Inline image: !testtest.jpe!' =>
215 'Inline image: !testtest.jpe!' =>
217 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
216 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '/testtest.JPE" alt="" />',
218 'Inline image: !testtest.bmp!' =>
217 'Inline image: !testtest.bmp!' =>
219 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
218 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '/Testtest.BMP" alt="" />',
220 }
219 }
221
220
222 attachments = [a1, a2, a3, a4]
221 attachments = [a1, a2, a3, a4]
223 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
222 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
224 end
223 end
225
224
226 def test_attached_images_should_read_later
225 def test_attached_images_should_read_later
227 set_fixtures_attachments_directory
226 set_fixtures_attachments_directory
228 a1 = Attachment.find(16)
227 a1 = Attachment.find(16)
229 assert_equal "testfile.png", a1.filename
228 assert_equal "testfile.png", a1.filename
230 assert a1.readable?
229 assert a1.readable?
231 assert (! a1.visible?(User.anonymous))
230 assert (! a1.visible?(User.anonymous))
232 assert a1.visible?(User.find(2))
231 assert a1.visible?(User.find(2))
233 a2 = Attachment.find(17)
232 a2 = Attachment.find(17)
234 assert_equal "testfile.PNG", a2.filename
233 assert_equal "testfile.PNG", a2.filename
235 assert a2.readable?
234 assert a2.readable?
236 assert (! a2.visible?(User.anonymous))
235 assert (! a2.visible?(User.anonymous))
237 assert a2.visible?(User.find(2))
236 assert a2.visible?(User.find(2))
238 assert a1.created_on < a2.created_on
237 assert a1.created_on < a2.created_on
239
238
240 to_test = {
239 to_test = {
241 'Inline image: !testfile.png!' =>
240 'Inline image: !testfile.png!' =>
242 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
241 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
243 'Inline image: !Testfile.PNG!' =>
242 'Inline image: !Testfile.PNG!' =>
244 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
243 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '/testfile.PNG" alt="" />',
245 }
244 }
246 attachments = [a1, a2]
245 attachments = [a1, a2]
247 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
246 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
248 set_tmp_attachments_directory
247 set_tmp_attachments_directory
249 end
248 end
250
249
251 def test_textile_external_links
250 def test_textile_external_links
252 to_test = {
251 to_test = {
253 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
252 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
254 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
253 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
255 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
254 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
256 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
255 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
257 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
256 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
258 # no multiline link text
257 # no multiline link text
259 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
258 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />and another on a second line\":test",
260 # mailto link
259 # mailto link
261 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
260 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
262 # two exclamation marks
261 # two exclamation marks
263 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
262 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
264 # escaping
263 # escaping
265 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
264 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
266 }
265 }
267 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
266 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
268 end
267 end
269
268
270 def test_textile_external_links_with_non_ascii_characters
269 def test_textile_external_links_with_non_ascii_characters
271 to_test = {
270 to_test = {
272 %|This is a "link":http://foo.bar/#{@russian_test}| =>
271 %|This is a "link":http://foo.bar/#{@russian_test}| =>
273 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
272 %|This is a <a href="http://foo.bar/#{@russian_test}" class="external">link</a>|
274 }
273 }
275 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
274 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
276 end
275 end
277
276
278 def test_redmine_links
277 def test_redmine_links
279 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
278 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
280 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
279 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
281 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
280 note_link = link_to('#3-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
282 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
281 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
283 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
282 note_link2 = link_to('#3#note-14', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
284 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
283 :class => Issue.find(3).css_classes, :title => 'Bug: Error 281 when updating a recipe (New)')
285
284
286 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
285 revision_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
287 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
286 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
288 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
287 revision_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
289 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
288 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
290
289
291 changeset_link2 = link_to('691322a8eb01e11fd7',
290 changeset_link2 = link_to('691322a8eb01e11fd7',
292 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
291 {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
293 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
292 :class => 'changeset', :title => 'My very first commit do not escaping #<>&')
294
293
295 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
294 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
296 :class => 'document')
295 :class => 'document')
297
296
298 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
297 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
299 :class => 'version')
298 :class => 'version')
300
299
301 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
300 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
302
301
303 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
302 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
304
303
305 news_url = {:controller => 'news', :action => 'show', :id => 1}
304 news_url = {:controller => 'news', :action => 'show', :id => 1}
306
305
307 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
306 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
308
307
309 source_url = '/projects/ecookbook/repository/entry/some/file'
308 source_url = '/projects/ecookbook/repository/entry/some/file'
310 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
309 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
311 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
310 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
312 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
311 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
313 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
312 source_url_with_branch = '/projects/ecookbook/repository/revisions/branch/entry/some/file'
314
313
315 export_url = '/projects/ecookbook/repository/raw/some/file'
314 export_url = '/projects/ecookbook/repository/raw/some/file'
316 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
315 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
317 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
316 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
318 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
317 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
319 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
318 export_url_with_branch = '/projects/ecookbook/repository/revisions/branch/raw/some/file'
320
319
321 to_test = {
320 to_test = {
322 # tickets
321 # tickets
323 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
322 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
324 # ticket notes
323 # ticket notes
325 '#3-14' => note_link,
324 '#3-14' => note_link,
326 '#3#note-14' => note_link2,
325 '#3#note-14' => note_link2,
327 # should not ignore leading zero
326 # should not ignore leading zero
328 '#03' => '#03',
327 '#03' => '#03',
329 # changesets
328 # changesets
330 'r1' => revision_link,
329 'r1' => revision_link,
331 'r1.' => "#{revision_link}.",
330 'r1.' => "#{revision_link}.",
332 'r1, r2' => "#{revision_link}, #{revision_link2}",
331 'r1, r2' => "#{revision_link}, #{revision_link2}",
333 'r1,r2' => "#{revision_link},#{revision_link2}",
332 'r1,r2' => "#{revision_link},#{revision_link2}",
334 'commit:691322a8eb01e11fd7' => changeset_link2,
333 'commit:691322a8eb01e11fd7' => changeset_link2,
335 # documents
334 # documents
336 'document#1' => document_link,
335 'document#1' => document_link,
337 'document:"Test document"' => document_link,
336 'document:"Test document"' => document_link,
338 # versions
337 # versions
339 'version#2' => version_link,
338 'version#2' => version_link,
340 'version:1.0' => version_link,
339 'version:1.0' => version_link,
341 'version:"1.0"' => version_link,
340 'version:"1.0"' => version_link,
342 # source
341 # source
343 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
342 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
344 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
343 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
345 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
344 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
346 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
345 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
347 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
346 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
348 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
347 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
349 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
348 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
350 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
349 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
351 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
350 'source:/some/file@branch' => link_to('source:/some/file@branch', source_url_with_branch, :class => 'source'),
352 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
351 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
353 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
352 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
354 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
353 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
355 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
354 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
356 # export
355 # export
357 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
356 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
358 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
357 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
359 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
358 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
360 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
359 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
361 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
360 'export:/some/file@branch' => link_to('export:/some/file@branch', export_url_with_branch, :class => 'source download'),
362 # forum
361 # forum
363 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
362 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
364 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
363 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
365 # message
364 # message
366 'message#4' => link_to('Post 2', message_url, :class => 'message'),
365 'message#4' => link_to('Post 2', message_url, :class => 'message'),
367 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
366 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
368 # news
367 # news
369 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
368 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
370 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
369 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
371 # project
370 # project
372 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
371 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
373 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
372 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
374 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
373 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
375 # not found
374 # not found
376 '#0123456789' => '#0123456789',
375 '#0123456789' => '#0123456789',
377 # invalid expressions
376 # invalid expressions
378 'source:' => 'source:',
377 'source:' => 'source:',
379 # url hash
378 # url hash
380 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
379 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
381 }
380 }
382 @project = Project.find(1)
381 @project = Project.find(1)
383 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
382 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
384 end
383 end
385
384
386 def test_should_not_parse_redmine_links_inside_link
385 def test_should_not_parse_redmine_links_inside_link
387 raw = "r1 should not be parsed in http://example.com/url-r1/"
386 raw = "r1 should not be parsed in http://example.com/url-r1/"
388 assert_match %r{<p><a class="changeset".*>r1</a> should not be parsed in <a class="external" href="http://example.com/url-r1/">http://example.com/url-r1/</a></p>},
387 assert_match %r{<p><a class="changeset".*>r1</a> should not be parsed in <a class="external" href="http://example.com/url-r1/">http://example.com/url-r1/</a></p>},
389 textilizable(raw, :project => Project.find(1))
388 textilizable(raw, :project => Project.find(1))
390 end
389 end
391
390
392 def test_redmine_links_with_a_different_project_before_current_project
391 def test_redmine_links_with_a_different_project_before_current_project
393 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
392 vp1 = Version.generate!(:project_id => 1, :name => '1.4.4')
394 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
393 vp3 = Version.generate!(:project_id => 3, :name => '1.4.4')
395 @project = Project.find(3)
394 @project = Project.find(3)
396 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
395 result1 = link_to("1.4.4", "/versions/#{vp1.id}", :class => "version")
397 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
396 result2 = link_to("1.4.4", "/versions/#{vp3.id}", :class => "version")
398 assert_equal "<p>#{result1} #{result2}</p>",
397 assert_equal "<p>#{result1} #{result2}</p>",
399 textilizable("ecookbook:version:1.4.4 version:1.4.4")
398 textilizable("ecookbook:version:1.4.4 version:1.4.4")
400 end
399 end
401
400
402 def test_escaped_redmine_links_should_not_be_parsed
401 def test_escaped_redmine_links_should_not_be_parsed
403 to_test = [
402 to_test = [
404 '#3.',
403 '#3.',
405 '#3-14.',
404 '#3-14.',
406 '#3#-note14.',
405 '#3#-note14.',
407 'r1',
406 'r1',
408 'document#1',
407 'document#1',
409 'document:"Test document"',
408 'document:"Test document"',
410 'version#2',
409 'version#2',
411 'version:1.0',
410 'version:1.0',
412 'version:"1.0"',
411 'version:"1.0"',
413 'source:/some/file'
412 'source:/some/file'
414 ]
413 ]
415 @project = Project.find(1)
414 @project = Project.find(1)
416 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
415 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
417 end
416 end
418
417
419 def test_cross_project_redmine_links
418 def test_cross_project_redmine_links
420 source_link = link_to('ecookbook:source:/some/file',
419 source_link = link_to('ecookbook:source:/some/file',
421 {:controller => 'repositories', :action => 'entry',
420 {:controller => 'repositories', :action => 'entry',
422 :id => 'ecookbook', :path => ['some', 'file']},
421 :id => 'ecookbook', :path => ['some', 'file']},
423 :class => 'source')
422 :class => 'source')
424 changeset_link = link_to('ecookbook:r2',
423 changeset_link = link_to('ecookbook:r2',
425 {:controller => 'repositories', :action => 'revision',
424 {:controller => 'repositories', :action => 'revision',
426 :id => 'ecookbook', :rev => 2},
425 :id => 'ecookbook', :rev => 2},
427 :class => 'changeset',
426 :class => 'changeset',
428 :title => 'This commit fixes #1, #2 and references #1 & #3')
427 :title => 'This commit fixes #1, #2 and references #1 & #3')
429 to_test = {
428 to_test = {
430 # documents
429 # documents
431 'document:"Test document"' => 'document:"Test document"',
430 'document:"Test document"' => 'document:"Test document"',
432 'ecookbook:document:"Test document"' =>
431 'ecookbook:document:"Test document"' =>
433 link_to("Test document", "/documents/1", :class => "document"),
432 link_to("Test document", "/documents/1", :class => "document"),
434 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
433 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
435 # versions
434 # versions
436 'version:"1.0"' => 'version:"1.0"',
435 'version:"1.0"' => 'version:"1.0"',
437 'ecookbook:version:"1.0"' =>
436 'ecookbook:version:"1.0"' =>
438 link_to("1.0", "/versions/2", :class => "version"),
437 link_to("1.0", "/versions/2", :class => "version"),
439 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
438 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
440 # changeset
439 # changeset
441 'r2' => 'r2',
440 'r2' => 'r2',
442 'ecookbook:r2' => changeset_link,
441 'ecookbook:r2' => changeset_link,
443 'invalid:r2' => 'invalid:r2',
442 'invalid:r2' => 'invalid:r2',
444 # source
443 # source
445 'source:/some/file' => 'source:/some/file',
444 'source:/some/file' => 'source:/some/file',
446 'ecookbook:source:/some/file' => source_link,
445 'ecookbook:source:/some/file' => source_link,
447 'invalid:source:/some/file' => 'invalid:source:/some/file',
446 'invalid:source:/some/file' => 'invalid:source:/some/file',
448 }
447 }
449 @project = Project.find(3)
448 @project = Project.find(3)
450 to_test.each do |text, result|
449 to_test.each do |text, result|
451 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
450 assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed"
452 end
451 end
453 end
452 end
454
453
455 def test_redmine_links_by_name_should_work_with_html_escaped_characters
454 def test_redmine_links_by_name_should_work_with_html_escaped_characters
456 v = Version.generate!(:name => "Test & Show.txt", :project_id => 1)
455 v = Version.generate!(:name => "Test & Show.txt", :project_id => 1)
457 link = link_to("Test & Show.txt", "/versions/#{v.id}", :class => "version")
456 link = link_to("Test & Show.txt", "/versions/#{v.id}", :class => "version")
458
457
459 @project = v.project
458 @project = v.project
460 assert_equal "<p>#{link}</p>", textilizable('version:"Test & Show.txt"')
459 assert_equal "<p>#{link}</p>", textilizable('version:"Test & Show.txt"')
461 end
460 end
462
461
463 def test_link_to_issue_subject
462 def test_link_to_issue_subject
464 issue = Issue.generate!(:subject => "01234567890123456789")
463 issue = Issue.generate!(:subject => "01234567890123456789")
465 str = link_to_issue(issue, :truncate => 10)
464 str = link_to_issue(issue, :truncate => 10)
466 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
465 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
467 assert_equal "#{result}: 0123456...", str
466 assert_equal "#{result}: 0123456...", str
468
467
469 issue = Issue.generate!(:subject => "<&>")
468 issue = Issue.generate!(:subject => "<&>")
470 str = link_to_issue(issue)
469 str = link_to_issue(issue)
471 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
470 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
472 assert_equal "#{result}: &lt;&amp;&gt;", str
471 assert_equal "#{result}: &lt;&amp;&gt;", str
473
472
474 issue = Issue.generate!(:subject => "<&>0123456789012345")
473 issue = Issue.generate!(:subject => "<&>0123456789012345")
475 str = link_to_issue(issue, :truncate => 10)
474 str = link_to_issue(issue, :truncate => 10)
476 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
475 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}", :class => issue.css_classes)
477 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
476 assert_equal "#{result}: &lt;&amp;&gt;0123...", str
478 end
477 end
479
478
480 def test_link_to_issue_title
479 def test_link_to_issue_title
481 long_str = "0123456789" * 5
480 long_str = "0123456789" * 5
482
481
483 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
482 issue = Issue.generate!(:subject => "#{long_str}01234567890123456789")
484 str = link_to_issue(issue, :subject => false)
483 str = link_to_issue(issue, :subject => false)
485 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
484 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
486 :class => issue.css_classes,
485 :class => issue.css_classes,
487 :title => "#{long_str}0123456...")
486 :title => "#{long_str}0123456...")
488 assert_equal result, str
487 assert_equal result, str
489
488
490 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
489 issue = Issue.generate!(:subject => "<&>#{long_str}01234567890123456789")
491 str = link_to_issue(issue, :subject => false)
490 str = link_to_issue(issue, :subject => false)
492 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
491 result = link_to("Bug ##{issue.id}", "/issues/#{issue.id}",
493 :class => issue.css_classes,
492 :class => issue.css_classes,
494 :title => "<&>#{long_str}0123...")
493 :title => "<&>#{long_str}0123...")
495 assert_equal result, str
494 assert_equal result, str
496 end
495 end
497
496
498 def test_multiple_repositories_redmine_links
497 def test_multiple_repositories_redmine_links
499 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
498 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn_repo-1', :url => 'file:///foo/hg')
500 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
499 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
501 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
500 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
502 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
501 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
503
502
504 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
503 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
505 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
504 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
506 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
505 svn_changeset_link = link_to('svn_repo-1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn_repo-1', :rev => 123},
507 :class => 'changeset', :title => '')
506 :class => 'changeset', :title => '')
508 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
507 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
509 :class => 'changeset', :title => '')
508 :class => 'changeset', :title => '')
510
509
511 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
510 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
512 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
511 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
513
512
514 to_test = {
513 to_test = {
515 'r2' => changeset_link,
514 'r2' => changeset_link,
516 'svn_repo-1|r123' => svn_changeset_link,
515 'svn_repo-1|r123' => svn_changeset_link,
517 'invalid|r123' => 'invalid|r123',
516 'invalid|r123' => 'invalid|r123',
518 'commit:hg1|abcd' => hg_changeset_link,
517 'commit:hg1|abcd' => hg_changeset_link,
519 'commit:invalid|abcd' => 'commit:invalid|abcd',
518 'commit:invalid|abcd' => 'commit:invalid|abcd',
520 # source
519 # source
521 'source:some/file' => source_link,
520 'source:some/file' => source_link,
522 'source:hg1|some/file' => hg_source_link,
521 'source:hg1|some/file' => hg_source_link,
523 'source:invalid|some/file' => 'source:invalid|some/file',
522 'source:invalid|some/file' => 'source:invalid|some/file',
524 }
523 }
525
524
526 @project = Project.find(1)
525 @project = Project.find(1)
527 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
526 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
528 end
527 end
529
528
530 def test_cross_project_multiple_repositories_redmine_links
529 def test_cross_project_multiple_repositories_redmine_links
531 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
530 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
532 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
531 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
533 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
532 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
534 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
533 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
535
534
536 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
535 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
537 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
536 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
538 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
537 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
539 :class => 'changeset', :title => '')
538 :class => 'changeset', :title => '')
540 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
539 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
541 :class => 'changeset', :title => '')
540 :class => 'changeset', :title => '')
542
541
543 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
542 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
544 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
543 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
545
544
546 to_test = {
545 to_test = {
547 'ecookbook:r2' => changeset_link,
546 'ecookbook:r2' => changeset_link,
548 'ecookbook:svn1|r123' => svn_changeset_link,
547 'ecookbook:svn1|r123' => svn_changeset_link,
549 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
548 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
550 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
549 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
551 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
550 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
552 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
551 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
553 # source
552 # source
554 'ecookbook:source:some/file' => source_link,
553 'ecookbook:source:some/file' => source_link,
555 'ecookbook:source:hg1|some/file' => hg_source_link,
554 'ecookbook:source:hg1|some/file' => hg_source_link,
556 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
555 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
557 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
556 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
558 }
557 }
559
558
560 @project = Project.find(3)
559 @project = Project.find(3)
561 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
560 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
562 end
561 end
563
562
564 def test_redmine_links_git_commit
563 def test_redmine_links_git_commit
565 changeset_link = link_to('abcd',
564 changeset_link = link_to('abcd',
566 {
565 {
567 :controller => 'repositories',
566 :controller => 'repositories',
568 :action => 'revision',
567 :action => 'revision',
569 :id => 'subproject1',
568 :id => 'subproject1',
570 :rev => 'abcd',
569 :rev => 'abcd',
571 },
570 },
572 :class => 'changeset', :title => 'test commit')
571 :class => 'changeset', :title => 'test commit')
573 to_test = {
572 to_test = {
574 'commit:abcd' => changeset_link,
573 'commit:abcd' => changeset_link,
575 }
574 }
576 @project = Project.find(3)
575 @project = Project.find(3)
577 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
576 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
578 assert r
577 assert r
579 c = Changeset.new(:repository => r,
578 c = Changeset.new(:repository => r,
580 :committed_on => Time.now,
579 :committed_on => Time.now,
581 :revision => 'abcd',
580 :revision => 'abcd',
582 :scmid => 'abcd',
581 :scmid => 'abcd',
583 :comments => 'test commit')
582 :comments => 'test commit')
584 assert( c.save )
583 assert( c.save )
585 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
584 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
586 end
585 end
587
586
588 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
587 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
589 def test_redmine_links_darcs_commit
588 def test_redmine_links_darcs_commit
590 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
589 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
591 {
590 {
592 :controller => 'repositories',
591 :controller => 'repositories',
593 :action => 'revision',
592 :action => 'revision',
594 :id => 'subproject1',
593 :id => 'subproject1',
595 :rev => '123',
594 :rev => '123',
596 },
595 },
597 :class => 'changeset', :title => 'test commit')
596 :class => 'changeset', :title => 'test commit')
598 to_test = {
597 to_test = {
599 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
598 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
600 }
599 }
601 @project = Project.find(3)
600 @project = Project.find(3)
602 r = Repository::Darcs.create!(
601 r = Repository::Darcs.create!(
603 :project => @project, :url => '/tmp/test/darcs',
602 :project => @project, :url => '/tmp/test/darcs',
604 :log_encoding => 'UTF-8')
603 :log_encoding => 'UTF-8')
605 assert r
604 assert r
606 c = Changeset.new(:repository => r,
605 c = Changeset.new(:repository => r,
607 :committed_on => Time.now,
606 :committed_on => Time.now,
608 :revision => '123',
607 :revision => '123',
609 :scmid => '20080308225258-98289-abcd456efg.gz',
608 :scmid => '20080308225258-98289-abcd456efg.gz',
610 :comments => 'test commit')
609 :comments => 'test commit')
611 assert( c.save )
610 assert( c.save )
612 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
611 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
613 end
612 end
614
613
615 def test_redmine_links_mercurial_commit
614 def test_redmine_links_mercurial_commit
616 changeset_link_rev = link_to('r123',
615 changeset_link_rev = link_to('r123',
617 {
616 {
618 :controller => 'repositories',
617 :controller => 'repositories',
619 :action => 'revision',
618 :action => 'revision',
620 :id => 'subproject1',
619 :id => 'subproject1',
621 :rev => '123' ,
620 :rev => '123' ,
622 },
621 },
623 :class => 'changeset', :title => 'test commit')
622 :class => 'changeset', :title => 'test commit')
624 changeset_link_commit = link_to('abcd',
623 changeset_link_commit = link_to('abcd',
625 {
624 {
626 :controller => 'repositories',
625 :controller => 'repositories',
627 :action => 'revision',
626 :action => 'revision',
628 :id => 'subproject1',
627 :id => 'subproject1',
629 :rev => 'abcd' ,
628 :rev => 'abcd' ,
630 },
629 },
631 :class => 'changeset', :title => 'test commit')
630 :class => 'changeset', :title => 'test commit')
632 to_test = {
631 to_test = {
633 'r123' => changeset_link_rev,
632 'r123' => changeset_link_rev,
634 'commit:abcd' => changeset_link_commit,
633 'commit:abcd' => changeset_link_commit,
635 }
634 }
636 @project = Project.find(3)
635 @project = Project.find(3)
637 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
636 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
638 assert r
637 assert r
639 c = Changeset.new(:repository => r,
638 c = Changeset.new(:repository => r,
640 :committed_on => Time.now,
639 :committed_on => Time.now,
641 :revision => '123',
640 :revision => '123',
642 :scmid => 'abcd',
641 :scmid => 'abcd',
643 :comments => 'test commit')
642 :comments => 'test commit')
644 assert( c.save )
643 assert( c.save )
645 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
644 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
646 end
645 end
647
646
648 def test_attachment_links
647 def test_attachment_links
649 text = 'attachment:error281.txt'
648 text = 'attachment:error281.txt'
650 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
649 result = link_to("error281.txt", "/attachments/download/1/error281.txt",
651 :class => "attachment")
650 :class => "attachment")
652 assert_equal "<p>#{result}</p>",
651 assert_equal "<p>#{result}</p>",
653 textilizable(text,
652 textilizable(text,
654 :attachments => Issue.find(3).attachments),
653 :attachments => Issue.find(3).attachments),
655 "#{text} failed"
654 "#{text} failed"
656 end
655 end
657
656
658 def test_attachment_link_should_link_to_latest_attachment
657 def test_attachment_link_should_link_to_latest_attachment
659 set_tmp_attachments_directory
658 set_tmp_attachments_directory
660 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
659 a1 = Attachment.generate!(:filename => "test.txt", :created_on => 1.hour.ago)
661 a2 = Attachment.generate!(:filename => "test.txt")
660 a2 = Attachment.generate!(:filename => "test.txt")
662 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
661 result = link_to("test.txt", "/attachments/download/#{a2.id}/test.txt",
663 :class => "attachment")
662 :class => "attachment")
664 assert_equal "<p>#{result}</p>",
663 assert_equal "<p>#{result}</p>",
665 textilizable('attachment:test.txt', :attachments => [a1, a2])
664 textilizable('attachment:test.txt', :attachments => [a1, a2])
666 end
665 end
667
666
668 def test_wiki_links
667 def test_wiki_links
669 russian_eacape = CGI.escape(@russian_test)
668 russian_eacape = CGI.escape(@russian_test)
670 to_test = {
669 to_test = {
671 '[[CookBook documentation]]' =>
670 '[[CookBook documentation]]' =>
672 link_to("CookBook documentation",
671 link_to("CookBook documentation",
673 "/projects/ecookbook/wiki/CookBook_documentation",
672 "/projects/ecookbook/wiki/CookBook_documentation",
674 :class => "wiki-page"),
673 :class => "wiki-page"),
675 '[[Another page|Page]]' =>
674 '[[Another page|Page]]' =>
676 link_to("Page",
675 link_to("Page",
677 "/projects/ecookbook/wiki/Another_page",
676 "/projects/ecookbook/wiki/Another_page",
678 :class => "wiki-page"),
677 :class => "wiki-page"),
679 # title content should be formatted
678 # title content should be formatted
680 '[[Another page|With _styled_ *title*]]' =>
679 '[[Another page|With _styled_ *title*]]' =>
681 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
680 link_to("With <em>styled</em> <strong>title</strong>".html_safe,
682 "/projects/ecookbook/wiki/Another_page",
681 "/projects/ecookbook/wiki/Another_page",
683 :class => "wiki-page"),
682 :class => "wiki-page"),
684 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
683 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' =>
685 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
684 link_to("With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;".html_safe,
686 "/projects/ecookbook/wiki/Another_page",
685 "/projects/ecookbook/wiki/Another_page",
687 :class => "wiki-page"),
686 :class => "wiki-page"),
688 # link with anchor
687 # link with anchor
689 '[[CookBook documentation#One-section]]' =>
688 '[[CookBook documentation#One-section]]' =>
690 link_to("CookBook documentation",
689 link_to("CookBook documentation",
691 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
690 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
692 :class => "wiki-page"),
691 :class => "wiki-page"),
693 '[[Another page#anchor|Page]]' =>
692 '[[Another page#anchor|Page]]' =>
694 link_to("Page",
693 link_to("Page",
695 "/projects/ecookbook/wiki/Another_page#anchor",
694 "/projects/ecookbook/wiki/Another_page#anchor",
696 :class => "wiki-page"),
695 :class => "wiki-page"),
697 # UTF8 anchor
696 # UTF8 anchor
698 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
697 "[[Another_page##{@russian_test}|#{@russian_test}]]" =>
699 link_to(@russian_test,
698 link_to(@russian_test,
700 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
699 "/projects/ecookbook/wiki/Another_page##{russian_eacape}",
701 :class => "wiki-page"),
700 :class => "wiki-page"),
702 # page that doesn't exist
701 # page that doesn't exist
703 '[[Unknown page]]' =>
702 '[[Unknown page]]' =>
704 link_to("Unknown page",
703 link_to("Unknown page",
705 "/projects/ecookbook/wiki/Unknown_page",
704 "/projects/ecookbook/wiki/Unknown_page",
706 :class => "wiki-page new"),
705 :class => "wiki-page new"),
707 '[[Unknown page|404]]' =>
706 '[[Unknown page|404]]' =>
708 link_to("404",
707 link_to("404",
709 "/projects/ecookbook/wiki/Unknown_page",
708 "/projects/ecookbook/wiki/Unknown_page",
710 :class => "wiki-page new"),
709 :class => "wiki-page new"),
711 # link to another project wiki
710 # link to another project wiki
712 '[[onlinestore:]]' =>
711 '[[onlinestore:]]' =>
713 link_to("onlinestore",
712 link_to("onlinestore",
714 "/projects/onlinestore/wiki",
713 "/projects/onlinestore/wiki",
715 :class => "wiki-page"),
714 :class => "wiki-page"),
716 '[[onlinestore:|Wiki]]' =>
715 '[[onlinestore:|Wiki]]' =>
717 link_to("Wiki",
716 link_to("Wiki",
718 "/projects/onlinestore/wiki",
717 "/projects/onlinestore/wiki",
719 :class => "wiki-page"),
718 :class => "wiki-page"),
720 '[[onlinestore:Start page]]' =>
719 '[[onlinestore:Start page]]' =>
721 link_to("Start page",
720 link_to("Start page",
722 "/projects/onlinestore/wiki/Start_page",
721 "/projects/onlinestore/wiki/Start_page",
723 :class => "wiki-page"),
722 :class => "wiki-page"),
724 '[[onlinestore:Start page|Text]]' =>
723 '[[onlinestore:Start page|Text]]' =>
725 link_to("Text",
724 link_to("Text",
726 "/projects/onlinestore/wiki/Start_page",
725 "/projects/onlinestore/wiki/Start_page",
727 :class => "wiki-page"),
726 :class => "wiki-page"),
728 '[[onlinestore:Unknown page]]' =>
727 '[[onlinestore:Unknown page]]' =>
729 link_to("Unknown page",
728 link_to("Unknown page",
730 "/projects/onlinestore/wiki/Unknown_page",
729 "/projects/onlinestore/wiki/Unknown_page",
731 :class => "wiki-page new"),
730 :class => "wiki-page new"),
732 # struck through link
731 # struck through link
733 '-[[Another page|Page]]-' =>
732 '-[[Another page|Page]]-' =>
734 "<del>".html_safe +
733 "<del>".html_safe +
735 link_to("Page",
734 link_to("Page",
736 "/projects/ecookbook/wiki/Another_page",
735 "/projects/ecookbook/wiki/Another_page",
737 :class => "wiki-page").html_safe +
736 :class => "wiki-page").html_safe +
738 "</del>".html_safe,
737 "</del>".html_safe,
739 '-[[Another page|Page]] link-' =>
738 '-[[Another page|Page]] link-' =>
740 "<del>".html_safe +
739 "<del>".html_safe +
741 link_to("Page",
740 link_to("Page",
742 "/projects/ecookbook/wiki/Another_page",
741 "/projects/ecookbook/wiki/Another_page",
743 :class => "wiki-page").html_safe +
742 :class => "wiki-page").html_safe +
744 " link</del>".html_safe,
743 " link</del>".html_safe,
745 # escaping
744 # escaping
746 '![[Another page|Page]]' => '[[Another page|Page]]',
745 '![[Another page|Page]]' => '[[Another page|Page]]',
747 # project does not exist
746 # project does not exist
748 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
747 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
749 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
748 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
750 }
749 }
751 @project = Project.find(1)
750 @project = Project.find(1)
752 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
751 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
753 end
752 end
754
753
755 def test_wiki_links_within_local_file_generation_context
754 def test_wiki_links_within_local_file_generation_context
756 to_test = {
755 to_test = {
757 # link to a page
756 # link to a page
758 '[[CookBook documentation]]' =>
757 '[[CookBook documentation]]' =>
759 link_to("CookBook documentation", "CookBook_documentation.html",
758 link_to("CookBook documentation", "CookBook_documentation.html",
760 :class => "wiki-page"),
759 :class => "wiki-page"),
761 '[[CookBook documentation|documentation]]' =>
760 '[[CookBook documentation|documentation]]' =>
762 link_to("documentation", "CookBook_documentation.html",
761 link_to("documentation", "CookBook_documentation.html",
763 :class => "wiki-page"),
762 :class => "wiki-page"),
764 '[[CookBook documentation#One-section]]' =>
763 '[[CookBook documentation#One-section]]' =>
765 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
764 link_to("CookBook documentation", "CookBook_documentation.html#One-section",
766 :class => "wiki-page"),
765 :class => "wiki-page"),
767 '[[CookBook documentation#One-section|documentation]]' =>
766 '[[CookBook documentation#One-section|documentation]]' =>
768 link_to("documentation", "CookBook_documentation.html#One-section",
767 link_to("documentation", "CookBook_documentation.html#One-section",
769 :class => "wiki-page"),
768 :class => "wiki-page"),
770 # page that doesn't exist
769 # page that doesn't exist
771 '[[Unknown page]]' =>
770 '[[Unknown page]]' =>
772 link_to("Unknown page", "Unknown_page.html",
771 link_to("Unknown page", "Unknown_page.html",
773 :class => "wiki-page new"),
772 :class => "wiki-page new"),
774 '[[Unknown page|404]]' =>
773 '[[Unknown page|404]]' =>
775 link_to("404", "Unknown_page.html",
774 link_to("404", "Unknown_page.html",
776 :class => "wiki-page new"),
775 :class => "wiki-page new"),
777 '[[Unknown page#anchor]]' =>
776 '[[Unknown page#anchor]]' =>
778 link_to("Unknown page", "Unknown_page.html#anchor",
777 link_to("Unknown page", "Unknown_page.html#anchor",
779 :class => "wiki-page new"),
778 :class => "wiki-page new"),
780 '[[Unknown page#anchor|404]]' =>
779 '[[Unknown page#anchor|404]]' =>
781 link_to("404", "Unknown_page.html#anchor",
780 link_to("404", "Unknown_page.html#anchor",
782 :class => "wiki-page new"),
781 :class => "wiki-page new"),
783 }
782 }
784 @project = Project.find(1)
783 @project = Project.find(1)
785 to_test.each do |text, result|
784 to_test.each do |text, result|
786 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
785 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local)
787 end
786 end
788 end
787 end
789
788
790 def test_wiki_links_within_wiki_page_context
789 def test_wiki_links_within_wiki_page_context
791 page = WikiPage.find_by_title('Another_page' )
790 page = WikiPage.find_by_title('Another_page' )
792 to_test = {
791 to_test = {
793 '[[CookBook documentation]]' =>
792 '[[CookBook documentation]]' =>
794 link_to("CookBook documentation",
793 link_to("CookBook documentation",
795 "/projects/ecookbook/wiki/CookBook_documentation",
794 "/projects/ecookbook/wiki/CookBook_documentation",
796 :class => "wiki-page"),
795 :class => "wiki-page"),
797 '[[CookBook documentation|documentation]]' =>
796 '[[CookBook documentation|documentation]]' =>
798 link_to("documentation",
797 link_to("documentation",
799 "/projects/ecookbook/wiki/CookBook_documentation",
798 "/projects/ecookbook/wiki/CookBook_documentation",
800 :class => "wiki-page"),
799 :class => "wiki-page"),
801 '[[CookBook documentation#One-section]]' =>
800 '[[CookBook documentation#One-section]]' =>
802 link_to("CookBook documentation",
801 link_to("CookBook documentation",
803 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
802 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
804 :class => "wiki-page"),
803 :class => "wiki-page"),
805 '[[CookBook documentation#One-section|documentation]]' =>
804 '[[CookBook documentation#One-section|documentation]]' =>
806 link_to("documentation",
805 link_to("documentation",
807 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
806 "/projects/ecookbook/wiki/CookBook_documentation#One-section",
808 :class => "wiki-page"),
807 :class => "wiki-page"),
809 # link to the current page
808 # link to the current page
810 '[[Another page]]' =>
809 '[[Another page]]' =>
811 link_to("Another page",
810 link_to("Another page",
812 "/projects/ecookbook/wiki/Another_page",
811 "/projects/ecookbook/wiki/Another_page",
813 :class => "wiki-page"),
812 :class => "wiki-page"),
814 '[[Another page|Page]]' =>
813 '[[Another page|Page]]' =>
815 link_to("Page",
814 link_to("Page",
816 "/projects/ecookbook/wiki/Another_page",
815 "/projects/ecookbook/wiki/Another_page",
817 :class => "wiki-page"),
816 :class => "wiki-page"),
818 '[[Another page#anchor]]' =>
817 '[[Another page#anchor]]' =>
819 link_to("Another page",
818 link_to("Another page",
820 "#anchor",
819 "#anchor",
821 :class => "wiki-page"),
820 :class => "wiki-page"),
822 '[[Another page#anchor|Page]]' =>
821 '[[Another page#anchor|Page]]' =>
823 link_to("Page",
822 link_to("Page",
824 "#anchor",
823 "#anchor",
825 :class => "wiki-page"),
824 :class => "wiki-page"),
826 # page that doesn't exist
825 # page that doesn't exist
827 '[[Unknown page]]' =>
826 '[[Unknown page]]' =>
828 link_to("Unknown page",
827 link_to("Unknown page",
829 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
828 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
830 :class => "wiki-page new"),
829 :class => "wiki-page new"),
831 '[[Unknown page|404]]' =>
830 '[[Unknown page|404]]' =>
832 link_to("404",
831 link_to("404",
833 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
832 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page",
834 :class => "wiki-page new"),
833 :class => "wiki-page new"),
835 '[[Unknown page#anchor]]' =>
834 '[[Unknown page#anchor]]' =>
836 link_to("Unknown page",
835 link_to("Unknown page",
837 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
836 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
838 :class => "wiki-page new"),
837 :class => "wiki-page new"),
839 '[[Unknown page#anchor|404]]' =>
838 '[[Unknown page#anchor|404]]' =>
840 link_to("404",
839 link_to("404",
841 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
840 "/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor",
842 :class => "wiki-page new"),
841 :class => "wiki-page new"),
843 }
842 }
844 @project = Project.find(1)
843 @project = Project.find(1)
845 to_test.each do |text, result|
844 to_test.each do |text, result|
846 assert_equal "<p>#{result}</p>",
845 assert_equal "<p>#{result}</p>",
847 textilizable(WikiContent.new( :text => text, :page => page ), :text)
846 textilizable(WikiContent.new( :text => text, :page => page ), :text)
848 end
847 end
849 end
848 end
850
849
851 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
850 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
852 to_test = {
851 to_test = {
853 # link to a page
852 # link to a page
854 '[[CookBook documentation]]' =>
853 '[[CookBook documentation]]' =>
855 link_to("CookBook documentation",
854 link_to("CookBook documentation",
856 "#CookBook_documentation",
855 "#CookBook_documentation",
857 :class => "wiki-page"),
856 :class => "wiki-page"),
858 '[[CookBook documentation|documentation]]' =>
857 '[[CookBook documentation|documentation]]' =>
859 link_to("documentation",
858 link_to("documentation",
860 "#CookBook_documentation",
859 "#CookBook_documentation",
861 :class => "wiki-page"),
860 :class => "wiki-page"),
862 '[[CookBook documentation#One-section]]' =>
861 '[[CookBook documentation#One-section]]' =>
863 link_to("CookBook documentation",
862 link_to("CookBook documentation",
864 "#CookBook_documentation_One-section",
863 "#CookBook_documentation_One-section",
865 :class => "wiki-page"),
864 :class => "wiki-page"),
866 '[[CookBook documentation#One-section|documentation]]' =>
865 '[[CookBook documentation#One-section|documentation]]' =>
867 link_to("documentation",
866 link_to("documentation",
868 "#CookBook_documentation_One-section",
867 "#CookBook_documentation_One-section",
869 :class => "wiki-page"),
868 :class => "wiki-page"),
870 # page that doesn't exist
869 # page that doesn't exist
871 '[[Unknown page]]' =>
870 '[[Unknown page]]' =>
872 link_to("Unknown page",
871 link_to("Unknown page",
873 "#Unknown_page",
872 "#Unknown_page",
874 :class => "wiki-page new"),
873 :class => "wiki-page new"),
875 '[[Unknown page|404]]' =>
874 '[[Unknown page|404]]' =>
876 link_to("404",
875 link_to("404",
877 "#Unknown_page",
876 "#Unknown_page",
878 :class => "wiki-page new"),
877 :class => "wiki-page new"),
879 '[[Unknown page#anchor]]' =>
878 '[[Unknown page#anchor]]' =>
880 link_to("Unknown page",
879 link_to("Unknown page",
881 "#Unknown_page_anchor",
880 "#Unknown_page_anchor",
882 :class => "wiki-page new"),
881 :class => "wiki-page new"),
883 '[[Unknown page#anchor|404]]' =>
882 '[[Unknown page#anchor|404]]' =>
884 link_to("404",
883 link_to("404",
885 "#Unknown_page_anchor",
884 "#Unknown_page_anchor",
886 :class => "wiki-page new"),
885 :class => "wiki-page new"),
887 }
886 }
888 @project = Project.find(1)
887 @project = Project.find(1)
889 to_test.each do |text, result|
888 to_test.each do |text, result|
890 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
889 assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor)
891 end
890 end
892 end
891 end
893
892
894 def test_html_tags
893 def test_html_tags
895 to_test = {
894 to_test = {
896 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
895 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
897 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
896 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
898 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
897 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
899 # do not escape pre/code tags
898 # do not escape pre/code tags
900 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
899 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
901 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
900 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
902 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
901 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
903 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
902 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
904 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
903 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
905 # remove attributes except class
904 # remove attributes except class
906 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
905 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
907 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
906 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
908 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
907 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
909 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
908 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
910 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
909 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
911 # xss
910 # xss
912 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
911 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
913 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
912 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
914 }
913 }
915 to_test.each { |text, result| assert_equal result, textilizable(text) }
914 to_test.each { |text, result| assert_equal result, textilizable(text) }
916 end
915 end
917
916
918 def test_allowed_html_tags
917 def test_allowed_html_tags
919 to_test = {
918 to_test = {
920 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
919 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
921 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
920 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
922 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
921 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
923 }
922 }
924 to_test.each { |text, result| assert_equal result, textilizable(text) }
923 to_test.each { |text, result| assert_equal result, textilizable(text) }
925 end
924 end
926
925
927 def test_pre_tags
926 def test_pre_tags
928 raw = <<-RAW
927 raw = <<-RAW
929 Before
928 Before
930
929
931 <pre>
930 <pre>
932 <prepared-statement-cache-size>32</prepared-statement-cache-size>
931 <prepared-statement-cache-size>32</prepared-statement-cache-size>
933 </pre>
932 </pre>
934
933
935 After
934 After
936 RAW
935 RAW
937
936
938 expected = <<-EXPECTED
937 expected = <<-EXPECTED
939 <p>Before</p>
938 <p>Before</p>
940 <pre>
939 <pre>
941 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
940 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
942 </pre>
941 </pre>
943 <p>After</p>
942 <p>After</p>
944 EXPECTED
943 EXPECTED
945
944
946 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
945 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
947 end
946 end
948
947
949 def test_pre_content_should_not_parse_wiki_and_redmine_links
948 def test_pre_content_should_not_parse_wiki_and_redmine_links
950 raw = <<-RAW
949 raw = <<-RAW
951 [[CookBook documentation]]
950 [[CookBook documentation]]
952
951
953 #1
952 #1
954
953
955 <pre>
954 <pre>
956 [[CookBook documentation]]
955 [[CookBook documentation]]
957
956
958 #1
957 #1
959 </pre>
958 </pre>
960 RAW
959 RAW
961
960
962 result1 = link_to("CookBook documentation",
961 result1 = link_to("CookBook documentation",
963 "/projects/ecookbook/wiki/CookBook_documentation",
962 "/projects/ecookbook/wiki/CookBook_documentation",
964 :class => "wiki-page")
963 :class => "wiki-page")
965 result2 = link_to('#1',
964 result2 = link_to('#1',
966 "/issues/1",
965 "/issues/1",
967 :class => Issue.find(1).css_classes,
966 :class => Issue.find(1).css_classes,
968 :title => "Bug: Cannot print recipes (New)")
967 :title => "Bug: Cannot print recipes (New)")
969
968
970 expected = <<-EXPECTED
969 expected = <<-EXPECTED
971 <p>#{result1}</p>
970 <p>#{result1}</p>
972 <p>#{result2}</p>
971 <p>#{result2}</p>
973 <pre>
972 <pre>
974 [[CookBook documentation]]
973 [[CookBook documentation]]
975
974
976 #1
975 #1
977 </pre>
976 </pre>
978 EXPECTED
977 EXPECTED
979
978
980 @project = Project.find(1)
979 @project = Project.find(1)
981 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
980 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
982 end
981 end
983
982
984 def test_non_closing_pre_blocks_should_be_closed
983 def test_non_closing_pre_blocks_should_be_closed
985 raw = <<-RAW
984 raw = <<-RAW
986 <pre><code>
985 <pre><code>
987 RAW
986 RAW
988
987
989 expected = <<-EXPECTED
988 expected = <<-EXPECTED
990 <pre><code>
989 <pre><code>
991 </code></pre>
990 </code></pre>
992 EXPECTED
991 EXPECTED
993
992
994 @project = Project.find(1)
993 @project = Project.find(1)
995 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
994 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
996 end
995 end
997
996
998 def test_unbalanced_closing_pre_tag_should_not_error
997 def test_unbalanced_closing_pre_tag_should_not_error
999 assert_nothing_raised do
998 assert_nothing_raised do
1000 textilizable("unbalanced</pre>")
999 textilizable("unbalanced</pre>")
1001 end
1000 end
1002 end
1001 end
1003
1002
1004 def test_syntax_highlight
1003 def test_syntax_highlight
1005 raw = <<-RAW
1004 raw = <<-RAW
1006 <pre><code class="ruby">
1005 <pre><code class="ruby">
1007 # Some ruby code here
1006 # Some ruby code here
1008 </code></pre>
1007 </code></pre>
1009 RAW
1008 RAW
1010
1009
1011 expected = <<-EXPECTED
1010 expected = <<-EXPECTED
1012 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
1011 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
1013 </code></pre>
1012 </code></pre>
1014 EXPECTED
1013 EXPECTED
1015
1014
1016 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1015 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1017 end
1016 end
1018
1017
1019 def test_to_path_param
1018 def test_to_path_param
1020 assert_equal 'test1/test2', to_path_param('test1/test2')
1019 assert_equal 'test1/test2', to_path_param('test1/test2')
1021 assert_equal 'test1/test2', to_path_param('/test1/test2/')
1020 assert_equal 'test1/test2', to_path_param('/test1/test2/')
1022 assert_equal 'test1/test2', to_path_param('//test1/test2/')
1021 assert_equal 'test1/test2', to_path_param('//test1/test2/')
1023 assert_nil to_path_param('/')
1022 assert_nil to_path_param('/')
1024 end
1023 end
1025
1024
1026 def test_wiki_links_in_tables
1025 def test_wiki_links_in_tables
1027 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
1026 text = "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|"
1028 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
1027 link1 = link_to("Link title", "/projects/ecookbook/wiki/Page", :class => "wiki-page new")
1029 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
1028 link2 = link_to("Other title", "/projects/ecookbook/wiki/Other_Page", :class => "wiki-page new")
1030 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1029 link3 = link_to("Last page", "/projects/ecookbook/wiki/Last_page", :class => "wiki-page new")
1031 result = "<tr><td>#{link1}</td>" +
1030 result = "<tr><td>#{link1}</td>" +
1032 "<td>#{link2}</td>" +
1031 "<td>#{link2}</td>" +
1033 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1032 "</tr><tr><td>Cell 21</td><td>#{link3}</td></tr>"
1034 @project = Project.find(1)
1033 @project = Project.find(1)
1035 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1034 assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '')
1036 end
1035 end
1037
1036
1038 def test_text_formatting
1037 def test_text_formatting
1039 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1038 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
1040 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1039 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
1041 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1040 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
1042 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
1041 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
1043 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
1042 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
1044 }
1043 }
1045 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1044 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
1046 end
1045 end
1047
1046
1048 def test_wiki_horizontal_rule
1047 def test_wiki_horizontal_rule
1049 assert_equal '<hr />', textilizable('---')
1048 assert_equal '<hr />', textilizable('---')
1050 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1049 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
1051 end
1050 end
1052
1051
1053 def test_footnotes
1052 def test_footnotes
1054 raw = <<-RAW
1053 raw = <<-RAW
1055 This is some text[1].
1054 This is some text[1].
1056
1055
1057 fn1. This is the foot note
1056 fn1. This is the foot note
1058 RAW
1057 RAW
1059
1058
1060 expected = <<-EXPECTED
1059 expected = <<-EXPECTED
1061 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1060 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
1062 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1061 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
1063 EXPECTED
1062 EXPECTED
1064
1063
1065 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1064 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
1066 end
1065 end
1067
1066
1068 def test_headings
1067 def test_headings
1069 raw = 'h1. Some heading'
1068 raw = 'h1. Some heading'
1070 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1069 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
1071
1070
1072 assert_equal expected, textilizable(raw)
1071 assert_equal expected, textilizable(raw)
1073 end
1072 end
1074
1073
1075 def test_headings_with_special_chars
1074 def test_headings_with_special_chars
1076 # This test makes sure that the generated anchor names match the expected
1075 # This test makes sure that the generated anchor names match the expected
1077 # ones even if the heading text contains unconventional characters
1076 # ones even if the heading text contains unconventional characters
1078 raw = 'h1. Some heading related to version 0.5'
1077 raw = 'h1. Some heading related to version 0.5'
1079 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1078 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
1080 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1079 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
1081
1080
1082 assert_equal expected, textilizable(raw)
1081 assert_equal expected, textilizable(raw)
1083 end
1082 end
1084
1083
1085 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1084 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
1086 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1085 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
1087 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1086 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
1088
1087
1089 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1088 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
1090
1089
1091 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1090 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
1092 end
1091 end
1093
1092
1094 def test_table_of_content
1093 def test_table_of_content
1095 set_language_if_valid 'en'
1094 set_language_if_valid 'en'
1096
1095
1097 raw = <<-RAW
1096 raw = <<-RAW
1098 {{toc}}
1097 {{toc}}
1099
1098
1100 h1. Title
1099 h1. Title
1101
1100
1102 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1101 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1103
1102
1104 h2. Subtitle with a [[Wiki]] link
1103 h2. Subtitle with a [[Wiki]] link
1105
1104
1106 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1105 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
1107
1106
1108 h2. Subtitle with [[Wiki|another Wiki]] link
1107 h2. Subtitle with [[Wiki|another Wiki]] link
1109
1108
1110 h2. Subtitle with %{color:red}red text%
1109 h2. Subtitle with %{color:red}red text%
1111
1110
1112 <pre>
1111 <pre>
1113 some code
1112 some code
1114 </pre>
1113 </pre>
1115
1114
1116 h3. Subtitle with *some* _modifiers_
1115 h3. Subtitle with *some* _modifiers_
1117
1116
1118 h3. Subtitle with @inline code@
1117 h3. Subtitle with @inline code@
1119
1118
1120 h1. Another title
1119 h1. Another title
1121
1120
1122 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1121 h3. An "Internet link":http://www.redmine.org/ inside subtitle
1123
1122
1124 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1123 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
1125
1124
1126 RAW
1125 RAW
1127
1126
1128 expected = '<ul class="toc">' +
1127 expected = '<ul class="toc">' +
1129 '<li><strong>Table of contents</strong></li>' +
1128 '<li><strong>Table of contents</strong></li>' +
1130 '<li><a href="#Title">Title</a>' +
1129 '<li><a href="#Title">Title</a>' +
1131 '<ul>' +
1130 '<ul>' +
1132 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1131 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
1133 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1132 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
1134 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1133 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
1135 '<ul>' +
1134 '<ul>' +
1136 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1135 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
1137 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1136 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
1138 '</ul>' +
1137 '</ul>' +
1139 '</li>' +
1138 '</li>' +
1140 '</ul>' +
1139 '</ul>' +
1141 '</li>' +
1140 '</li>' +
1142 '<li><a href="#Another-title">Another title</a>' +
1141 '<li><a href="#Another-title">Another title</a>' +
1143 '<ul>' +
1142 '<ul>' +
1144 '<li>' +
1143 '<li>' +
1145 '<ul>' +
1144 '<ul>' +
1146 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1145 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
1147 '</ul>' +
1146 '</ul>' +
1148 '</li>' +
1147 '</li>' +
1149 '<li><a href="#Project-Name">Project Name</a></li>' +
1148 '<li><a href="#Project-Name">Project Name</a></li>' +
1150 '</ul>' +
1149 '</ul>' +
1151 '</li>' +
1150 '</li>' +
1152 '</ul>'
1151 '</ul>'
1153
1152
1154 @project = Project.find(1)
1153 @project = Project.find(1)
1155 assert textilizable(raw).gsub("\n", "").include?(expected)
1154 assert textilizable(raw).gsub("\n", "").include?(expected)
1156 end
1155 end
1157
1156
1158 def test_table_of_content_should_generate_unique_anchors
1157 def test_table_of_content_should_generate_unique_anchors
1159 set_language_if_valid 'en'
1158 set_language_if_valid 'en'
1160
1159
1161 raw = <<-RAW
1160 raw = <<-RAW
1162 {{toc}}
1161 {{toc}}
1163
1162
1164 h1. Title
1163 h1. Title
1165
1164
1166 h2. Subtitle
1165 h2. Subtitle
1167
1166
1168 h2. Subtitle
1167 h2. Subtitle
1169 RAW
1168 RAW
1170
1169
1171 expected = '<ul class="toc">' +
1170 expected = '<ul class="toc">' +
1172 '<li><strong>Table of contents</strong></li>' +
1171 '<li><strong>Table of contents</strong></li>' +
1173 '<li><a href="#Title">Title</a>' +
1172 '<li><a href="#Title">Title</a>' +
1174 '<ul>' +
1173 '<ul>' +
1175 '<li><a href="#Subtitle">Subtitle</a></li>' +
1174 '<li><a href="#Subtitle">Subtitle</a></li>' +
1176 '<li><a href="#Subtitle-2">Subtitle</a></li>' +
1175 '<li><a href="#Subtitle-2">Subtitle</a></li>' +
1177 '</ul>' +
1176 '</ul>' +
1178 '</li>' +
1177 '</li>' +
1179 '</ul>'
1178 '</ul>'
1180
1179
1181 @project = Project.find(1)
1180 @project = Project.find(1)
1182 result = textilizable(raw).gsub("\n", "")
1181 result = textilizable(raw).gsub("\n", "")
1183 assert_include expected, result
1182 assert_include expected, result
1184 assert_include '<a name="Subtitle">', result
1183 assert_include '<a name="Subtitle">', result
1185 assert_include '<a name="Subtitle-2">', result
1184 assert_include '<a name="Subtitle-2">', result
1186 end
1185 end
1187
1186
1188 def test_table_of_content_should_contain_included_page_headings
1187 def test_table_of_content_should_contain_included_page_headings
1189 set_language_if_valid 'en'
1188 set_language_if_valid 'en'
1190
1189
1191 raw = <<-RAW
1190 raw = <<-RAW
1192 {{toc}}
1191 {{toc}}
1193
1192
1194 h1. Included
1193 h1. Included
1195
1194
1196 {{include(Child_1)}}
1195 {{include(Child_1)}}
1197 RAW
1196 RAW
1198
1197
1199 expected = '<ul class="toc">' +
1198 expected = '<ul class="toc">' +
1200 '<li><strong>Table of contents</strong></li>' +
1199 '<li><strong>Table of contents</strong></li>' +
1201 '<li><a href="#Included">Included</a></li>' +
1200 '<li><a href="#Included">Included</a></li>' +
1202 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1201 '<li><a href="#Child-page-1">Child page 1</a></li>' +
1203 '</ul>'
1202 '</ul>'
1204
1203
1205 @project = Project.find(1)
1204 @project = Project.find(1)
1206 assert textilizable(raw).gsub("\n", "").include?(expected)
1205 assert textilizable(raw).gsub("\n", "").include?(expected)
1207 end
1206 end
1208
1207
1209 def test_toc_with_textile_formatting_should_be_parsed
1208 def test_toc_with_textile_formatting_should_be_parsed
1210 with_settings :text_formatting => 'textile' do
1209 with_settings :text_formatting => 'textile' do
1211 assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
1210 assert_select_in textilizable("{{toc}}\n\nh1. Heading"), 'ul.toc li', :text => 'Heading'
1212 assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
1211 assert_select_in textilizable("{{<toc}}\n\nh1. Heading"), 'ul.toc.left li', :text => 'Heading'
1213 assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
1212 assert_select_in textilizable("{{>toc}}\n\nh1. Heading"), 'ul.toc.right li', :text => 'Heading'
1214 end
1213 end
1215 end
1214 end
1216
1215
1217 if Object.const_defined?(:Redcarpet)
1216 if Object.const_defined?(:Redcarpet)
1218 def test_toc_with_markdown_formatting_should_be_parsed
1217 def test_toc_with_markdown_formatting_should_be_parsed
1219 with_settings :text_formatting => 'markdown' do
1218 with_settings :text_formatting => 'markdown' do
1220 assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1219 assert_select_in textilizable("{{toc}}\n\n# Heading"), 'ul.toc li', :text => 'Heading'
1221 assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1220 assert_select_in textilizable("{{<toc}}\n\n# Heading"), 'ul.toc.left li', :text => 'Heading'
1222 assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1221 assert_select_in textilizable("{{>toc}}\n\n# Heading"), 'ul.toc.right li', :text => 'Heading'
1223 end
1222 end
1224 end
1223 end
1225 end
1224 end
1226
1225
1227 def test_section_edit_links
1226 def test_section_edit_links
1228 raw = <<-RAW
1227 raw = <<-RAW
1229 h1. Title
1228 h1. Title
1230
1229
1231 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1230 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
1232
1231
1233 h2. Subtitle with a [[Wiki]] link
1232 h2. Subtitle with a [[Wiki]] link
1234
1233
1235 h2. Subtitle with *some* _modifiers_
1234 h2. Subtitle with *some* _modifiers_
1236
1235
1237 h2. Subtitle with @inline code@
1236 h2. Subtitle with @inline code@
1238
1237
1239 <pre>
1238 <pre>
1240 some code
1239 some code
1241
1240
1242 h2. heading inside pre
1241 h2. heading inside pre
1243
1242
1244 <h2>html heading inside pre</h2>
1243 <h2>html heading inside pre</h2>
1245 </pre>
1244 </pre>
1246
1245
1247 h2. Subtitle after pre tag
1246 h2. Subtitle after pre tag
1248 RAW
1247 RAW
1249
1248
1250 @project = Project.find(1)
1249 @project = Project.find(1)
1251 set_language_if_valid 'en'
1250 set_language_if_valid 'en'
1252 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1251 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
1253
1252
1254 # heading that contains inline code
1253 # heading that contains inline code
1255 assert_match Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-4">' +
1254 assert_match Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-4">' +
1256 '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=4">Edit this section</a></div>' +
1255 '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=4">Edit this section</a></div>' +
1257 '<a name="Subtitle-with-inline-code"></a>' +
1256 '<a name="Subtitle-with-inline-code"></a>' +
1258 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1257 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
1259 result
1258 result
1260
1259
1261 # last heading
1260 # last heading
1262 assert_match Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-5">' +
1261 assert_match Regexp.new('<div class="contextual heading-2" title="Edit this section" id="section-5">' +
1263 '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=5">Edit this section</a></div>' +
1262 '<a class="icon-only icon-edit" href="/projects/1/wiki/Test/edit\?section=5">Edit this section</a></div>' +
1264 '<a name="Subtitle-after-pre-tag"></a>' +
1263 '<a name="Subtitle-after-pre-tag"></a>' +
1265 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1264 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
1266 result
1265 result
1267 end
1266 end
1268
1267
1269 def test_default_formatter
1268 def test_default_formatter
1270 with_settings :text_formatting => 'unknown' do
1269 with_settings :text_formatting => 'unknown' do
1271 text = 'a *link*: http://www.example.net/'
1270 text = 'a *link*: http://www.example.net/'
1272 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1271 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
1273 end
1272 end
1274 end
1273 end
1275
1274
1276 def test_parse_redmine_links_should_handle_a_tag_without_attributes
1275 def test_parse_redmine_links_should_handle_a_tag_without_attributes
1277 text = '<a>http://example.com</a>'
1276 text = '<a>http://example.com</a>'
1278 expected = text.dup
1277 expected = text.dup
1279 parse_redmine_links(text, nil, nil, nil, true, {})
1278 parse_redmine_links(text, nil, nil, nil, true, {})
1280 assert_equal expected, text
1279 assert_equal expected, text
1281 end
1280 end
1282
1281
1283 def test_due_date_distance_in_words
1282 def test_due_date_distance_in_words
1284 to_test = { Date.today => 'Due in 0 days',
1283 to_test = { Date.today => 'Due in 0 days',
1285 Date.today + 1 => 'Due in 1 day',
1284 Date.today + 1 => 'Due in 1 day',
1286 Date.today + 100 => 'Due in about 3 months',
1285 Date.today + 100 => 'Due in about 3 months',
1287 Date.today + 20000 => 'Due in over 54 years',
1286 Date.today + 20000 => 'Due in over 54 years',
1288 Date.today - 1 => '1 day late',
1287 Date.today - 1 => '1 day late',
1289 Date.today - 100 => 'about 3 months late',
1288 Date.today - 100 => 'about 3 months late',
1290 Date.today - 20000 => 'over 54 years late',
1289 Date.today - 20000 => 'over 54 years late',
1291 }
1290 }
1292 ::I18n.locale = :en
1291 ::I18n.locale = :en
1293 to_test.each do |date, expected|
1292 to_test.each do |date, expected|
1294 assert_equal expected, due_date_distance_in_words(date)
1293 assert_equal expected, due_date_distance_in_words(date)
1295 end
1294 end
1296 end
1295 end
1297
1296
1298 def test_avatar_enabled
1297 def test_avatar_enabled
1299 with_settings :gravatar_enabled => '1' do
1298 with_settings :gravatar_enabled => '1' do
1300 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1299 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1301 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1300 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1302 # Default size is 50
1301 # Default size is 50
1303 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1302 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1304 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1303 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1305 # Non-avatar options should be considered html options
1304 # Non-avatar options should be considered html options
1306 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1305 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1307 # The default class of the img tag should be gravatar
1306 # The default class of the img tag should be gravatar
1308 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1307 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1309 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1308 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1310 assert_nil avatar('jsmith')
1309 assert_nil avatar('jsmith')
1311 assert_nil avatar(nil)
1310 assert_nil avatar(nil)
1312 end
1311 end
1313 end
1312 end
1314
1313
1315 def test_avatar_disabled
1314 def test_avatar_disabled
1316 with_settings :gravatar_enabled => '0' do
1315 with_settings :gravatar_enabled => '0' do
1317 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1316 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1318 end
1317 end
1319 end
1318 end
1320
1319
1321 def test_link_to_user
1320 def test_link_to_user
1322 user = User.find(2)
1321 user = User.find(2)
1323 result = link_to("John Smith", "/users/2", :class => "user active")
1322 result = link_to("John Smith", "/users/2", :class => "user active")
1324 assert_equal result, link_to_user(user)
1323 assert_equal result, link_to_user(user)
1325 end
1324 end
1326
1325
1327 def test_link_to_user_should_not_link_to_locked_user
1326 def test_link_to_user_should_not_link_to_locked_user
1328 with_current_user nil do
1327 with_current_user nil do
1329 user = User.find(5)
1328 user = User.find(5)
1330 assert user.locked?
1329 assert user.locked?
1331 assert_equal 'Dave2 Lopper2', link_to_user(user)
1330 assert_equal 'Dave2 Lopper2', link_to_user(user)
1332 end
1331 end
1333 end
1332 end
1334
1333
1335 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1334 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1336 with_current_user User.find(1) do
1335 with_current_user User.find(1) do
1337 user = User.find(5)
1336 user = User.find(5)
1338 assert user.locked?
1337 assert user.locked?
1339 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1338 result = link_to("Dave2 Lopper2", "/users/5", :class => "user locked")
1340 assert_equal result, link_to_user(user)
1339 assert_equal result, link_to_user(user)
1341 end
1340 end
1342 end
1341 end
1343
1342
1344 def test_link_to_user_should_not_link_to_anonymous
1343 def test_link_to_user_should_not_link_to_anonymous
1345 user = User.anonymous
1344 user = User.anonymous
1346 assert user.anonymous?
1345 assert user.anonymous?
1347 t = link_to_user(user)
1346 t = link_to_user(user)
1348 assert_equal ::I18n.t(:label_user_anonymous), t
1347 assert_equal ::I18n.t(:label_user_anonymous), t
1349 end
1348 end
1350
1349
1351 def test_link_to_attachment
1350 def test_link_to_attachment
1352 a = Attachment.find(3)
1351 a = Attachment.find(3)
1353 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1352 assert_equal '<a href="/attachments/3/logo.gif">logo.gif</a>',
1354 link_to_attachment(a)
1353 link_to_attachment(a)
1355 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1354 assert_equal '<a href="/attachments/3/logo.gif">Text</a>',
1356 link_to_attachment(a, :text => 'Text')
1355 link_to_attachment(a, :text => 'Text')
1357 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1356 result = link_to("logo.gif", "/attachments/3/logo.gif", :class => "foo")
1358 assert_equal result,
1357 assert_equal result,
1359 link_to_attachment(a, :class => 'foo')
1358 link_to_attachment(a, :class => 'foo')
1360 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1359 assert_equal '<a href="/attachments/download/3/logo.gif">logo.gif</a>',
1361 link_to_attachment(a, :download => true)
1360 link_to_attachment(a, :download => true)
1362 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1361 assert_equal '<a href="http://test.host/attachments/3/logo.gif">logo.gif</a>',
1363 link_to_attachment(a, :only_path => false)
1362 link_to_attachment(a, :only_path => false)
1364 end
1363 end
1365
1364
1366 def test_thumbnail_tag
1365 def test_thumbnail_tag
1367 a = Attachment.find(3)
1366 a = Attachment.find(3)
1368 assert_select_in thumbnail_tag(a),
1367 assert_select_in thumbnail_tag(a),
1369 'a[href=?][title=?] img[alt="3"][src=?]',
1368 'a[href=?][title=?] img[alt="3"][src=?]',
1370 "/attachments/3/logo.gif", "logo.gif", "/attachments/thumbnail/3"
1369 "/attachments/3/logo.gif", "logo.gif", "/attachments/thumbnail/3"
1371 end
1370 end
1372
1371
1373 def test_link_to_project
1372 def test_link_to_project
1374 project = Project.find(1)
1373 project = Project.find(1)
1375 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1374 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1376 link_to_project(project)
1375 link_to_project(project)
1377 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1376 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1378 link_to_project(project, {:only_path => false, :jump => 'blah'})
1377 link_to_project(project, {:only_path => false, :jump => 'blah'})
1379 end
1378 end
1380
1379
1381 def test_link_to_project_settings
1380 def test_link_to_project_settings
1382 project = Project.find(1)
1381 project = Project.find(1)
1383 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1382 assert_equal '<a href="/projects/ecookbook/settings">eCookbook</a>', link_to_project_settings(project)
1384
1383
1385 project.status = Project::STATUS_CLOSED
1384 project.status = Project::STATUS_CLOSED
1386 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1385 assert_equal '<a href="/projects/ecookbook">eCookbook</a>', link_to_project_settings(project)
1387
1386
1388 project.status = Project::STATUS_ARCHIVED
1387 project.status = Project::STATUS_ARCHIVED
1389 assert_equal 'eCookbook', link_to_project_settings(project)
1388 assert_equal 'eCookbook', link_to_project_settings(project)
1390 end
1389 end
1391
1390
1392 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1391 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1393 # numeric identifier are no longer allowed
1392 # numeric identifier are no longer allowed
1394 Project.where(:id => 1).update_all(:identifier => 25)
1393 Project.where(:id => 1).update_all(:identifier => 25)
1395 assert_equal '<a href="/projects/1">eCookbook</a>',
1394 assert_equal '<a href="/projects/1">eCookbook</a>',
1396 link_to_project(Project.find(1))
1395 link_to_project(Project.find(1))
1397 end
1396 end
1398
1397
1399 def test_principals_options_for_select_with_users
1398 def test_principals_options_for_select_with_users
1400 User.current = nil
1399 User.current = nil
1401 users = [User.find(2), User.find(4)]
1400 users = [User.find(2), User.find(4)]
1402 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1401 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1403 principals_options_for_select(users)
1402 principals_options_for_select(users)
1404 end
1403 end
1405
1404
1406 def test_principals_options_for_select_with_selected
1405 def test_principals_options_for_select_with_selected
1407 User.current = nil
1406 User.current = nil
1408 users = [User.find(2), User.find(4)]
1407 users = [User.find(2), User.find(4)]
1409 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1408 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1410 principals_options_for_select(users, User.find(4))
1409 principals_options_for_select(users, User.find(4))
1411 end
1410 end
1412
1411
1413 def test_principals_options_for_select_with_users_and_groups
1412 def test_principals_options_for_select_with_users_and_groups
1414 User.current = nil
1413 User.current = nil
1415 set_language_if_valid 'en'
1414 set_language_if_valid 'en'
1416 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1415 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1417 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1416 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1418 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1417 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1419 principals_options_for_select(users)
1418 principals_options_for_select(users)
1420 end
1419 end
1421
1420
1422 def test_principals_options_for_select_with_empty_collection
1421 def test_principals_options_for_select_with_empty_collection
1423 assert_equal '', principals_options_for_select([])
1422 assert_equal '', principals_options_for_select([])
1424 end
1423 end
1425
1424
1426 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1425 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1427 set_language_if_valid 'en'
1426 set_language_if_valid 'en'
1428 users = [User.find(2), User.find(4)]
1427 users = [User.find(2), User.find(4)]
1429 User.current = User.find(4)
1428 User.current = User.find(4)
1430 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1429 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1431 end
1430 end
1432
1431
1433 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1432 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1434 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1433 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1435 end
1434 end
1436
1435
1437 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1436 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1438 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1437 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1439 end
1438 end
1440
1439
1441 def test_image_tag_should_pick_the_default_image
1440 def test_image_tag_should_pick_the_default_image
1442 assert_match 'src="/images/image.png"', image_tag("image.png")
1441 assert_match 'src="/images/image.png"', image_tag("image.png")
1443 end
1442 end
1444
1443
1445 def test_image_tag_should_pick_the_theme_image_if_it_exists
1444 def test_image_tag_should_pick_the_theme_image_if_it_exists
1446 theme = Redmine::Themes.themes.last
1445 theme = Redmine::Themes.themes.last
1447 theme.images << 'image.png'
1446 theme.images << 'image.png'
1448
1447
1449 with_settings :ui_theme => theme.id do
1448 with_settings :ui_theme => theme.id do
1450 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1449 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1451 assert_match %|src="/images/other.png"|, image_tag("other.png")
1450 assert_match %|src="/images/other.png"|, image_tag("other.png")
1452 end
1451 end
1453 ensure
1452 ensure
1454 theme.images.delete 'image.png'
1453 theme.images.delete 'image.png'
1455 end
1454 end
1456
1455
1457 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1456 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1458 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1457 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1459 end
1458 end
1460
1459
1461 def test_javascript_include_tag_should_pick_the_default_javascript
1460 def test_javascript_include_tag_should_pick_the_default_javascript
1462 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1461 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1463 end
1462 end
1464
1463
1465 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1464 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1466 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1465 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1467 end
1466 end
1468
1467
1469 def test_raw_json_should_escape_closing_tags
1468 def test_raw_json_should_escape_closing_tags
1470 s = raw_json(["<foo>bar</foo>"])
1469 s = raw_json(["<foo>bar</foo>"])
1471 assert_include '\/foo', s
1470 assert_include '\/foo', s
1472 end
1471 end
1473
1472
1474 def test_raw_json_should_be_html_safe
1473 def test_raw_json_should_be_html_safe
1475 s = raw_json(["foo"])
1474 s = raw_json(["foo"])
1476 assert s.html_safe?
1475 assert s.html_safe?
1477 end
1476 end
1478
1477
1479 def test_html_title_should_app_title_if_not_set
1478 def test_html_title_should_app_title_if_not_set
1480 assert_equal 'Redmine', html_title
1479 assert_equal 'Redmine', html_title
1481 end
1480 end
1482
1481
1483 def test_html_title_should_join_items
1482 def test_html_title_should_join_items
1484 html_title 'Foo', 'Bar'
1483 html_title 'Foo', 'Bar'
1485 assert_equal 'Foo - Bar - Redmine', html_title
1484 assert_equal 'Foo - Bar - Redmine', html_title
1486 end
1485 end
1487
1486
1488 def test_html_title_should_append_current_project_name
1487 def test_html_title_should_append_current_project_name
1489 @project = Project.find(1)
1488 @project = Project.find(1)
1490 html_title 'Foo', 'Bar'
1489 html_title 'Foo', 'Bar'
1491 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1490 assert_equal 'Foo - Bar - eCookbook - Redmine', html_title
1492 end
1491 end
1493
1492
1494 def test_title_should_return_a_h2_tag
1493 def test_title_should_return_a_h2_tag
1495 assert_equal '<h2>Foo</h2>', title('Foo')
1494 assert_equal '<h2>Foo</h2>', title('Foo')
1496 end
1495 end
1497
1496
1498 def test_title_should_set_html_title
1497 def test_title_should_set_html_title
1499 title('Foo')
1498 title('Foo')
1500 assert_equal 'Foo - Redmine', html_title
1499 assert_equal 'Foo - Redmine', html_title
1501 end
1500 end
1502
1501
1503 def test_title_should_turn_arrays_into_links
1502 def test_title_should_turn_arrays_into_links
1504 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1503 assert_equal '<h2><a href="/foo">Foo</a></h2>', title(['Foo', '/foo'])
1505 assert_equal 'Foo - Redmine', html_title
1504 assert_equal 'Foo - Redmine', html_title
1506 end
1505 end
1507
1506
1508 def test_title_should_join_items
1507 def test_title_should_join_items
1509 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1508 assert_equal '<h2>Foo &#187; Bar</h2>', title('Foo', 'Bar')
1510 assert_equal 'Bar - Foo - Redmine', html_title
1509 assert_equal 'Bar - Foo - Redmine', html_title
1511 end
1510 end
1512
1511
1513 def test_favicon_path
1512 def test_favicon_path
1514 assert_match %r{^/favicon\.ico}, favicon_path
1513 assert_match %r{^/favicon\.ico}, favicon_path
1515 end
1514 end
1516
1515
1517 def test_favicon_path_with_suburi
1516 def test_favicon_path_with_suburi
1518 Redmine::Utils.relative_url_root = '/foo'
1517 Redmine::Utils.relative_url_root = '/foo'
1519 assert_match %r{^/foo/favicon\.ico}, favicon_path
1518 assert_match %r{^/foo/favicon\.ico}, favicon_path
1520 ensure
1519 ensure
1521 Redmine::Utils.relative_url_root = ''
1520 Redmine::Utils.relative_url_root = ''
1522 end
1521 end
1523
1522
1524 def test_favicon_url
1523 def test_favicon_url
1525 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1524 assert_match %r{^http://test\.host/favicon\.ico}, favicon_url
1526 end
1525 end
1527
1526
1528 def test_favicon_url_with_suburi
1527 def test_favicon_url_with_suburi
1529 Redmine::Utils.relative_url_root = '/foo'
1528 Redmine::Utils.relative_url_root = '/foo'
1530 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1529 assert_match %r{^http://test\.host/foo/favicon\.ico}, favicon_url
1531 ensure
1530 ensure
1532 Redmine::Utils.relative_url_root = ''
1531 Redmine::Utils.relative_url_root = ''
1533 end
1532 end
1534
1533
1535 def test_truncate_single_line
1534 def test_truncate_single_line
1536 str = "01234"
1535 str = "01234"
1537 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1536 result = truncate_single_line_raw("#{str}\n#{str}", 10)
1538 assert_equal "01234 0...", result
1537 assert_equal "01234 0...", result
1539 assert !result.html_safe?
1538 assert !result.html_safe?
1540 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1539 result = truncate_single_line_raw("#{str}<&#>\n#{str}#{str}", 16)
1541 assert_equal "01234<&#> 012...", result
1540 assert_equal "01234<&#> 012...", result
1542 assert !result.html_safe?
1541 assert !result.html_safe?
1543 end
1542 end
1544
1543
1545 def test_truncate_single_line_non_ascii
1544 def test_truncate_single_line_non_ascii
1546 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e".force_encoding('UTF-8')
1545 ja = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e".force_encoding('UTF-8')
1547 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1546 result = truncate_single_line_raw("#{ja}\n#{ja}\n#{ja}", 10)
1548 assert_equal "#{ja} #{ja}...", result
1547 assert_equal "#{ja} #{ja}...", result
1549 assert !result.html_safe?
1548 assert !result.html_safe?
1550 end
1549 end
1551
1550
1552 def test_back_url_should_remove_utf8_checkmark_from_referer
1551 def test_back_url_should_remove_utf8_checkmark_from_referer
1553 stubs(:request).returns(stub(:env => {'HTTP_REFERER' => "/path?utf8=\u2713&foo=bar"}))
1552 stubs(:request).returns(stub(:env => {'HTTP_REFERER' => "/path?utf8=\u2713&foo=bar"}))
1554 assert_equal "/path?foo=bar", back_url
1553 assert_equal "/path?foo=bar", back_url
1555 end
1554 end
1556
1555
1557 def test_hours_formatting
1556 def test_hours_formatting
1558 set_language_if_valid 'en'
1557 set_language_if_valid 'en'
1559
1558
1560 with_settings :timespan_format => 'minutes' do
1559 with_settings :timespan_format => 'minutes' do
1561 assert_equal '0:45', format_hours(0.75)
1560 assert_equal '0:45', format_hours(0.75)
1562 assert_equal '0:45 h', l_hours_short(0.75)
1561 assert_equal '0:45 h', l_hours_short(0.75)
1563 assert_equal '0:45 hour', l_hours(0.75)
1562 assert_equal '0:45 hour', l_hours(0.75)
1564 end
1563 end
1565 with_settings :timespan_format => 'decimal' do
1564 with_settings :timespan_format => 'decimal' do
1566 assert_equal '0.75', format_hours(0.75)
1565 assert_equal '0.75', format_hours(0.75)
1567 assert_equal '0.75 h', l_hours_short(0.75)
1566 assert_equal '0.75 h', l_hours_short(0.75)
1568 assert_equal '0.75 hour', l_hours(0.75)
1567 assert_equal '0.75 hour', l_hours(0.75)
1569 end
1568 end
1570 end
1569 end
1571
1570
1572 def test_html_hours
1571 def test_html_hours
1573 assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">:45</span>', html_hours('0:45')
1572 assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">:45</span>', html_hours('0:45')
1574 assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">.75</span>', html_hours('0.75')
1573 assert_equal '<span class="hours hours-int">0</span><span class="hours hours-dec">.75</span>', html_hours('0.75')
1575 end
1574 end
1576 end
1575 end
@@ -1,60 +1,59
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class CustomFieldsHelperTest < Redmine::HelperTest
20 class CustomFieldsHelperTest < Redmine::HelperTest
21 include ApplicationHelper
21 include ApplicationHelper
22 include CustomFieldsHelper
22 include CustomFieldsHelper
23 include Redmine::I18n
24 include ERB::Util
23 include ERB::Util
25
24
26 def test_format_boolean_value
25 def test_format_boolean_value
27 I18n.locale = 'en'
26 I18n.locale = 'en'
28 assert_equal 'Yes', format_value('1', CustomField.new(:field_format => 'bool'))
27 assert_equal 'Yes', format_value('1', CustomField.new(:field_format => 'bool'))
29 assert_equal 'No', format_value('0', CustomField.new(:field_format => 'bool'))
28 assert_equal 'No', format_value('0', CustomField.new(:field_format => 'bool'))
30 end
29 end
31
30
32 def test_label_tag_should_include_description_as_span_title_if_present
31 def test_label_tag_should_include_description_as_span_title_if_present
33 field = CustomField.new(:field_format => 'string', :description => 'This is the description')
32 field = CustomField.new(:field_format => 'string', :description => 'This is the description')
34 tag = custom_field_label_tag('foo', CustomValue.new(:custom_field => field))
33 tag = custom_field_label_tag('foo', CustomValue.new(:custom_field => field))
35 assert_select_in tag, 'label span[title=?]', 'This is the description'
34 assert_select_in tag, 'label span[title=?]', 'This is the description'
36 end
35 end
37
36
38 def test_label_tag_should_not_include_title_if_description_is_blank
37 def test_label_tag_should_not_include_title_if_description_is_blank
39 field = CustomField.new(:field_format => 'string')
38 field = CustomField.new(:field_format => 'string')
40 tag = custom_field_label_tag('foo', CustomValue.new(:custom_field => field))
39 tag = custom_field_label_tag('foo', CustomValue.new(:custom_field => field))
41 assert_select_in tag, 'label span[title]', 0
40 assert_select_in tag, 'label span[title]', 0
42 end
41 end
43
42
44 def test_unknow_field_format_should_be_edited_as_string
43 def test_unknow_field_format_should_be_edited_as_string
45 field = CustomField.new(:field_format => 'foo')
44 field = CustomField.new(:field_format => 'foo')
46 value = CustomValue.new(:value => 'bar', :custom_field => field)
45 value = CustomValue.new(:value => 'bar', :custom_field => field)
47 field.id = 52
46 field.id = 52
48
47
49 assert_select_in custom_field_tag('object', value),
48 assert_select_in custom_field_tag('object', value),
50 'input[type=text][value=bar][name=?]', 'object[custom_field_values][52]'
49 'input[type=text][value=bar][name=?]', 'object[custom_field_values][52]'
51 end
50 end
52
51
53 def test_unknow_field_format_should_be_bulk_edited_as_string
52 def test_unknow_field_format_should_be_bulk_edited_as_string
54 field = CustomField.new(:field_format => 'foo')
53 field = CustomField.new(:field_format => 'foo')
55 field.id = 52
54 field.id = 52
56
55
57 assert_select_in custom_field_tag_for_bulk_edit('object', field),
56 assert_select_in custom_field_tag_for_bulk_edit('object', field),
58 'input[type=text][value=""][name=?]', 'object[custom_field_values][52]'
57 'input[type=text][value=""][name=?]', 'object[custom_field_values][52]'
59 end
58 end
60 end
59 end
@@ -1,43 +1,42
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class GroupsHelperTest < Redmine::HelperTest
20 class GroupsHelperTest < Redmine::HelperTest
21 include Redmine::I18n
22 include ERB::Util
21 include ERB::Util
23 include GroupsHelper
22 include GroupsHelper
24 include Rails.application.routes.url_helpers
23 include Rails.application.routes.url_helpers
25
24
26 fixtures :users
25 fixtures :users
27
26
28 def test_render_principals_for_new_group_users
27 def test_render_principals_for_new_group_users
29 group = Group.generate!
28 group = Group.generate!
30
29
31 result = render_principals_for_new_group_users(group)
30 result = render_principals_for_new_group_users(group)
32 assert_select_in result, 'input[name=?][value="2"]', 'user_ids[]'
31 assert_select_in result, 'input[name=?][value="2"]', 'user_ids[]'
33 end
32 end
34
33
35 def test_render_principals_for_new_group_users_with_limited_results_should_paginate
34 def test_render_principals_for_new_group_users_with_limited_results_should_paginate
36 group = Group.generate!
35 group = Group.generate!
37
36
38 result = render_principals_for_new_group_users(group, 3)
37 result = render_principals_for_new_group_users(group, 3)
39 assert_select_in result, 'span.pagination'
38 assert_select_in result, 'span.pagination'
40 assert_select_in result, 'span.pagination li.current span', :text => '1'
39 assert_select_in result, 'span.pagination li.current span', :text => '1'
41 assert_select_in result, 'a[href=?]', "/groups/#{group.id}/autocomplete_for_user.js?page=2", :text => '2'
40 assert_select_in result, 'a[href=?]', "/groups/#{group.id}/autocomplete_for_user.js?page=2", :text => '2'
42 end
41 end
43 end
42 end
@@ -1,338 +1,332
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class IssuesHelperTest < Redmine::HelperTest
20 class IssuesHelperTest < Redmine::HelperTest
21 include Redmine::I18n
22 include IssuesHelper
21 include IssuesHelper
23 include CustomFieldsHelper
22 include CustomFieldsHelper
24 include ERB::Util
23 include ERB::Util
25 include Rails.application.routes.url_helpers
24 include Rails.application.routes.url_helpers
26
25
27 fixtures :projects, :trackers, :issue_statuses, :issues,
26 fixtures :projects, :trackers, :issue_statuses, :issues,
28 :enumerations, :users, :issue_categories,
27 :enumerations, :users, :issue_categories,
29 :projects_trackers,
28 :projects_trackers,
30 :roles,
29 :roles,
31 :member_roles,
30 :member_roles,
32 :members,
31 :members,
33 :enabled_modules,
32 :enabled_modules,
34 :custom_fields,
33 :custom_fields,
35 :attachments,
34 :attachments,
36 :versions
35 :versions
37
36
38 def setup
39 super
40 set_language_if_valid('en')
41 end
42
43 def test_issue_heading
37 def test_issue_heading
44 assert_equal "Bug #1", issue_heading(Issue.find(1))
38 assert_equal "Bug #1", issue_heading(Issue.find(1))
45 end
39 end
46
40
47 def test_issues_destroy_confirmation_message_with_one_root_issue
41 def test_issues_destroy_confirmation_message_with_one_root_issue
48 assert_equal l(:text_issues_destroy_confirmation),
42 assert_equal l(:text_issues_destroy_confirmation),
49 issues_destroy_confirmation_message(Issue.find(1))
43 issues_destroy_confirmation_message(Issue.find(1))
50 end
44 end
51
45
52 def test_issues_destroy_confirmation_message_with_an_arrayt_of_root_issues
46 def test_issues_destroy_confirmation_message_with_an_arrayt_of_root_issues
53 assert_equal l(:text_issues_destroy_confirmation),
47 assert_equal l(:text_issues_destroy_confirmation),
54 issues_destroy_confirmation_message(Issue.find([1, 2]))
48 issues_destroy_confirmation_message(Issue.find([1, 2]))
55 end
49 end
56
50
57 def test_issues_destroy_confirmation_message_with_one_parent_issue
51 def test_issues_destroy_confirmation_message_with_one_parent_issue
58 Issue.find(2).update! :parent_issue_id => 1
52 Issue.find(2).update! :parent_issue_id => 1
59 assert_equal l(:text_issues_destroy_confirmation) + "\n" +
53 assert_equal l(:text_issues_destroy_confirmation) + "\n" +
60 l(:text_issues_destroy_descendants_confirmation, :count => 1),
54 l(:text_issues_destroy_descendants_confirmation, :count => 1),
61 issues_destroy_confirmation_message(Issue.find(1))
55 issues_destroy_confirmation_message(Issue.find(1))
62 end
56 end
63
57
64 def test_issues_destroy_confirmation_message_with_one_parent_issue_and_its_child
58 def test_issues_destroy_confirmation_message_with_one_parent_issue_and_its_child
65 Issue.find(2).update! :parent_issue_id => 1
59 Issue.find(2).update! :parent_issue_id => 1
66 assert_equal l(:text_issues_destroy_confirmation),
60 assert_equal l(:text_issues_destroy_confirmation),
67 issues_destroy_confirmation_message(Issue.find([1, 2]))
61 issues_destroy_confirmation_message(Issue.find([1, 2]))
68 end
62 end
69
63
70 def test_issues_destroy_confirmation_message_with_issues_that_share_descendants
64 def test_issues_destroy_confirmation_message_with_issues_that_share_descendants
71 root = Issue.generate!
65 root = Issue.generate!
72 child = Issue.generate!(:parent_issue_id => root.id)
66 child = Issue.generate!(:parent_issue_id => root.id)
73 Issue.generate!(:parent_issue_id => child.id)
67 Issue.generate!(:parent_issue_id => child.id)
74
68
75 assert_equal l(:text_issues_destroy_confirmation) + "\n" +
69 assert_equal l(:text_issues_destroy_confirmation) + "\n" +
76 l(:text_issues_destroy_descendants_confirmation, :count => 1),
70 l(:text_issues_destroy_descendants_confirmation, :count => 1),
77 issues_destroy_confirmation_message([root.reload, child.reload])
71 issues_destroy_confirmation_message([root.reload, child.reload])
78 end
72 end
79
73
80 test 'show_detail with no_html should show a changing attribute' do
74 test 'show_detail with no_html should show a changing attribute' do
81 detail = JournalDetail.new(:property => 'attr', :old_value => '40',
75 detail = JournalDetail.new(:property => 'attr', :old_value => '40',
82 :value => '100', :prop_key => 'done_ratio')
76 :value => '100', :prop_key => 'done_ratio')
83 assert_equal "% Done changed from 40 to 100", show_detail(detail, true)
77 assert_equal "% Done changed from 40 to 100", show_detail(detail, true)
84 end
78 end
85
79
86 test 'show_detail with no_html should show a new attribute' do
80 test 'show_detail with no_html should show a new attribute' do
87 detail = JournalDetail.new(:property => 'attr', :old_value => nil,
81 detail = JournalDetail.new(:property => 'attr', :old_value => nil,
88 :value => '100', :prop_key => 'done_ratio')
82 :value => '100', :prop_key => 'done_ratio')
89 assert_equal "% Done set to 100", show_detail(detail, true)
83 assert_equal "% Done set to 100", show_detail(detail, true)
90 end
84 end
91
85
92 test 'show_detail with no_html should show a deleted attribute' do
86 test 'show_detail with no_html should show a deleted attribute' do
93 detail = JournalDetail.new(:property => 'attr', :old_value => '50',
87 detail = JournalDetail.new(:property => 'attr', :old_value => '50',
94 :value => nil, :prop_key => 'done_ratio')
88 :value => nil, :prop_key => 'done_ratio')
95 assert_equal "% Done deleted (50)", show_detail(detail, true)
89 assert_equal "% Done deleted (50)", show_detail(detail, true)
96 end
90 end
97
91
98 test 'show_detail with html should show a changing attribute with HTML highlights' do
92 test 'show_detail with html should show a changing attribute with HTML highlights' do
99 detail = JournalDetail.new(:property => 'attr', :old_value => '40',
93 detail = JournalDetail.new(:property => 'attr', :old_value => '40',
100 :value => '100', :prop_key => 'done_ratio')
94 :value => '100', :prop_key => 'done_ratio')
101 html = show_detail(detail, false)
95 html = show_detail(detail, false)
102 assert_include '<strong>% Done</strong>', html
96 assert_include '<strong>% Done</strong>', html
103 assert_include '<i>40</i>', html
97 assert_include '<i>40</i>', html
104 assert_include '<i>100</i>', html
98 assert_include '<i>100</i>', html
105 end
99 end
106
100
107 test 'show_detail with html should show a new attribute with HTML highlights' do
101 test 'show_detail with html should show a new attribute with HTML highlights' do
108 detail = JournalDetail.new(:property => 'attr', :old_value => nil,
102 detail = JournalDetail.new(:property => 'attr', :old_value => nil,
109 :value => '100', :prop_key => 'done_ratio')
103 :value => '100', :prop_key => 'done_ratio')
110 html = show_detail(detail, false)
104 html = show_detail(detail, false)
111 assert_include '<strong>% Done</strong>', html
105 assert_include '<strong>% Done</strong>', html
112 assert_include '<i>100</i>', html
106 assert_include '<i>100</i>', html
113 end
107 end
114
108
115 test 'show_detail with html should show a deleted attribute with HTML highlights' do
109 test 'show_detail with html should show a deleted attribute with HTML highlights' do
116 detail = JournalDetail.new(:property => 'attr', :old_value => '50',
110 detail = JournalDetail.new(:property => 'attr', :old_value => '50',
117 :value => nil, :prop_key => 'done_ratio')
111 :value => nil, :prop_key => 'done_ratio')
118 html = show_detail(detail, false)
112 html = show_detail(detail, false)
119 assert_include '<strong>% Done</strong>', html
113 assert_include '<strong>% Done</strong>', html
120 assert_include '<del><i>50</i></del>', html
114 assert_include '<del><i>50</i></del>', html
121 end
115 end
122
116
123 test 'show_detail with a start_date attribute should format the dates' do
117 test 'show_detail with a start_date attribute should format the dates' do
124 detail = JournalDetail.new(
118 detail = JournalDetail.new(
125 :property => 'attr',
119 :property => 'attr',
126 :old_value => '2010-01-01',
120 :old_value => '2010-01-01',
127 :value => '2010-01-31',
121 :value => '2010-01-31',
128 :prop_key => 'start_date'
122 :prop_key => 'start_date'
129 )
123 )
130 with_settings :date_format => '%m/%d/%Y' do
124 with_settings :date_format => '%m/%d/%Y' do
131 assert_match "01/31/2010", show_detail(detail, true)
125 assert_match "01/31/2010", show_detail(detail, true)
132 assert_match "01/01/2010", show_detail(detail, true)
126 assert_match "01/01/2010", show_detail(detail, true)
133 end
127 end
134 end
128 end
135
129
136 test 'show_detail with a due_date attribute should format the dates' do
130 test 'show_detail with a due_date attribute should format the dates' do
137 detail = JournalDetail.new(
131 detail = JournalDetail.new(
138 :property => 'attr',
132 :property => 'attr',
139 :old_value => '2010-01-01',
133 :old_value => '2010-01-01',
140 :value => '2010-01-31',
134 :value => '2010-01-31',
141 :prop_key => 'due_date'
135 :prop_key => 'due_date'
142 )
136 )
143 with_settings :date_format => '%m/%d/%Y' do
137 with_settings :date_format => '%m/%d/%Y' do
144 assert_match "01/31/2010", show_detail(detail, true)
138 assert_match "01/31/2010", show_detail(detail, true)
145 assert_match "01/01/2010", show_detail(detail, true)
139 assert_match "01/01/2010", show_detail(detail, true)
146 end
140 end
147 end
141 end
148
142
149 test 'show_detail should show old and new values with a project attribute' do
143 test 'show_detail should show old and new values with a project attribute' do
150 detail = JournalDetail.new(:property => 'attr', :prop_key => 'project_id',
144 detail = JournalDetail.new(:property => 'attr', :prop_key => 'project_id',
151 :old_value => 1, :value => 2)
145 :old_value => 1, :value => 2)
152 assert_match 'eCookbook', show_detail(detail, true)
146 assert_match 'eCookbook', show_detail(detail, true)
153 assert_match 'OnlineStore', show_detail(detail, true)
147 assert_match 'OnlineStore', show_detail(detail, true)
154 end
148 end
155
149
156 test 'show_detail should show old and new values with a issue status attribute' do
150 test 'show_detail should show old and new values with a issue status attribute' do
157 detail = JournalDetail.new(:property => 'attr', :prop_key => 'status_id',
151 detail = JournalDetail.new(:property => 'attr', :prop_key => 'status_id',
158 :old_value => 1, :value => 2)
152 :old_value => 1, :value => 2)
159 assert_match 'New', show_detail(detail, true)
153 assert_match 'New', show_detail(detail, true)
160 assert_match 'Assigned', show_detail(detail, true)
154 assert_match 'Assigned', show_detail(detail, true)
161 end
155 end
162
156
163 test 'show_detail should show old and new values with a tracker attribute' do
157 test 'show_detail should show old and new values with a tracker attribute' do
164 detail = JournalDetail.new(:property => 'attr', :prop_key => 'tracker_id',
158 detail = JournalDetail.new(:property => 'attr', :prop_key => 'tracker_id',
165 :old_value => 1, :value => 2)
159 :old_value => 1, :value => 2)
166 assert_match 'Bug', show_detail(detail, true)
160 assert_match 'Bug', show_detail(detail, true)
167 assert_match 'Feature request', show_detail(detail, true)
161 assert_match 'Feature request', show_detail(detail, true)
168 end
162 end
169
163
170 test 'show_detail should show old and new values with a assigned to attribute' do
164 test 'show_detail should show old and new values with a assigned to attribute' do
171 detail = JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id',
165 detail = JournalDetail.new(:property => 'attr', :prop_key => 'assigned_to_id',
172 :old_value => 1, :value => 2)
166 :old_value => 1, :value => 2)
173 assert_match 'Redmine Admin', show_detail(detail, true)
167 assert_match 'Redmine Admin', show_detail(detail, true)
174 assert_match 'John Smith', show_detail(detail, true)
168 assert_match 'John Smith', show_detail(detail, true)
175 end
169 end
176
170
177 test 'show_detail should show old and new values with a priority attribute' do
171 test 'show_detail should show old and new values with a priority attribute' do
178 detail = JournalDetail.new(:property => 'attr', :prop_key => 'priority_id',
172 detail = JournalDetail.new(:property => 'attr', :prop_key => 'priority_id',
179 :old_value => 4, :value => 5)
173 :old_value => 4, :value => 5)
180 assert_match 'Low', show_detail(detail, true)
174 assert_match 'Low', show_detail(detail, true)
181 assert_match 'Normal', show_detail(detail, true)
175 assert_match 'Normal', show_detail(detail, true)
182 end
176 end
183
177
184 test 'show_detail should show old and new values with a category attribute' do
178 test 'show_detail should show old and new values with a category attribute' do
185 detail = JournalDetail.new(:property => 'attr', :prop_key => 'category_id',
179 detail = JournalDetail.new(:property => 'attr', :prop_key => 'category_id',
186 :old_value => 1, :value => 2)
180 :old_value => 1, :value => 2)
187 assert_match 'Printing', show_detail(detail, true)
181 assert_match 'Printing', show_detail(detail, true)
188 assert_match 'Recipes', show_detail(detail, true)
182 assert_match 'Recipes', show_detail(detail, true)
189 end
183 end
190
184
191 test 'show_detail should show old and new values with a fixed version attribute' do
185 test 'show_detail should show old and new values with a fixed version attribute' do
192 detail = JournalDetail.new(:property => 'attr', :prop_key => 'fixed_version_id',
186 detail = JournalDetail.new(:property => 'attr', :prop_key => 'fixed_version_id',
193 :old_value => 1, :value => 2)
187 :old_value => 1, :value => 2)
194 assert_match '0.1', show_detail(detail, true)
188 assert_match '0.1', show_detail(detail, true)
195 assert_match '1.0', show_detail(detail, true)
189 assert_match '1.0', show_detail(detail, true)
196 end
190 end
197
191
198 test 'show_detail should show old and new values with a estimated hours attribute' do
192 test 'show_detail should show old and new values with a estimated hours attribute' do
199 detail = JournalDetail.new(:property => 'attr', :prop_key => 'estimated_hours',
193 detail = JournalDetail.new(:property => 'attr', :prop_key => 'estimated_hours',
200 :old_value => '5', :value => '6.3')
194 :old_value => '5', :value => '6.3')
201 assert_match '5.00', show_detail(detail, true)
195 assert_match '5.00', show_detail(detail, true)
202 assert_match '6.30', show_detail(detail, true)
196 assert_match '6.30', show_detail(detail, true)
203 end
197 end
204
198
205 test 'show_detail should not show values with a description attribute' do
199 test 'show_detail should not show values with a description attribute' do
206 detail = JournalDetail.new(:property => 'attr', :prop_key => 'description',
200 detail = JournalDetail.new(:property => 'attr', :prop_key => 'description',
207 :old_value => 'Foo', :value => 'Bar')
201 :old_value => 'Foo', :value => 'Bar')
208 assert_equal 'Description updated', show_detail(detail, true)
202 assert_equal 'Description updated', show_detail(detail, true)
209 end
203 end
210
204
211 test 'show_detail should show old and new values with a custom field' do
205 test 'show_detail should show old and new values with a custom field' do
212 detail = JournalDetail.new(:property => 'cf', :prop_key => '1',
206 detail = JournalDetail.new(:property => 'cf', :prop_key => '1',
213 :old_value => 'MySQL', :value => 'PostgreSQL')
207 :old_value => 'MySQL', :value => 'PostgreSQL')
214 assert_equal 'Database changed from MySQL to PostgreSQL', show_detail(detail, true)
208 assert_equal 'Database changed from MySQL to PostgreSQL', show_detail(detail, true)
215 end
209 end
216
210
217 test 'show_detail should not show values with a long text custom field' do
211 test 'show_detail should not show values with a long text custom field' do
218 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text')
212 field = IssueCustomField.create!(:name => "Long field", :field_format => 'text')
219 detail = JournalDetail.new(:property => 'cf', :prop_key => field.id,
213 detail = JournalDetail.new(:property => 'cf', :prop_key => field.id,
220 :old_value => 'Foo', :value => 'Bar')
214 :old_value => 'Foo', :value => 'Bar')
221 assert_equal 'Long field updated', show_detail(detail, true)
215 assert_equal 'Long field updated', show_detail(detail, true)
222 end
216 end
223
217
224 test 'show_detail should show added file' do
218 test 'show_detail should show added file' do
225 detail = JournalDetail.new(:property => 'attachment', :prop_key => '1',
219 detail = JournalDetail.new(:property => 'attachment', :prop_key => '1',
226 :old_value => nil, :value => 'error281.txt')
220 :old_value => nil, :value => 'error281.txt')
227 assert_match 'error281.txt', show_detail(detail, true)
221 assert_match 'error281.txt', show_detail(detail, true)
228 end
222 end
229
223
230 test 'show_detail should show removed file' do
224 test 'show_detail should show removed file' do
231 detail = JournalDetail.new(:property => 'attachment', :prop_key => '1',
225 detail = JournalDetail.new(:property => 'attachment', :prop_key => '1',
232 :old_value => 'error281.txt', :value => nil)
226 :old_value => 'error281.txt', :value => nil)
233 assert_match 'error281.txt', show_detail(detail, true)
227 assert_match 'error281.txt', show_detail(detail, true)
234 end
228 end
235
229
236 def test_show_detail_relation_added
230 def test_show_detail_relation_added
237 detail = JournalDetail.new(:property => 'relation',
231 detail = JournalDetail.new(:property => 'relation',
238 :prop_key => 'precedes',
232 :prop_key => 'precedes',
239 :value => 1)
233 :value => 1)
240 assert_equal "Precedes Bug #1: Cannot print recipes added", show_detail(detail, true)
234 assert_equal "Precedes Bug #1: Cannot print recipes added", show_detail(detail, true)
241 str = link_to("Bug #1", "/issues/1", :class => Issue.find(1).css_classes)
235 str = link_to("Bug #1", "/issues/1", :class => Issue.find(1).css_classes)
242 assert_equal "<strong>Precedes</strong> <i>#{str}: Cannot print recipes</i> added",
236 assert_equal "<strong>Precedes</strong> <i>#{str}: Cannot print recipes</i> added",
243 show_detail(detail, false)
237 show_detail(detail, false)
244 end
238 end
245
239
246 def test_show_detail_relation_added_with_inexistant_issue
240 def test_show_detail_relation_added_with_inexistant_issue
247 inexistant_issue_number = 9999
241 inexistant_issue_number = 9999
248 assert_nil Issue.find_by_id(inexistant_issue_number)
242 assert_nil Issue.find_by_id(inexistant_issue_number)
249 detail = JournalDetail.new(:property => 'relation',
243 detail = JournalDetail.new(:property => 'relation',
250 :prop_key => 'precedes',
244 :prop_key => 'precedes',
251 :value => inexistant_issue_number)
245 :value => inexistant_issue_number)
252 assert_equal "Precedes Issue ##{inexistant_issue_number} added", show_detail(detail, true)
246 assert_equal "Precedes Issue ##{inexistant_issue_number} added", show_detail(detail, true)
253 assert_equal "<strong>Precedes</strong> <i>Issue ##{inexistant_issue_number}</i> added", show_detail(detail, false)
247 assert_equal "<strong>Precedes</strong> <i>Issue ##{inexistant_issue_number}</i> added", show_detail(detail, false)
254 end
248 end
255
249
256 def test_show_detail_relation_added_should_not_disclose_issue_that_is_not_visible
250 def test_show_detail_relation_added_should_not_disclose_issue_that_is_not_visible
257 issue = Issue.generate!(:is_private => true)
251 issue = Issue.generate!(:is_private => true)
258 detail = JournalDetail.new(:property => 'relation',
252 detail = JournalDetail.new(:property => 'relation',
259 :prop_key => 'precedes',
253 :prop_key => 'precedes',
260 :value => issue.id)
254 :value => issue.id)
261
255
262 assert_equal "Precedes Issue ##{issue.id} added", show_detail(detail, true)
256 assert_equal "Precedes Issue ##{issue.id} added", show_detail(detail, true)
263 assert_equal "<strong>Precedes</strong> <i>Issue ##{issue.id}</i> added", show_detail(detail, false)
257 assert_equal "<strong>Precedes</strong> <i>Issue ##{issue.id}</i> added", show_detail(detail, false)
264 end
258 end
265
259
266 def test_show_detail_relation_deleted
260 def test_show_detail_relation_deleted
267 detail = JournalDetail.new(:property => 'relation',
261 detail = JournalDetail.new(:property => 'relation',
268 :prop_key => 'precedes',
262 :prop_key => 'precedes',
269 :old_value => 1)
263 :old_value => 1)
270 assert_equal "Precedes deleted (Bug #1: Cannot print recipes)", show_detail(detail, true)
264 assert_equal "Precedes deleted (Bug #1: Cannot print recipes)", show_detail(detail, true)
271 str = link_to("Bug #1",
265 str = link_to("Bug #1",
272 "/issues/1",
266 "/issues/1",
273 :class => Issue.find(1).css_classes)
267 :class => Issue.find(1).css_classes)
274 assert_equal "<strong>Precedes</strong> deleted (<i>#{str}: Cannot print recipes</i>)",
268 assert_equal "<strong>Precedes</strong> deleted (<i>#{str}: Cannot print recipes</i>)",
275 show_detail(detail, false)
269 show_detail(detail, false)
276 end
270 end
277
271
278 def test_show_detail_relation_deleted_with_inexistant_issue
272 def test_show_detail_relation_deleted_with_inexistant_issue
279 inexistant_issue_number = 9999
273 inexistant_issue_number = 9999
280 assert_nil Issue.find_by_id(inexistant_issue_number)
274 assert_nil Issue.find_by_id(inexistant_issue_number)
281 detail = JournalDetail.new(:property => 'relation',
275 detail = JournalDetail.new(:property => 'relation',
282 :prop_key => 'precedes',
276 :prop_key => 'precedes',
283 :old_value => inexistant_issue_number)
277 :old_value => inexistant_issue_number)
284 assert_equal "Precedes deleted (Issue #9999)", show_detail(detail, true)
278 assert_equal "Precedes deleted (Issue #9999)", show_detail(detail, true)
285 assert_equal "<strong>Precedes</strong> deleted (<i>Issue #9999</i>)", show_detail(detail, false)
279 assert_equal "<strong>Precedes</strong> deleted (<i>Issue #9999</i>)", show_detail(detail, false)
286 end
280 end
287
281
288 def test_show_detail_relation_deleted_should_not_disclose_issue_that_is_not_visible
282 def test_show_detail_relation_deleted_should_not_disclose_issue_that_is_not_visible
289 issue = Issue.generate!(:is_private => true)
283 issue = Issue.generate!(:is_private => true)
290 detail = JournalDetail.new(:property => 'relation',
284 detail = JournalDetail.new(:property => 'relation',
291 :prop_key => 'precedes',
285 :prop_key => 'precedes',
292 :old_value => issue.id)
286 :old_value => issue.id)
293
287
294 assert_equal "Precedes deleted (Issue ##{issue.id})", show_detail(detail, true)
288 assert_equal "Precedes deleted (Issue ##{issue.id})", show_detail(detail, true)
295 assert_equal "<strong>Precedes</strong> deleted (<i>Issue ##{issue.id}</i>)", show_detail(detail, false)
289 assert_equal "<strong>Precedes</strong> deleted (<i>Issue ##{issue.id}</i>)", show_detail(detail, false)
296 end
290 end
297
291
298 def test_details_to_strings_with_multiple_values_removed_from_custom_field
292 def test_details_to_strings_with_multiple_values_removed_from_custom_field
299 field = IssueCustomField.generate!(:name => 'User', :field_format => 'user', :multiple => true)
293 field = IssueCustomField.generate!(:name => 'User', :field_format => 'user', :multiple => true)
300 details = []
294 details = []
301 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '1', :value => nil)
295 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '1', :value => nil)
302 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '3', :value => nil)
296 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '3', :value => nil)
303
297
304 assert_equal ["User deleted (Dave Lopper, Redmine Admin)"], details_to_strings(details, true)
298 assert_equal ["User deleted (Dave Lopper, Redmine Admin)"], details_to_strings(details, true)
305 assert_equal ["<strong>User</strong> deleted (<del><i>Dave Lopper, Redmine Admin</i></del>)"], details_to_strings(details, false)
299 assert_equal ["<strong>User</strong> deleted (<del><i>Dave Lopper, Redmine Admin</i></del>)"], details_to_strings(details, false)
306 end
300 end
307
301
308 def test_details_to_strings_with_multiple_values_added_to_custom_field
302 def test_details_to_strings_with_multiple_values_added_to_custom_field
309 field = IssueCustomField.generate!(:name => 'User', :field_format => 'user', :multiple => true)
303 field = IssueCustomField.generate!(:name => 'User', :field_format => 'user', :multiple => true)
310 details = []
304 details = []
311 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => nil, :value => '1')
305 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => nil, :value => '1')
312 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => nil, :value => '3')
306 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => nil, :value => '3')
313
307
314 assert_equal ["User Dave Lopper, Redmine Admin added"], details_to_strings(details, true)
308 assert_equal ["User Dave Lopper, Redmine Admin added"], details_to_strings(details, true)
315 assert_equal ["<strong>User</strong> <i>Dave Lopper, Redmine Admin</i> added"], details_to_strings(details, false)
309 assert_equal ["<strong>User</strong> <i>Dave Lopper, Redmine Admin</i> added"], details_to_strings(details, false)
316 end
310 end
317
311
318 def test_details_to_strings_with_multiple_values_added_and_removed_from_custom_field
312 def test_details_to_strings_with_multiple_values_added_and_removed_from_custom_field
319 field = IssueCustomField.generate!(:name => 'User', :field_format => 'user', :multiple => true)
313 field = IssueCustomField.generate!(:name => 'User', :field_format => 'user', :multiple => true)
320 details = []
314 details = []
321 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => nil, :value => '1')
315 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => nil, :value => '1')
322 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '2', :value => nil)
316 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '2', :value => nil)
323 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '3', :value => nil)
317 details << JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s, :old_value => '3', :value => nil)
324
318
325 assert_equal [
319 assert_equal [
326 "User Redmine Admin added",
320 "User Redmine Admin added",
327 "User deleted (Dave Lopper, John Smith)"
321 "User deleted (Dave Lopper, John Smith)"
328 ], details_to_strings(details, true)
322 ], details_to_strings(details, true)
329 assert_equal [
323 assert_equal [
330 "<strong>User</strong> <i>Redmine Admin</i> added",
324 "<strong>User</strong> <i>Redmine Admin</i> added",
331 "<strong>User</strong> deleted (<del><i>Dave Lopper, John Smith</i></del>)"
325 "<strong>User</strong> deleted (<del><i>Dave Lopper, John Smith</i></del>)"
332 ], details_to_strings(details, false)
326 ], details_to_strings(details, false)
333 end
327 end
334
328
335 def test_find_name_by_reflection_should_return_nil_for_missing_record
329 def test_find_name_by_reflection_should_return_nil_for_missing_record
336 assert_nil find_name_by_reflection('status', 99)
330 assert_nil find_name_by_reflection('status', 99)
337 end
331 end
338 end
332 end
@@ -1,44 +1,43
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class MembersHelperTest < Redmine::HelperTest
20 class MembersHelperTest < Redmine::HelperTest
21 include Redmine::I18n
22 include ERB::Util
21 include ERB::Util
23 include MembersHelper
22 include MembersHelper
24 include Rails.application.routes.url_helpers
23 include Rails.application.routes.url_helpers
25
24
26 fixtures :projects, :users, :members, :member_roles,
25 fixtures :projects, :users, :members, :member_roles,
27 :trackers, :issue_statuses
26 :trackers, :issue_statuses
28
27
29 def test_render_principals_for_new_members
28 def test_render_principals_for_new_members
30 project = Project.generate!
29 project = Project.generate!
31
30
32 result = render_principals_for_new_members(project)
31 result = render_principals_for_new_members(project)
33 assert_select_in result, 'input[name=?][value="2"]', 'membership[user_ids][]'
32 assert_select_in result, 'input[name=?][value="2"]', 'membership[user_ids][]'
34 end
33 end
35
34
36 def test_render_principals_for_new_members_with_limited_results_should_paginate
35 def test_render_principals_for_new_members_with_limited_results_should_paginate
37 project = Project.generate!
36 project = Project.generate!
38
37
39 result = render_principals_for_new_members(project, 3)
38 result = render_principals_for_new_members(project, 3)
40 assert_select_in result, 'span.pagination'
39 assert_select_in result, 'span.pagination'
41 assert_select_in result, 'span.pagination li.current span', :text => '1'
40 assert_select_in result, 'span.pagination li.current span', :text => '1'
42 assert_select_in result, 'a[href=?]', "/projects/#{project.identifier}/memberships/autocomplete.js?page=2", :text => '2'
41 assert_select_in result, 'a[href=?]', "/projects/#{project.identifier}/memberships/autocomplete.js?page=2", :text => '2'
43 end
42 end
44 end
43 end
@@ -1,50 +1,49
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class MyHelperTest < Redmine::HelperTest
20 class MyHelperTest < Redmine::HelperTest
21 include Redmine::I18n
22 include ERB::Util
21 include ERB::Util
23 include MyHelper
22 include MyHelper
24
23
25
24
26 fixtures :projects, :trackers, :issue_statuses, :issues,
25 fixtures :projects, :trackers, :issue_statuses, :issues,
27 :enumerations, :users, :issue_categories,
26 :enumerations, :users, :issue_categories,
28 :projects_trackers,
27 :projects_trackers,
29 :roles,
28 :roles,
30 :member_roles,
29 :member_roles,
31 :members,
30 :members,
32 :enabled_modules,
31 :enabled_modules,
33 :versions
32 :versions
34
33
35 def test_timelog_items_should_include_time_entries_without_issue
34 def test_timelog_items_should_include_time_entries_without_issue
36 User.current = User.find(2)
35 User.current = User.find(2)
37 entry = TimeEntry.generate!(:spent_on => Date.today, :user_id => 2, :project_id => 1)
36 entry = TimeEntry.generate!(:spent_on => Date.today, :user_id => 2, :project_id => 1)
38 assert_nil entry.issue
37 assert_nil entry.issue
39
38
40 assert_include entry, timelog_items.first
39 assert_include entry, timelog_items.first
41 end
40 end
42
41
43 def test_timelog_items_should_include_time_entries_with_issue
42 def test_timelog_items_should_include_time_entries_with_issue
44 User.current = User.find(2)
43 User.current = User.find(2)
45 entry = TimeEntry.generate!(:spent_on => Date.today, :user_id => 2, :project_id => 1, :issue_id => 1)
44 entry = TimeEntry.generate!(:spent_on => Date.today, :user_id => 2, :project_id => 1, :issue_id => 1)
46 assert_not_nil entry.issue
45 assert_not_nil entry.issue
47
46
48 assert_include entry, timelog_items.first
47 assert_include entry, timelog_items.first
49 end
48 end
50 end
49 end
@@ -1,84 +1,78
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class ProjectsHelperTest < Redmine::HelperTest
20 class ProjectsHelperTest < Redmine::HelperTest
21 include ApplicationHelper
21 include ApplicationHelper
22 include ProjectsHelper
22 include ProjectsHelper
23 include Redmine::I18n
24 include ERB::Util
23 include ERB::Util
25 include Rails.application.routes.url_helpers
24 include Rails.application.routes.url_helpers
26
25
27 fixtures :projects, :trackers, :issue_statuses, :issues,
26 fixtures :projects, :trackers, :issue_statuses, :issues,
28 :enumerations, :users, :issue_categories,
27 :enumerations, :users, :issue_categories,
29 :versions,
28 :versions,
30 :projects_trackers,
29 :projects_trackers,
31 :member_roles,
30 :member_roles,
32 :members,
31 :members,
33 :groups_users,
32 :groups_users,
34 :enabled_modules
33 :enabled_modules
35
34
36 def setup
37 super
38 set_language_if_valid('en')
39 end
40
41 def test_link_to_version_within_project
35 def test_link_to_version_within_project
42 @project = Project.find(2)
36 @project = Project.find(2)
43 User.current = User.find(1)
37 User.current = User.find(1)
44 assert_equal '<a title="07/01/2006" href="/versions/5">Alpha</a>', link_to_version(Version.find(5))
38 assert_equal '<a title="07/01/2006" href="/versions/5">Alpha</a>', link_to_version(Version.find(5))
45 end
39 end
46
40
47 def test_link_to_version
41 def test_link_to_version
48 User.current = User.find(1)
42 User.current = User.find(1)
49 assert_equal '<a title="07/01/2006" href="/versions/5">OnlineStore - Alpha</a>', link_to_version(Version.find(5))
43 assert_equal '<a title="07/01/2006" href="/versions/5">OnlineStore - Alpha</a>', link_to_version(Version.find(5))
50 end
44 end
51
45
52 def test_link_to_version_without_effective_date
46 def test_link_to_version_without_effective_date
53 User.current = User.find(1)
47 User.current = User.find(1)
54 version = Version.find(5)
48 version = Version.find(5)
55 version.effective_date = nil
49 version.effective_date = nil
56 assert_equal '<a href="/versions/5">OnlineStore - Alpha</a>', link_to_version(version)
50 assert_equal '<a href="/versions/5">OnlineStore - Alpha</a>', link_to_version(version)
57 end
51 end
58
52
59 def test_link_to_private_version
53 def test_link_to_private_version
60 assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5))
54 assert_equal 'OnlineStore - Alpha', link_to_version(Version.find(5))
61 end
55 end
62
56
63 def test_link_to_version_invalid_version
57 def test_link_to_version_invalid_version
64 assert_equal '', link_to_version(Object)
58 assert_equal '', link_to_version(Object)
65 end
59 end
66
60
67 def test_format_version_name_within_project
61 def test_format_version_name_within_project
68 @project = Project.find(1)
62 @project = Project.find(1)
69 assert_equal "0.1", format_version_name(Version.find(1))
63 assert_equal "0.1", format_version_name(Version.find(1))
70 end
64 end
71
65
72 def test_format_version_name
66 def test_format_version_name
73 assert_equal "eCookbook - 0.1", format_version_name(Version.find(1))
67 assert_equal "eCookbook - 0.1", format_version_name(Version.find(1))
74 end
68 end
75
69
76 def test_format_version_name_for_system_version
70 def test_format_version_name_for_system_version
77 assert_equal "OnlineStore - Systemwide visible version", format_version_name(Version.find(7))
71 assert_equal "OnlineStore - Systemwide visible version", format_version_name(Version.find(7))
78 end
72 end
79
73
80 def test_version_options_for_select_with_no_versions
74 def test_version_options_for_select_with_no_versions
81 assert_equal '', version_options_for_select([])
75 assert_equal '', version_options_for_select([])
82 assert_equal '', version_options_for_select([], Version.find(1))
76 assert_equal '', version_options_for_select([], Version.find(1))
83 end
77 end
84 end
78 end
@@ -1,97 +1,96
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class QueriesHelperTest < Redmine::HelperTest
20 class QueriesHelperTest < Redmine::HelperTest
21 include QueriesHelper
21 include QueriesHelper
22 include Redmine::I18n
23
22
24 fixtures :projects, :enabled_modules, :users, :members,
23 fixtures :projects, :enabled_modules, :users, :members,
25 :member_roles, :roles, :trackers, :issue_statuses,
24 :member_roles, :roles, :trackers, :issue_statuses,
26 :issue_categories, :enumerations, :issues,
25 :issue_categories, :enumerations, :issues,
27 :watchers, :custom_fields, :custom_values, :versions,
26 :watchers, :custom_fields, :custom_values, :versions,
28 :queries,
27 :queries,
29 :projects_trackers,
28 :projects_trackers,
30 :custom_fields_trackers
29 :custom_fields_trackers
31
30
32 def test_filters_options_for_select_should_have_a_blank_option
31 def test_filters_options_for_select_should_have_a_blank_option
33 options = filters_options_for_select(IssueQuery.new)
32 options = filters_options_for_select(IssueQuery.new)
34 assert_select_in options, 'option[value=""]'
33 assert_select_in options, 'option[value=""]'
35 end
34 end
36
35
37 def test_filters_options_for_select_should_not_group_regular_filters
36 def test_filters_options_for_select_should_not_group_regular_filters
38 with_locale 'en' do
37 with_locale 'en' do
39 options = filters_options_for_select(IssueQuery.new)
38 options = filters_options_for_select(IssueQuery.new)
40 assert_select_in options, 'optgroup option[value=status_id]', 0
39 assert_select_in options, 'optgroup option[value=status_id]', 0
41 assert_select_in options, 'option[value=status_id]', :text => 'Status'
40 assert_select_in options, 'option[value=status_id]', :text => 'Status'
42 end
41 end
43 end
42 end
44
43
45 def test_filters_options_for_select_should_group_date_filters
44 def test_filters_options_for_select_should_group_date_filters
46 with_locale 'en' do
45 with_locale 'en' do
47 options = filters_options_for_select(IssueQuery.new)
46 options = filters_options_for_select(IssueQuery.new)
48 assert_select_in options, 'optgroup[label=?]', 'Date', 1
47 assert_select_in options, 'optgroup[label=?]', 'Date', 1
49 assert_select_in options, 'optgroup > option[value=due_date]', :text => 'Due date'
48 assert_select_in options, 'optgroup > option[value=due_date]', :text => 'Due date'
50 end
49 end
51 end
50 end
52
51
53 def test_filters_options_for_select_should_not_group_only_one_date_filter
52 def test_filters_options_for_select_should_not_group_only_one_date_filter
54 with_locale 'en' do
53 with_locale 'en' do
55 options = filters_options_for_select(TimeEntryQuery.new)
54 options = filters_options_for_select(TimeEntryQuery.new)
56 assert_select_in options, 'option[value=spent_on]'
55 assert_select_in options, 'option[value=spent_on]'
57 assert_select_in options, 'optgroup[label=?]', 'Date', 0
56 assert_select_in options, 'optgroup[label=?]', 'Date', 0
58 assert_select_in options, 'optgroup option[value=spent_on]', 0
57 assert_select_in options, 'optgroup option[value=spent_on]', 0
59 end
58 end
60 end
59 end
61
60
62 def test_filters_options_for_select_should_group_relations_filters
61 def test_filters_options_for_select_should_group_relations_filters
63 with_locale 'en' do
62 with_locale 'en' do
64 options = filters_options_for_select(IssueQuery.new)
63 options = filters_options_for_select(IssueQuery.new)
65 assert_select_in options, 'optgroup[label=?]', 'Relations', 1
64 assert_select_in options, 'optgroup[label=?]', 'Relations', 1
66 assert_select_in options, 'optgroup[label=?] > option', 'Relations', 11
65 assert_select_in options, 'optgroup[label=?] > option', 'Relations', 11
67 assert_select_in options, 'optgroup > option[value=relates]', :text => 'Related to'
66 assert_select_in options, 'optgroup > option[value=relates]', :text => 'Related to'
68 end
67 end
69 end
68 end
70
69
71 def test_filters_options_for_select_should_group_associations_filters
70 def test_filters_options_for_select_should_group_associations_filters
72 CustomField.delete_all
71 CustomField.delete_all
73 cf1 = ProjectCustomField.create!(:name => 'Foo', :field_format => 'string', :is_filter => true)
72 cf1 = ProjectCustomField.create!(:name => 'Foo', :field_format => 'string', :is_filter => true)
74 cf2 = ProjectCustomField.create!(:name => 'Bar', :field_format => 'string', :is_filter => true)
73 cf2 = ProjectCustomField.create!(:name => 'Bar', :field_format => 'string', :is_filter => true)
75
74
76 with_locale 'en' do
75 with_locale 'en' do
77 options = filters_options_for_select(IssueQuery.new)
76 options = filters_options_for_select(IssueQuery.new)
78 assert_select_in options, 'optgroup[label=?]', 'Project', 1
77 assert_select_in options, 'optgroup[label=?]', 'Project', 1
79 assert_select_in options, 'optgroup[label=?] > option', 'Project', 2
78 assert_select_in options, 'optgroup[label=?] > option', 'Project', 2
80 assert_select_in options, 'optgroup > option[value=?]', "project.cf_#{cf1.id}", :text => "Project's Foo"
79 assert_select_in options, 'optgroup > option[value=?]', "project.cf_#{cf1.id}", :text => "Project's Foo"
81 end
80 end
82 end
81 end
83
82
84 def test_query_to_csv_should_translate_boolean_custom_field_values
83 def test_query_to_csv_should_translate_boolean_custom_field_values
85 f = IssueCustomField.generate!(:field_format => 'bool', :name => 'Boolean', :is_for_all => true, :trackers => Tracker.all)
84 f = IssueCustomField.generate!(:field_format => 'bool', :name => 'Boolean', :is_for_all => true, :trackers => Tracker.all)
86 issues = [
85 issues = [
87 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '0'}),
86 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '0'}),
88 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '1'})
87 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {f.id.to_s => '1'})
89 ]
88 ]
90
89
91 with_locale 'fr' do
90 with_locale 'fr' do
92 csv = query_to_csv(issues, IssueQuery.new(:column_names => ['id', "cf_#{f.id}"]))
91 csv = query_to_csv(issues, IssueQuery.new(:column_names => ['id', "cf_#{f.id}"]))
93 assert_include "Oui", csv
92 assert_include "Oui", csv
94 assert_include "Non", csv
93 assert_include "Non", csv
95 end
94 end
96 end
95 end
97 end
96 end
@@ -1,49 +1,48
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../../test_helper', __FILE__)
20 require File.expand_path('../../../test_helper', __FILE__)
21
21
22 class SearchHelperTest < Redmine::HelperTest
22 class SearchHelperTest < Redmine::HelperTest
23 include SearchHelper
23 include SearchHelper
24 include Redmine::I18n
25 include ERB::Util
24 include ERB::Util
26
25
27 def test_highlight_single_token
26 def test_highlight_single_token
28 assert_equal 'This is a <span class="highlight token-0">token</span>.',
27 assert_equal 'This is a <span class="highlight token-0">token</span>.',
29 highlight_tokens('This is a token.', %w(token))
28 highlight_tokens('This is a token.', %w(token))
30 end
29 end
31
30
32 def test_highlight_multiple_tokens
31 def test_highlight_multiple_tokens
33 assert_equal 'This is a <span class="highlight token-0">token</span> and <span class="highlight token-1">another</span> <span class="highlight token-0">token</span>.',
32 assert_equal 'This is a <span class="highlight token-0">token</span> and <span class="highlight token-1">another</span> <span class="highlight token-0">token</span>.',
34 highlight_tokens('This is a token and another token.', %w(token another))
33 highlight_tokens('This is a token and another token.', %w(token another))
35 end
34 end
36
35
37 def test_highlight_should_not_exceed_maximum_length
36 def test_highlight_should_not_exceed_maximum_length
38 s = (('1234567890' * 100) + ' token ') * 100
37 s = (('1234567890' * 100) + ' token ') * 100
39 r = highlight_tokens(s, %w(token))
38 r = highlight_tokens(s, %w(token))
40 assert r.include?('<span class="highlight token-0">token</span>')
39 assert r.include?('<span class="highlight token-0">token</span>')
41 assert r.length <= 1300
40 assert r.length <= 1300
42 end
41 end
43
42
44 def test_highlight_multibyte
43 def test_highlight_multibyte
45 s = ('ΠΉ' * 200) + ' token ' + ('ΠΉ' * 200)
44 s = ('ΠΉ' * 200) + ' token ' + ('ΠΉ' * 200)
46 r = highlight_tokens(s, %w(token))
45 r = highlight_tokens(s, %w(token))
47 assert_equal ('ΠΉ' * 45) + ' ... ' + ('ΠΉ' * 44) + ' <span class="highlight token-0">token</span> ' + ('ΠΉ' * 44) + ' ... ' + ('ΠΉ' * 45), r
46 assert_equal ('ΠΉ' * 45) + ' ... ' + ('ΠΉ' * 44) + ' <span class="highlight token-0">token</span> ' + ('ΠΉ' * 44) + ' ... ' + ('ΠΉ' * 45), r
48 end
47 end
49 end
48 end
@@ -1,31 +1,30
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class SettingsHelperTest < Redmine::HelperTest
20 class SettingsHelperTest < Redmine::HelperTest
21 include SettingsHelper
21 include SettingsHelper
22 include Redmine::I18n
23 include ERB::Util
22 include ERB::Util
24
23
25 def test_date_format_setting_options_should_include_human_readable_format
24 def test_date_format_setting_options_should_include_human_readable_format
26 Date.stubs(:today).returns(Date.parse("2015-07-14"))
25 Date.stubs(:today).returns(Date.parse("2015-07-14"))
27
26
28 options = date_format_setting_options('en')
27 options = date_format_setting_options('en')
29 assert_include ["2015-07-14 (yyyy-mm-dd)", "%Y-%m-%d"], options
28 assert_include ["2015-07-14 (yyyy-mm-dd)", "%Y-%m-%d"], options
30 end
29 end
31 end
30 end
@@ -1,110 +1,109
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class SortHelperTest < Redmine::HelperTest
20 class SortHelperTest < Redmine::HelperTest
21 include SortHelper
21 include SortHelper
22 include Redmine::I18n
23 include ERB::Util
22 include ERB::Util
24
23
25 def setup
24 def setup
26 super
25 super
27 @session = nil
26 @session = nil
28 @sort_param = nil
27 @sort_param = nil
29 end
28 end
30
29
31 def test_default_sort_clause_with_array
30 def test_default_sort_clause_with_array
32 sort_init 'attr1', 'desc'
31 sort_init 'attr1', 'desc'
33 sort_update(['attr1', 'attr2'])
32 sort_update(['attr1', 'attr2'])
34
33
35 assert_equal ['attr1 DESC'], sort_clause
34 assert_equal ['attr1 DESC'], sort_clause
36 end
35 end
37
36
38 def test_default_sort_clause_with_hash
37 def test_default_sort_clause_with_hash
39 sort_init 'attr1', 'desc'
38 sort_init 'attr1', 'desc'
40 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
39 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
41
40
42 assert_equal ['table1.attr1 DESC'], sort_clause
41 assert_equal ['table1.attr1 DESC'], sort_clause
43 end
42 end
44
43
45 def test_default_sort_clause_with_multiple_columns
44 def test_default_sort_clause_with_multiple_columns
46 sort_init 'attr1', 'desc'
45 sort_init 'attr1', 'desc'
47 sort_update({'attr1' => ['table1.attr1', 'table1.attr2'], 'attr2' => 'table2.attr2'})
46 sort_update({'attr1' => ['table1.attr1', 'table1.attr2'], 'attr2' => 'table2.attr2'})
48
47
49 assert_equal ['table1.attr1 DESC', 'table1.attr2 DESC'], sort_clause
48 assert_equal ['table1.attr1 DESC', 'table1.attr2 DESC'], sort_clause
50 end
49 end
51
50
52 def test_params_sort
51 def test_params_sort
53 @sort_param = 'attr1,attr2:desc'
52 @sort_param = 'attr1,attr2:desc'
54
53
55 sort_init 'attr1', 'desc'
54 sort_init 'attr1', 'desc'
56 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
55 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
57
56
58 assert_equal ['table1.attr1 ASC', 'table2.attr2 DESC'], sort_clause
57 assert_equal ['table1.attr1 ASC', 'table2.attr2 DESC'], sort_clause
59 assert_equal 'attr1,attr2:desc', @session['foo_bar_sort']
58 assert_equal 'attr1,attr2:desc', @session['foo_bar_sort']
60 end
59 end
61
60
62 def test_invalid_params_sort
61 def test_invalid_params_sort
63 @sort_param = 'invalid_key'
62 @sort_param = 'invalid_key'
64
63
65 sort_init 'attr1', 'desc'
64 sort_init 'attr1', 'desc'
66 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
65 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
67
66
68 assert_equal ['table1.attr1 DESC'], sort_clause
67 assert_equal ['table1.attr1 DESC'], sort_clause
69 assert_equal 'attr1:desc', @session['foo_bar_sort']
68 assert_equal 'attr1:desc', @session['foo_bar_sort']
70 end
69 end
71
70
72 def test_invalid_order_params_sort
71 def test_invalid_order_params_sort
73 @sort_param = 'attr1:foo:bar,attr2'
72 @sort_param = 'attr1:foo:bar,attr2'
74
73
75 sort_init 'attr1', 'desc'
74 sort_init 'attr1', 'desc'
76 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
75 sort_update({'attr1' => 'table1.attr1', 'attr2' => 'table2.attr2'})
77
76
78 assert_equal ['table1.attr1 ASC', 'table2.attr2 ASC'], sort_clause
77 assert_equal ['table1.attr1 ASC', 'table2.attr2 ASC'], sort_clause
79 assert_equal 'attr1,attr2', @session['foo_bar_sort']
78 assert_equal 'attr1,attr2', @session['foo_bar_sort']
80 end
79 end
81
80
82 def test_sort_css_without_params_should_use_default_sort
81 def test_sort_css_without_params_should_use_default_sort
83 sort_init 'attr1', 'desc'
82 sort_init 'attr1', 'desc'
84 sort_update(['attr1', 'attr2'])
83 sort_update(['attr1', 'attr2'])
85
84
86 assert_equal 'sort-by-attr1 sort-desc', sort_css_classes
85 assert_equal 'sort-by-attr1 sort-desc', sort_css_classes
87 end
86 end
88
87
89 def test_sort_css_should_use_params
88 def test_sort_css_should_use_params
90 @sort_param = 'attr2,attr1'
89 @sort_param = 'attr2,attr1'
91 sort_init 'attr1', 'desc'
90 sort_init 'attr1', 'desc'
92 sort_update(['attr1', 'attr2'])
91 sort_update(['attr1', 'attr2'])
93
92
94 assert_equal 'sort-by-attr2 sort-asc', sort_css_classes
93 assert_equal 'sort-by-attr2 sort-asc', sort_css_classes
95 end
94 end
96
95
97 def test_sort_css_should_dasherize_sort_name
96 def test_sort_css_should_dasherize_sort_name
98 sort_init 'foo_bar'
97 sort_init 'foo_bar'
99 sort_update(['foo_bar'])
98 sort_update(['foo_bar'])
100
99
101 assert_equal 'sort-by-foo-bar sort-asc', sort_css_classes
100 assert_equal 'sort-by-foo-bar sort-asc', sort_css_classes
102 end
101 end
103
102
104 private
103 private
105
104
106 def controller_name; 'foo'; end
105 def controller_name; 'foo'; end
107 def action_name; 'bar'; end
106 def action_name; 'bar'; end
108 def params; {:sort => @sort_param}; end
107 def params; {:sort => @sort_param}; end
109 def session; @session ||= {}; end
108 def session; @session ||= {}; end
110 end
109 end
@@ -1,54 +1,53
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class TimelogHelperTest < Redmine::HelperTest
20 class TimelogHelperTest < Redmine::HelperTest
21 include TimelogHelper
21 include TimelogHelper
22 include Redmine::I18n
23 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
24 include ActionView::Helpers::DateHelper
23 include ActionView::Helpers::DateHelper
25 include ERB::Util
24 include ERB::Util
26
25
27 fixtures :projects, :roles, :enabled_modules, :users,
26 fixtures :projects, :roles, :enabled_modules, :users,
28 :repositories, :changesets,
27 :repositories, :changesets,
29 :trackers, :issue_statuses, :issues, :versions, :documents,
28 :trackers, :issue_statuses, :issues, :versions, :documents,
30 :wikis, :wiki_pages, :wiki_contents,
29 :wikis, :wiki_pages, :wiki_contents,
31 :boards, :messages,
30 :boards, :messages,
32 :attachments,
31 :attachments,
33 :enumerations
32 :enumerations
34
33
35 def test_activities_collection_for_select_options_should_return_array_of_activity_names_and_ids
34 def test_activities_collection_for_select_options_should_return_array_of_activity_names_and_ids
36 activities = activity_collection_for_select_options
35 activities = activity_collection_for_select_options
37 assert activities.include?(["Design", 9])
36 assert activities.include?(["Design", 9])
38 assert activities.include?(["Development", 10])
37 assert activities.include?(["Development", 10])
39 end
38 end
40
39
41 def test_activities_collection_for_select_options_should_not_include_inactive_activities
40 def test_activities_collection_for_select_options_should_not_include_inactive_activities
42 activities = activity_collection_for_select_options
41 activities = activity_collection_for_select_options
43 assert !activities.include?(["Inactive Activity", 14])
42 assert !activities.include?(["Inactive Activity", 14])
44 end
43 end
45
44
46 def test_activities_collection_for_select_options_should_use_the_projects_override
45 def test_activities_collection_for_select_options_should_use_the_projects_override
47 project = Project.find(1)
46 project = Project.find(1)
48 override_activity = TimeEntryActivity.create!({:name => "Design override", :parent => TimeEntryActivity.find_by_name("Design"), :project => project})
47 override_activity = TimeEntryActivity.create!({:name => "Design override", :parent => TimeEntryActivity.find_by_name("Design"), :project => project})
49
48
50 activities = activity_collection_for_select_options(nil, project)
49 activities = activity_collection_for_select_options(nil, project)
51 assert !activities.include?(["Design", 9]), "System activity found in: " + activities.inspect
50 assert !activities.include?(["Design", 9]), "System activity found in: " + activities.inspect
52 assert activities.include?(["Design override", override_activity.id]), "Override activity not found in: " + activities.inspect
51 assert activities.include?(["Design override", override_activity.id]), "Override activity not found in: " + activities.inspect
53 end
52 end
54 end
53 end
@@ -1,73 +1,67
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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.expand_path('../../../test_helper', __FILE__)
18 require File.expand_path('../../../test_helper', __FILE__)
19
19
20 class WatchersHelperTest < Redmine::HelperTest
20 class WatchersHelperTest < Redmine::HelperTest
21 include WatchersHelper
21 include WatchersHelper
22 include Redmine::I18n
23 include Rails.application.routes.url_helpers
22 include Rails.application.routes.url_helpers
24
23
25 fixtures :users, :issues
24 fixtures :users, :issues
26
25
27 def setup
28 super
29 set_language_if_valid('en')
30 end
31
32 test '#watcher_link with a non-watched object' do
26 test '#watcher_link with a non-watched object' do
33 expected = link_to(
27 expected = link_to(
34 "Watch",
28 "Watch",
35 "/watchers/watch?object_id=1&object_type=issue",
29 "/watchers/watch?object_id=1&object_type=issue",
36 :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
30 :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
37 )
31 )
38 assert_equal expected, watcher_link(Issue.find(1), User.find(1))
32 assert_equal expected, watcher_link(Issue.find(1), User.find(1))
39 end
33 end
40
34
41 test '#watcher_link with a single objet array' do
35 test '#watcher_link with a single objet array' do
42 expected = link_to(
36 expected = link_to(
43 "Watch",
37 "Watch",
44 "/watchers/watch?object_id=1&object_type=issue",
38 "/watchers/watch?object_id=1&object_type=issue",
45 :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
39 :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off"
46 )
40 )
47 assert_equal expected, watcher_link([Issue.find(1)], User.find(1))
41 assert_equal expected, watcher_link([Issue.find(1)], User.find(1))
48 end
42 end
49
43
50 test '#watcher_link with a multiple objets array' do
44 test '#watcher_link with a multiple objets array' do
51 expected = link_to(
45 expected = link_to(
52 "Watch",
46 "Watch",
53 "/watchers/watch?object_id%5B%5D=1&object_id%5B%5D=3&object_type=issue",
47 "/watchers/watch?object_id%5B%5D=1&object_id%5B%5D=3&object_type=issue",
54 :remote => true, :method => 'post', :class => "issue-bulk-watcher icon icon-fav-off"
48 :remote => true, :method => 'post', :class => "issue-bulk-watcher icon icon-fav-off"
55 )
49 )
56 assert_equal expected, watcher_link([Issue.find(1), Issue.find(3)], User.find(1))
50 assert_equal expected, watcher_link([Issue.find(1), Issue.find(3)], User.find(1))
57 end
51 end
58
52
59 def test_watcher_link_with_nil_should_return_empty_string
53 def test_watcher_link_with_nil_should_return_empty_string
60 assert_equal '', watcher_link(nil, User.find(1))
54 assert_equal '', watcher_link(nil, User.find(1))
61 end
55 end
62
56
63 test '#watcher_link with a watched object' do
57 test '#watcher_link with a watched object' do
64 Watcher.create!(:watchable => Issue.find(1), :user => User.find(1))
58 Watcher.create!(:watchable => Issue.find(1), :user => User.find(1))
65
59
66 expected = link_to(
60 expected = link_to(
67 "Unwatch",
61 "Unwatch",
68 "/watchers/watch?object_id=1&object_type=issue",
62 "/watchers/watch?object_id=1&object_type=issue",
69 :remote => true, :method => 'delete', :class => "issue-1-watcher icon icon-fav"
63 :remote => true, :method => 'delete', :class => "issue-1-watcher icon icon-fav"
70 )
64 )
71 assert_equal expected, watcher_link(Issue.find(1), User.find(1))
65 assert_equal expected, watcher_link(Issue.find(1), User.find(1))
72 end
66 end
73 end
67 end
General Comments 0
You need to be logged in to leave comments. Login now