##// END OF EJS Templates
Make the tests pass when config.threadsafe! is enabled (#12097)....
Jean-Philippe Lang -
r10683:e821020394a6
parent child
Show More
@@ -1,346 +1,346
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 RedmineApp::Application.routes.draw do
19 19 root :to => 'welcome#index', :as => 'home'
20 20
21 21 match 'login', :to => 'account#login', :as => 'signin'
22 22 match 'logout', :to => 'account#logout', :as => 'signout'
23 23 match 'account/register', :to => 'account#register', :via => [:get, :post], :as => 'register'
24 24 match 'account/lost_password', :to => 'account#lost_password', :via => [:get, :post], :as => 'lost_password'
25 25 match 'account/activate', :to => 'account#activate', :via => :get
26 26
27 27 match '/news/preview', :controller => 'previews', :action => 'news', :as => 'preview_news'
28 28 match '/issues/preview/new/:project_id', :to => 'previews#issue', :as => 'preview_new_issue'
29 29 match '/issues/preview/edit/:id', :to => 'previews#issue', :as => 'preview_edit_issue'
30 30 match '/issues/preview', :to => 'previews#issue', :as => 'preview_issue'
31 31
32 32 match 'projects/:id/wiki', :to => 'wikis#edit', :via => :post
33 33 match 'projects/:id/wiki/destroy', :to => 'wikis#destroy', :via => [:get, :post]
34 34
35 35 match 'boards/:board_id/topics/new', :to => 'messages#new', :via => [:get, :post]
36 36 get 'boards/:board_id/topics/:id', :to => 'messages#show', :as => 'board_message'
37 37 match 'boards/:board_id/topics/quote/:id', :to => 'messages#quote', :via => [:get, :post]
38 38 get 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
39 39
40 40 post 'boards/:board_id/topics/preview', :to => 'messages#preview'
41 41 post 'boards/:board_id/topics/:id/replies', :to => 'messages#reply'
42 42 post 'boards/:board_id/topics/:id/edit', :to => 'messages#edit'
43 43 post 'boards/:board_id/topics/:id/destroy', :to => 'messages#destroy'
44 44
45 45 # Misc issue routes. TODO: move into resources
46 46 match '/issues/auto_complete', :to => 'auto_completes#issues', :via => :get, :as => 'auto_complete_issues'
47 47 match '/issues/context_menu', :to => 'context_menus#issues', :as => 'issues_context_menu'
48 48 match '/issues/changes', :to => 'journals#index', :as => 'issue_changes'
49 49 match '/issues/:id/quoted', :to => 'journals#new', :id => /\d+/, :via => :post, :as => 'quoted_issue'
50 50
51 51 match '/journals/diff/:id', :to => 'journals#diff', :id => /\d+/, :via => :get
52 52 match '/journals/edit/:id', :to => 'journals#edit', :id => /\d+/, :via => [:get, :post]
53 53
54 54 match '/projects/:project_id/issues/gantt', :to => 'gantts#show'
55 55 match '/issues/gantt', :to => 'gantts#show'
56 56
57 57 match '/projects/:project_id/issues/calendar', :to => 'calendars#show'
58 58 match '/issues/calendar', :to => 'calendars#show'
59 59
60 60 match 'projects/:id/issues/report', :to => 'reports#issue_report', :via => :get
61 61 match 'projects/:id/issues/report/:detail', :to => 'reports#issue_report_details', :via => :get
62 62
63 63 match 'my/account', :controller => 'my', :action => 'account', :via => [:get, :post]
64 64 match 'my/account/destroy', :controller => 'my', :action => 'destroy', :via => [:get, :post]
65 65 match 'my/page', :controller => 'my', :action => 'page', :via => :get
66 66 match 'my', :controller => 'my', :action => 'index', :via => :get # Redirects to my/page
67 67 match 'my/reset_rss_key', :controller => 'my', :action => 'reset_rss_key', :via => :post
68 68 match 'my/reset_api_key', :controller => 'my', :action => 'reset_api_key', :via => :post
69 69 match 'my/password', :controller => 'my', :action => 'password', :via => [:get, :post]
70 70 match 'my/page_layout', :controller => 'my', :action => 'page_layout', :via => :get
71 71 match 'my/add_block', :controller => 'my', :action => 'add_block', :via => :post
72 72 match 'my/remove_block', :controller => 'my', :action => 'remove_block', :via => :post
73 73 match 'my/order_blocks', :controller => 'my', :action => 'order_blocks', :via => :post
74 74
75 75 resources :users
76 76 match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
77 77 match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
78 78 match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
79 79
80 80 match 'watchers/new', :controller=> 'watchers', :action => 'new', :via => :get
81 81 match 'watchers', :controller=> 'watchers', :action => 'create', :via => :post
82 82 match 'watchers/append', :controller=> 'watchers', :action => 'append', :via => :post
83 83 match 'watchers/destroy', :controller=> 'watchers', :action => 'destroy', :via => :post
84 84 match 'watchers/watch', :controller=> 'watchers', :action => 'watch', :via => :post
85 85 match 'watchers/unwatch', :controller=> 'watchers', :action => 'unwatch', :via => :post
86 86 match 'watchers/autocomplete_for_user', :controller=> 'watchers', :action => 'autocomplete_for_user', :via => :get
87 87
88 88 match 'projects/:id/settings/:tab', :to => "projects#settings"
89 89
90 90 resources :projects do
91 91 member do
92 92 get 'settings'
93 93 post 'modules'
94 94 post 'archive'
95 95 post 'unarchive'
96 96 post 'close'
97 97 post 'reopen'
98 98 match 'copy', :via => [:get, :post]
99 99 end
100 100
101 101 resources :memberships, :shallow => true, :controller => 'members', :only => [:index, :show, :new, :create, :update, :destroy] do
102 102 collection do
103 103 get 'autocomplete'
104 104 end
105 105 end
106 106
107 107 resource :enumerations, :controller => 'project_enumerations', :only => [:update, :destroy]
108 108
109 109 match 'issues/:copy_from/copy', :to => 'issues#new'
110 110 resources :issues, :only => [:index, :new, :create] do
111 111 resources :time_entries, :controller => 'timelog' do
112 112 collection do
113 113 get 'report'
114 114 end
115 115 end
116 116 end
117 117 # issue form update
118 118 match 'issues/new', :controller => 'issues', :action => 'new', :via => [:put, :post], :as => 'issue_form'
119 119
120 120 resources :files, :only => [:index, :new, :create]
121 121
122 122 resources :versions, :except => [:index, :show, :edit, :update, :destroy] do
123 123 collection do
124 124 put 'close_completed'
125 125 end
126 126 end
127 127 match 'versions.:format', :to => 'versions#index'
128 128 match 'roadmap', :to => 'versions#index', :format => false
129 129 match 'versions', :to => 'versions#index'
130 130
131 131 resources :news, :except => [:show, :edit, :update, :destroy]
132 132 resources :time_entries, :controller => 'timelog' do
133 133 get 'report', :on => :collection
134 134 end
135 135 resources :queries, :only => [:new, :create]
136 136 resources :issue_categories, :shallow => true
137 137 resources :documents, :except => [:show, :edit, :update, :destroy]
138 138 resources :boards
139 139 resources :repositories, :shallow => true, :except => [:index, :show] do
140 140 member do
141 141 match 'committers', :via => [:get, :post]
142 142 end
143 143 end
144 144
145 145 match 'wiki/index', :controller => 'wiki', :action => 'index', :via => :get
146 146 resources :wiki, :except => [:index, :new, :create] do
147 147 member do
148 148 get 'rename'
149 149 post 'rename'
150 150 get 'history'
151 151 get 'diff'
152 152 match 'preview', :via => [:post, :put]
153 153 post 'protect'
154 154 post 'add_attachment'
155 155 end
156 156 collection do
157 157 get 'export'
158 158 get 'date_index'
159 159 end
160 160 end
161 161 match 'wiki', :controller => 'wiki', :action => 'show', :via => :get
162 162 get 'wiki/:id/:version', :to => 'wiki#show'
163 163 delete 'wiki/:id/:version', :to => 'wiki#destroy_version'
164 164 get 'wiki/:id/:version/annotate', :to => 'wiki#annotate'
165 165 get 'wiki/:id/:version/diff', :to => 'wiki#diff'
166 166 end
167 167
168 168 resources :issues do
169 169 collection do
170 170 match 'bulk_edit', :via => [:get, :post]
171 171 post 'bulk_update'
172 172 end
173 173 resources :time_entries, :controller => 'timelog' do
174 174 collection do
175 175 get 'report'
176 176 end
177 177 end
178 178 resources :relations, :shallow => true, :controller => 'issue_relations', :only => [:index, :show, :create, :destroy]
179 179 end
180 180 match '/issues', :controller => 'issues', :action => 'destroy', :via => :delete
181 181
182 182 resources :queries, :except => [:show]
183 183
184 184 resources :news, :only => [:index, :show, :edit, :update, :destroy]
185 185 match '/news/:id/comments', :to => 'comments#create', :via => :post
186 186 match '/news/:id/comments/:comment_id', :to => 'comments#destroy', :via => :delete
187 187
188 188 resources :versions, :only => [:show, :edit, :update, :destroy] do
189 189 post 'status_by', :on => :member
190 190 end
191 191
192 192 resources :documents, :only => [:show, :edit, :update, :destroy] do
193 193 post 'add_attachment', :on => :member
194 194 end
195 195
196 196 match '/time_entries/context_menu', :to => 'context_menus#time_entries', :as => :time_entries_context_menu
197 197
198 198 resources :time_entries, :controller => 'timelog', :except => :destroy do
199 199 collection do
200 200 get 'report'
201 201 get 'bulk_edit'
202 202 post 'bulk_update'
203 203 end
204 204 end
205 205 match '/time_entries/:id', :to => 'timelog#destroy', :via => :delete, :id => /\d+/
206 206 # TODO: delete /time_entries for bulk deletion
207 207 match '/time_entries/destroy', :to => 'timelog#destroy', :via => :delete
208 208
209 209 # TODO: port to be part of the resources route(s)
210 210 match 'projects/:id/settings/:tab', :to => 'projects#settings', :via => :get
211 211
212 212 get 'projects/:id/activity', :to => 'activities#index'
213 213 get 'projects/:id/activity.:format', :to => 'activities#index'
214 214 get 'activity', :to => 'activities#index'
215 215
216 216 # repositories routes
217 217 get 'projects/:id/repository/:repository_id/statistics', :to => 'repositories#stats'
218 218 get 'projects/:id/repository/:repository_id/graph', :to => 'repositories#graph'
219 219
220 220 get 'projects/:id/repository/:repository_id/changes(/*path(.:ext))',
221 221 :to => 'repositories#changes'
222 222
223 223 get 'projects/:id/repository/:repository_id/revisions/:rev', :to => 'repositories#revision'
224 224 get 'projects/:id/repository/:repository_id/revision', :to => 'repositories#revision'
225 225 post 'projects/:id/repository/:repository_id/revisions/:rev/issues', :to => 'repositories#add_related_issue'
226 226 delete 'projects/:id/repository/:repository_id/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
227 227 get 'projects/:id/repository/:repository_id/revisions', :to => 'repositories#revisions'
228 228 get 'projects/:id/repository/:repository_id/revisions/:rev/:action(/*path(.:ext))',
229 229 :controller => 'repositories',
230 230 :format => false,
231 231 :constraints => {
232 232 :action => /(browse|show|entry|raw|annotate|diff)/,
233 233 :rev => /[a-z0-9\.\-_]+/
234 234 }
235 235
236 236 get 'projects/:id/repository/statistics', :to => 'repositories#stats'
237 237 get 'projects/:id/repository/graph', :to => 'repositories#graph'
238 238
239 239 get 'projects/:id/repository/changes(/*path(.:ext))',
240 240 :to => 'repositories#changes'
241 241
242 242 get 'projects/:id/repository/revisions', :to => 'repositories#revisions'
243 243 get 'projects/:id/repository/revisions/:rev', :to => 'repositories#revision'
244 244 get 'projects/:id/repository/revision', :to => 'repositories#revision'
245 245 post 'projects/:id/repository/revisions/:rev/issues', :to => 'repositories#add_related_issue'
246 246 delete 'projects/:id/repository/revisions/:rev/issues/:issue_id', :to => 'repositories#remove_related_issue'
247 247 get 'projects/:id/repository/revisions/:rev/:action(/*path(.:ext))',
248 248 :controller => 'repositories',
249 249 :format => false,
250 250 :constraints => {
251 251 :action => /(browse|show|entry|raw|annotate|diff)/,
252 252 :rev => /[a-z0-9\.\-_]+/
253 253 }
254 254 get 'projects/:id/repository/:repository_id/:action(/*path(.:ext))',
255 255 :controller => 'repositories',
256 256 :action => /(browse|show|entry|raw|changes|annotate|diff)/
257 257 get 'projects/:id/repository/:action(/*path(.:ext))',
258 258 :controller => 'repositories',
259 259 :action => /(browse|show|entry|raw|changes|annotate|diff)/
260 260
261 261 get 'projects/:id/repository/:repository_id', :to => 'repositories#show', :path => nil
262 262 get 'projects/:id/repository', :to => 'repositories#show', :path => nil
263 263
264 264 # additional routes for having the file name at the end of url
265 265 match 'attachments/:id/:filename', :controller => 'attachments', :action => 'show', :id => /\d+/, :filename => /.*/, :via => :get
266 266 match 'attachments/download/:id/:filename', :controller => 'attachments', :action => 'download', :id => /\d+/, :filename => /.*/, :via => :get
267 267 match 'attachments/download/:id', :controller => 'attachments', :action => 'download', :id => /\d+/, :via => :get
268 268 match 'attachments/thumbnail/:id(/:size)', :controller => 'attachments', :action => 'thumbnail', :id => /\d+/, :via => :get, :size => /\d+/
269 269 resources :attachments, :only => [:show, :destroy]
270 270
271 271 resources :groups do
272 272 member do
273 273 get 'autocomplete_for_user'
274 274 end
275 275 end
276 276
277 277 match 'groups/:id/users', :controller => 'groups', :action => 'add_users', :id => /\d+/, :via => :post, :as => 'group_users'
278 278 match 'groups/:id/users/:user_id', :controller => 'groups', :action => 'remove_user', :id => /\d+/, :via => :delete, :as => 'group_user'
279 279 match 'groups/destroy_membership/:id', :controller => 'groups', :action => 'destroy_membership', :id => /\d+/, :via => :post
280 280 match 'groups/edit_membership/:id', :controller => 'groups', :action => 'edit_membership', :id => /\d+/, :via => :post
281 281
282 282 resources :trackers, :except => :show do
283 283 collection do
284 284 match 'fields', :via => [:get, :post]
285 285 end
286 286 end
287 287 resources :issue_statuses, :except => :show do
288 288 collection do
289 289 post 'update_issue_done_ratio'
290 290 end
291 291 end
292 292 resources :custom_fields, :except => :show
293 293 resources :roles do
294 294 collection do
295 295 match 'permissions', :via => [:get, :post]
296 296 end
297 297 end
298 298 resources :enumerations, :except => :show
299 299 match 'enumerations/:type', :to => 'enumerations#index', :via => :get
300 300
301 301 get 'projects/:id/search', :controller => 'search', :action => 'index'
302 302 get 'search', :controller => 'search', :action => 'index'
303 303
304 304 match 'mail_handler', :controller => 'mail_handler', :action => 'index', :via => :post
305 305
306 306 match 'admin', :controller => 'admin', :action => 'index', :via => :get
307 307 match 'admin/projects', :controller => 'admin', :action => 'projects', :via => :get
308 308 match 'admin/plugins', :controller => 'admin', :action => 'plugins', :via => :get
309 309 match 'admin/info', :controller => 'admin', :action => 'info', :via => :get
310 310 match 'admin/test_email', :controller => 'admin', :action => 'test_email', :via => :get
311 311 match 'admin/default_configuration', :controller => 'admin', :action => 'default_configuration', :via => :post
312 312
313 313 resources :auth_sources do
314 314 member do
315 get 'test_connection'
315 get 'test_connection', :as => 'try_connection'
316 316 end
317 317 end
318 318
319 319 match 'workflows', :controller => 'workflows', :action => 'index', :via => :get
320 320 match 'workflows/edit', :controller => 'workflows', :action => 'edit', :via => [:get, :post]
321 321 match 'workflows/permissions', :controller => 'workflows', :action => 'permissions', :via => [:get, :post]
322 322 match 'workflows/copy', :controller => 'workflows', :action => 'copy', :via => [:get, :post]
323 323 match 'settings', :controller => 'settings', :action => 'index', :via => :get
324 324 match 'settings/edit', :controller => 'settings', :action => 'edit', :via => [:get, :post]
325 325 match 'settings/plugin/:id', :controller => 'settings', :action => 'plugin', :via => [:get, :post]
326 326
327 327 match 'sys/projects', :to => 'sys#projects', :via => :get
328 328 match 'sys/projects/:id/repository', :to => 'sys#create_project_repository', :via => :post
329 329 match 'sys/fetch_changesets', :to => 'sys#fetch_changesets', :via => :get
330 330
331 331 match 'uploads', :to => 'attachments#upload', :via => :post
332 332
333 333 get 'robots.txt', :to => 'welcome#robots'
334 334
335 335 Dir.glob File.expand_path("plugins/*", Rails.root) do |plugin_dir|
336 336 file = File.join(plugin_dir, "config/routes.rb")
337 337 if File.exists?(file)
338 338 begin
339 339 instance_eval File.read(file)
340 340 rescue Exception => e
341 341 puts "An error occurred while loading the routes definition of #{File.basename(plugin_dir)} plugin (#{file}): #{e.message}."
342 342 exit 1
343 343 end
344 344 end
345 345 end
346 346 end
@@ -1,494 +1,505
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 #require 'shoulda'
19 19 ENV["RAILS_ENV"] = "test"
20 20 require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
21 21 require 'rails/test_help'
22 22 require Rails.root.join('test', 'mocks', 'open_id_authentication_mock.rb').to_s
23 23
24 24 require File.expand_path(File.dirname(__FILE__) + '/object_helpers')
25 25 include ObjectHelpers
26 26
27 27 class ActiveSupport::TestCase
28 28 include ActionDispatch::TestProcess
29 29
30 30 # Transactional fixtures accelerate your tests by wrapping each test method
31 31 # in a transaction that's rolled back on completion. This ensures that the
32 32 # test database remains unchanged so your fixtures don't have to be reloaded
33 33 # between every test method. Fewer database queries means faster tests.
34 34 #
35 35 # Read Mike Clark's excellent walkthrough at
36 36 # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
37 37 #
38 38 # Every Active Record database supports transactions except MyISAM tables
39 39 # in MySQL. Turn off transactional fixtures in this case; however, if you
40 40 # don't care one way or the other, switching from MyISAM to InnoDB tables
41 41 # is recommended.
42 42 self.use_transactional_fixtures = true
43 43
44 44 # Instantiated fixtures are slow, but give you @david where otherwise you
45 45 # would need people(:david). If you don't want to migrate your existing
46 46 # test cases which use the @david style and don't mind the speed hit (each
47 47 # instantiated fixtures translates to a database query per test method),
48 48 # then set this back to true.
49 49 self.use_instantiated_fixtures = false
50 50
51 51 # Add more helper methods to be used by all tests here...
52 52
53 53 def log_user(login, password)
54 54 User.anonymous
55 55 get "/login"
56 56 assert_equal nil, session[:user_id]
57 57 assert_response :success
58 58 assert_template "account/login"
59 59 post "/login", :username => login, :password => password
60 60 assert_equal login, User.find(session[:user_id]).login
61 61 end
62 62
63 63 def uploaded_test_file(name, mime)
64 64 fixture_file_upload("files/#{name}", mime, true)
65 65 end
66 66
67 67 def credentials(user, password=nil)
68 68 {'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password || user)}
69 69 end
70 70
71 71 # Mock out a file
72 72 def self.mock_file
73 73 file = 'a_file.png'
74 74 file.stubs(:size).returns(32)
75 75 file.stubs(:original_filename).returns('a_file.png')
76 76 file.stubs(:content_type).returns('image/png')
77 77 file.stubs(:read).returns(false)
78 78 file
79 79 end
80 80
81 81 def mock_file
82 82 self.class.mock_file
83 83 end
84 84
85 85 def mock_file_with_options(options={})
86 86 file = ''
87 87 file.stubs(:size).returns(32)
88 88 original_filename = options[:original_filename] || nil
89 89 file.stubs(:original_filename).returns(original_filename)
90 90 content_type = options[:content_type] || nil
91 91 file.stubs(:content_type).returns(content_type)
92 92 file.stubs(:read).returns(false)
93 93 file
94 94 end
95 95
96 96 # Use a temporary directory for attachment related tests
97 97 def set_tmp_attachments_directory
98 98 Dir.mkdir "#{Rails.root}/tmp/test" unless File.directory?("#{Rails.root}/tmp/test")
99 99 unless File.directory?("#{Rails.root}/tmp/test/attachments")
100 100 Dir.mkdir "#{Rails.root}/tmp/test/attachments"
101 101 end
102 102 Attachment.storage_path = "#{Rails.root}/tmp/test/attachments"
103 103 end
104 104
105 105 def set_fixtures_attachments_directory
106 106 Attachment.storage_path = "#{Rails.root}/test/fixtures/files"
107 107 end
108 108
109 109 def with_settings(options, &block)
110 110 saved_settings = options.keys.inject({}) {|h, k| h[k] = Setting[k].is_a?(Symbol) ? Setting[k] : Setting[k].dup; h}
111 111 options.each {|k, v| Setting[k] = v}
112 112 yield
113 113 ensure
114 114 saved_settings.each {|k, v| Setting[k] = v} if saved_settings
115 115 end
116 116
117 117 # Yields the block with user as the current user
118 118 def with_current_user(user, &block)
119 119 saved_user = User.current
120 120 User.current = user
121 121 yield
122 122 ensure
123 123 User.current = saved_user
124 124 end
125 125
126 126 def change_user_password(login, new_password)
127 127 user = User.first(:conditions => {:login => login})
128 128 user.password, user.password_confirmation = new_password, new_password
129 129 user.save!
130 130 end
131 131
132 132 def self.ldap_configured?
133 133 @test_ldap = Net::LDAP.new(:host => '127.0.0.1', :port => 389)
134 134 return @test_ldap.bind
135 135 rescue Exception => e
136 136 # LDAP is not listening
137 137 return nil
138 138 end
139 139
140 140 def self.convert_installed?
141 141 Redmine::Thumbnail.convert_available?
142 142 end
143 143
144 144 # Returns the path to the test +vendor+ repository
145 145 def self.repository_path(vendor)
146 146 Rails.root.join("tmp/test/#{vendor.downcase}_repository").to_s
147 147 end
148 148
149 149 # Returns the url of the subversion test repository
150 150 def self.subversion_repository_url
151 151 path = repository_path('subversion')
152 152 path = '/' + path unless path.starts_with?('/')
153 153 "file://#{path}"
154 154 end
155 155
156 156 # Returns true if the +vendor+ test repository is configured
157 157 def self.repository_configured?(vendor)
158 158 File.directory?(repository_path(vendor))
159 159 end
160 160
161 161 def repository_path_hash(arr)
162 162 hs = {}
163 163 hs[:path] = arr.join("/")
164 164 hs[:param] = arr.join("/")
165 165 hs
166 166 end
167 167
168 168 def assert_save(object)
169 169 saved = object.save
170 170 message = "#{object.class} could not be saved"
171 171 errors = object.errors.full_messages.map {|m| "- #{m}"}
172 172 message << ":\n#{errors.join("\n")}" if errors.any?
173 173 assert_equal true, saved, message
174 174 end
175 175
176 176 def assert_error_tag(options={})
177 177 assert_tag({:attributes => { :id => 'errorExplanation' }}.merge(options))
178 178 end
179 179
180 180 def assert_include(expected, s, message=nil)
181 181 assert s.include?(expected), (message || "\"#{expected}\" not found in \"#{s}\"")
182 182 end
183 183
184 184 def assert_not_include(expected, s)
185 185 assert !s.include?(expected), "\"#{expected}\" found in \"#{s}\""
186 186 end
187 187
188 188 def assert_select_in(text, *args, &block)
189 189 d = HTML::Document.new(CGI::unescapeHTML(String.new(text))).root
190 190 assert_select(d, *args, &block)
191 191 end
192 192
193 193 def assert_mail_body_match(expected, mail)
194 194 if expected.is_a?(String)
195 195 assert_include expected, mail_body(mail)
196 196 else
197 197 assert_match expected, mail_body(mail)
198 198 end
199 199 end
200 200
201 201 def assert_mail_body_no_match(expected, mail)
202 202 if expected.is_a?(String)
203 203 assert_not_include expected, mail_body(mail)
204 204 else
205 205 assert_no_match expected, mail_body(mail)
206 206 end
207 207 end
208 208
209 209 def mail_body(mail)
210 210 mail.parts.first.body.encoded
211 211 end
212 212
213 213 # Shoulda macros
214 214 def self.should_render_404
215 215 should_respond_with :not_found
216 216 should_render_template 'common/error'
217 217 end
218 218
219 219 def self.should_have_before_filter(expected_method, options = {})
220 220 should_have_filter('before', expected_method, options)
221 221 end
222 222
223 223 def self.should_have_after_filter(expected_method, options = {})
224 224 should_have_filter('after', expected_method, options)
225 225 end
226 226
227 227 def self.should_have_filter(filter_type, expected_method, options)
228 228 description = "have #{filter_type}_filter :#{expected_method}"
229 229 description << " with #{options.inspect}" unless options.empty?
230 230
231 231 should description do
232 232 klass = "action_controller/filters/#{filter_type}_filter".classify.constantize
233 233 expected = klass.new(:filter, expected_method.to_sym, options)
234 234 assert_equal 1, @controller.class.filter_chain.select { |filter|
235 235 filter.method == expected.method && filter.kind == expected.kind &&
236 236 filter.options == expected.options && filter.class == expected.class
237 237 }.size
238 238 end
239 239 end
240 240
241 241 # Test that a request allows the three types of API authentication
242 242 #
243 243 # * HTTP Basic with username and password
244 244 # * HTTP Basic with an api key for the username
245 245 # * Key based with the key=X parameter
246 246 #
247 247 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
248 248 # @param [String] url the request url
249 249 # @param [optional, Hash] parameters additional request parameters
250 250 # @param [optional, Hash] options additional options
251 251 # @option options [Symbol] :success_code Successful response code (:success)
252 252 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
253 253 def self.should_allow_api_authentication(http_method, url, parameters={}, options={})
254 254 should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters, options)
255 255 should_allow_http_basic_auth_with_key(http_method, url, parameters, options)
256 256 should_allow_key_based_auth(http_method, url, parameters, options)
257 257 end
258 258
259 259 # Test that a request allows the username and password for HTTP BASIC
260 260 #
261 261 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
262 262 # @param [String] url the request url
263 263 # @param [optional, Hash] parameters additional request parameters
264 264 # @param [optional, Hash] options additional options
265 265 # @option options [Symbol] :success_code Successful response code (:success)
266 266 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
267 267 def self.should_allow_http_basic_auth_with_username_and_password(http_method, url, parameters={}, options={})
268 268 success_code = options[:success_code] || :success
269 269 failure_code = options[:failure_code] || :unauthorized
270 270
271 271 context "should allow http basic auth using a username and password for #{http_method} #{url}" do
272 272 context "with a valid HTTP authentication" do
273 273 setup do
274 274 @user = User.generate! do |user|
275 275 user.admin = true
276 276 user.password = 'my_password'
277 277 end
278 278 send(http_method, url, parameters, credentials(@user.login, 'my_password'))
279 279 end
280 280
281 281 should_respond_with success_code
282 282 should_respond_with_content_type_based_on_url(url)
283 283 should "login as the user" do
284 284 assert_equal @user, User.current
285 285 end
286 286 end
287 287
288 288 context "with an invalid HTTP authentication" do
289 289 setup do
290 290 @user = User.generate!
291 291 send(http_method, url, parameters, credentials(@user.login, 'wrong_password'))
292 292 end
293 293
294 294 should_respond_with failure_code
295 295 should_respond_with_content_type_based_on_url(url)
296 296 should "not login as the user" do
297 297 assert_equal User.anonymous, User.current
298 298 end
299 299 end
300 300
301 301 context "without credentials" do
302 302 setup do
303 303 send(http_method, url, parameters)
304 304 end
305 305
306 306 should_respond_with failure_code
307 307 should_respond_with_content_type_based_on_url(url)
308 308 should "include_www_authenticate_header" do
309 309 assert @controller.response.headers.has_key?('WWW-Authenticate')
310 310 end
311 311 end
312 312 end
313 313 end
314 314
315 315 # Test that a request allows the API key with HTTP BASIC
316 316 #
317 317 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
318 318 # @param [String] url the request url
319 319 # @param [optional, Hash] parameters additional request parameters
320 320 # @param [optional, Hash] options additional options
321 321 # @option options [Symbol] :success_code Successful response code (:success)
322 322 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
323 323 def self.should_allow_http_basic_auth_with_key(http_method, url, parameters={}, options={})
324 324 success_code = options[:success_code] || :success
325 325 failure_code = options[:failure_code] || :unauthorized
326 326
327 327 context "should allow http basic auth with a key for #{http_method} #{url}" do
328 328 context "with a valid HTTP authentication using the API token" do
329 329 setup do
330 330 @user = User.generate! do |user|
331 331 user.admin = true
332 332 end
333 333 @token = Token.create!(:user => @user, :action => 'api')
334 334 send(http_method, url, parameters, credentials(@token.value, 'X'))
335 335 end
336 336 should_respond_with success_code
337 337 should_respond_with_content_type_based_on_url(url)
338 338 should_be_a_valid_response_string_based_on_url(url)
339 339 should "login as the user" do
340 340 assert_equal @user, User.current
341 341 end
342 342 end
343 343
344 344 context "with an invalid HTTP authentication" do
345 345 setup do
346 346 @user = User.generate!
347 347 @token = Token.create!(:user => @user, :action => 'feeds')
348 348 send(http_method, url, parameters, credentials(@token.value, 'X'))
349 349 end
350 350 should_respond_with failure_code
351 351 should_respond_with_content_type_based_on_url(url)
352 352 should "not login as the user" do
353 353 assert_equal User.anonymous, User.current
354 354 end
355 355 end
356 356 end
357 357 end
358 358
359 359 # Test that a request allows full key authentication
360 360 #
361 361 # @param [Symbol] http_method the HTTP method for request (:get, :post, :put, :delete)
362 362 # @param [String] url the request url, without the key=ZXY parameter
363 363 # @param [optional, Hash] parameters additional request parameters
364 364 # @param [optional, Hash] options additional options
365 365 # @option options [Symbol] :success_code Successful response code (:success)
366 366 # @option options [Symbol] :failure_code Failure response code (:unauthorized)
367 367 def self.should_allow_key_based_auth(http_method, url, parameters={}, options={})
368 368 success_code = options[:success_code] || :success
369 369 failure_code = options[:failure_code] || :unauthorized
370 370
371 371 context "should allow key based auth using key=X for #{http_method} #{url}" do
372 372 context "with a valid api token" do
373 373 setup do
374 374 @user = User.generate! do |user|
375 375 user.admin = true
376 376 end
377 377 @token = Token.create!(:user => @user, :action => 'api')
378 378 # Simple url parse to add on ?key= or &key=
379 379 request_url = if url.match(/\?/)
380 380 url + "&key=#{@token.value}"
381 381 else
382 382 url + "?key=#{@token.value}"
383 383 end
384 384 send(http_method, request_url, parameters)
385 385 end
386 386 should_respond_with success_code
387 387 should_respond_with_content_type_based_on_url(url)
388 388 should_be_a_valid_response_string_based_on_url(url)
389 389 should "login as the user" do
390 390 assert_equal @user, User.current
391 391 end
392 392 end
393 393
394 394 context "with an invalid api token" do
395 395 setup do
396 396 @user = User.generate! do |user|
397 397 user.admin = true
398 398 end
399 399 @token = Token.create!(:user => @user, :action => 'feeds')
400 400 # Simple url parse to add on ?key= or &key=
401 401 request_url = if url.match(/\?/)
402 402 url + "&key=#{@token.value}"
403 403 else
404 404 url + "?key=#{@token.value}"
405 405 end
406 406 send(http_method, request_url, parameters)
407 407 end
408 408 should_respond_with failure_code
409 409 should_respond_with_content_type_based_on_url(url)
410 410 should "not login as the user" do
411 411 assert_equal User.anonymous, User.current
412 412 end
413 413 end
414 414 end
415 415
416 416 context "should allow key based auth using X-Redmine-API-Key header for #{http_method} #{url}" do
417 417 setup do
418 418 @user = User.generate! do |user|
419 419 user.admin = true
420 420 end
421 421 @token = Token.create!(:user => @user, :action => 'api')
422 422 send(http_method, url, parameters, {'X-Redmine-API-Key' => @token.value.to_s})
423 423 end
424 424 should_respond_with success_code
425 425 should_respond_with_content_type_based_on_url(url)
426 426 should_be_a_valid_response_string_based_on_url(url)
427 427 should "login as the user" do
428 428 assert_equal @user, User.current
429 429 end
430 430 end
431 431 end
432 432
433 433 # Uses should_respond_with_content_type based on what's in the url:
434 434 #
435 435 # '/project/issues.xml' => should_respond_with_content_type :xml
436 436 # '/project/issues.json' => should_respond_with_content_type :json
437 437 #
438 438 # @param [String] url Request
439 439 def self.should_respond_with_content_type_based_on_url(url)
440 440 case
441 441 when url.match(/xml/i)
442 442 should "respond with XML" do
443 443 assert_equal 'application/xml', @response.content_type
444 444 end
445 445 when url.match(/json/i)
446 446 should "respond with JSON" do
447 447 assert_equal 'application/json', @response.content_type
448 448 end
449 449 else
450 450 raise "Unknown content type for should_respond_with_content_type_based_on_url: #{url}"
451 451 end
452 452 end
453 453
454 454 # Uses the url to assert which format the response should be in
455 455 #
456 456 # '/project/issues.xml' => should_be_a_valid_xml_string
457 457 # '/project/issues.json' => should_be_a_valid_json_string
458 458 #
459 459 # @param [String] url Request
460 460 def self.should_be_a_valid_response_string_based_on_url(url)
461 461 case
462 462 when url.match(/xml/i)
463 463 should_be_a_valid_xml_string
464 464 when url.match(/json/i)
465 465 should_be_a_valid_json_string
466 466 else
467 467 raise "Unknown content type for should_be_a_valid_response_based_on_url: #{url}"
468 468 end
469 469 end
470 470
471 471 # Checks that the response is a valid JSON string
472 472 def self.should_be_a_valid_json_string
473 473 should "be a valid JSON string (or empty)" do
474 474 assert(response.body.blank? || ActiveSupport::JSON.decode(response.body))
475 475 end
476 476 end
477 477
478 478 # Checks that the response is a valid XML string
479 479 def self.should_be_a_valid_xml_string
480 480 should "be a valid XML string" do
481 481 assert REXML::Document.new(response.body)
482 482 end
483 483 end
484 484
485 485 def self.should_respond_with(status)
486 486 should "respond with #{status}" do
487 487 assert_response status
488 488 end
489 489 end
490 490 end
491 491
492 492 # Simple module to "namespace" all of the API tests
493 493 module ApiTest
494 494 end
495
496 # URL helpers do not work with config.threadsafe!
497 # https://github.com/rspec/rspec-rails/issues/476#issuecomment-4705454
498 ActionView::TestCase::TestController.instance_eval do
499 helper Rails.application.routes.url_helpers
500 end
501 ActionView::TestCase::TestController.class_eval do
502 def _routes
503 Rails.application.routes
504 end
505 end
@@ -1,1163 +1,1164
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2012 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 require File.expand_path('../../../test_helper', __FILE__)
21 21
22 22 class ApplicationHelperTest < ActionView::TestCase
23 23 include ERB::Util
24 include Rails.application.routes.url_helpers
24 25
25 26 fixtures :projects, :roles, :enabled_modules, :users,
26 27 :repositories, :changesets,
27 28 :trackers, :issue_statuses, :issues, :versions, :documents,
28 29 :wikis, :wiki_pages, :wiki_contents,
29 30 :boards, :messages, :news,
30 31 :attachments, :enumerations
31 32
32 33 def setup
33 34 super
34 35 set_tmp_attachments_directory
35 36 end
36 37
37 38 context "#link_to_if_authorized" do
38 39 context "authorized user" do
39 40 should "be tested"
40 41 end
41 42
42 43 context "unauthorized user" do
43 44 should "be tested"
44 45 end
45 46
46 47 should "allow using the :controller and :action for the target link" do
47 48 User.current = User.find_by_login('admin')
48 49
49 50 @project = Issue.first.project # Used by helper
50 51 response = link_to_if_authorized("By controller/action",
51 52 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
52 53 assert_match /href/, response
53 54 end
54 55
55 56 end
56 57
57 58 def test_auto_links
58 59 to_test = {
59 60 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
60 61 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
61 62 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
62 63 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
63 64 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
64 65 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
65 66 '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>.',
66 67 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
67 68 '(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>)',
68 69 '(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>)',
69 70 '(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>).',
70 71 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
71 72 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
72 73 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
73 74 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
74 75 '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>',
75 76 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
76 77 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
77 78 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
78 79 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
79 80 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
80 81 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
81 82 # two exclamation marks
82 83 '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>',
83 84 # escaping
84 85 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo&quot;bar</a>',
85 86 # wrap in angle brackets
86 87 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
87 88 }
88 89 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
89 90 end
90 91
91 92 if 'ruby'.respond_to?(:encoding)
92 93 def test_auto_links_with_non_ascii_characters
93 94 to_test = {
94 95 'http://foo.bar/тСст' => '<a class="external" href="http://foo.bar/тСст">http://foo.bar/тСст</a>'
95 96 }
96 97 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
97 98 end
98 99 else
99 100 puts 'Skipping test_auto_links_with_non_ascii_characters, unsupported ruby version'
100 101 end
101 102
102 103 def test_auto_mailto
103 104 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
104 105 textilizable('test@foo.bar')
105 106 end
106 107
107 108 def test_inline_images
108 109 to_test = {
109 110 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
110 111 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
111 112 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
112 113 '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="" />',
113 114 '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" />',
114 115 '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;" />',
115 116 }
116 117 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
117 118 end
118 119
119 120 def test_inline_images_inside_tags
120 121 raw = <<-RAW
121 122 h1. !foo.png! Heading
122 123
123 124 Centered image:
124 125
125 126 p=. !bar.gif!
126 127 RAW
127 128
128 129 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
129 130 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
130 131 end
131 132
132 133 def test_attached_images
133 134 to_test = {
134 135 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
135 136 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
136 137 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
137 138 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />',
138 139 # link image
139 140 '!logo.gif!:http://foo.bar/' => '<a href="http://foo.bar/"><img src="/attachments/download/3" title="This is a logo" alt="This is a logo" /></a>',
140 141 }
141 142 attachments = Attachment.find(:all)
142 143 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
143 144 end
144 145
145 146 def test_attached_images_filename_extension
146 147 set_tmp_attachments_directory
147 148 a1 = Attachment.new(
148 149 :container => Issue.find(1),
149 150 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
150 151 :author => User.find(1))
151 152 assert a1.save
152 153 assert_equal "testtest.JPG", a1.filename
153 154 assert_equal "image/jpeg", a1.content_type
154 155 assert a1.image?
155 156
156 157 a2 = Attachment.new(
157 158 :container => Issue.find(1),
158 159 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
159 160 :author => User.find(1))
160 161 assert a2.save
161 162 assert_equal "testtest.jpeg", a2.filename
162 163 assert_equal "image/jpeg", a2.content_type
163 164 assert a2.image?
164 165
165 166 a3 = Attachment.new(
166 167 :container => Issue.find(1),
167 168 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
168 169 :author => User.find(1))
169 170 assert a3.save
170 171 assert_equal "testtest.JPE", a3.filename
171 172 assert_equal "image/jpeg", a3.content_type
172 173 assert a3.image?
173 174
174 175 a4 = Attachment.new(
175 176 :container => Issue.find(1),
176 177 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
177 178 :author => User.find(1))
178 179 assert a4.save
179 180 assert_equal "Testtest.BMP", a4.filename
180 181 assert_equal "image/x-ms-bmp", a4.content_type
181 182 assert a4.image?
182 183
183 184 to_test = {
184 185 'Inline image: !testtest.jpg!' =>
185 186 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '" alt="" />',
186 187 'Inline image: !testtest.jpeg!' =>
187 188 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
188 189 'Inline image: !testtest.jpe!' =>
189 190 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '" alt="" />',
190 191 'Inline image: !testtest.bmp!' =>
191 192 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '" alt="" />',
192 193 }
193 194
194 195 attachments = [a1, a2, a3, a4]
195 196 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
196 197 end
197 198
198 199 def test_attached_images_should_read_later
199 200 set_fixtures_attachments_directory
200 201 a1 = Attachment.find(16)
201 202 assert_equal "testfile.png", a1.filename
202 203 assert a1.readable?
203 204 assert (! a1.visible?(User.anonymous))
204 205 assert a1.visible?(User.find(2))
205 206 a2 = Attachment.find(17)
206 207 assert_equal "testfile.PNG", a2.filename
207 208 assert a2.readable?
208 209 assert (! a2.visible?(User.anonymous))
209 210 assert a2.visible?(User.find(2))
210 211 assert a1.created_on < a2.created_on
211 212
212 213 to_test = {
213 214 'Inline image: !testfile.png!' =>
214 215 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
215 216 'Inline image: !Testfile.PNG!' =>
216 217 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
217 218 }
218 219 attachments = [a1, a2]
219 220 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
220 221 set_tmp_attachments_directory
221 222 end
222 223
223 224 def test_textile_external_links
224 225 to_test = {
225 226 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
226 227 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
227 228 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
228 229 '"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>',
229 230 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
230 231 # no multiline link text
231 232 "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",
232 233 # mailto link
233 234 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
234 235 # two exclamation marks
235 236 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
236 237 # escaping
237 238 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
238 239 }
239 240 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
240 241 end
241 242
242 243 if 'ruby'.respond_to?(:encoding)
243 244 def test_textile_external_links_with_non_ascii_characters
244 245 to_test = {
245 246 'This is a "link":http://foo.bar/тСст' => 'This is a <a href="http://foo.bar/тСст" class="external">link</a>'
246 247 }
247 248 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
248 249 end
249 250 else
250 251 puts 'Skipping test_textile_external_links_with_non_ascii_characters, unsupported ruby version'
251 252 end
252 253
253 254 def test_redmine_links
254 255 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
255 256 :class => 'issue status-1 priority-4 priority-lowest overdue', :title => 'Error 281 when updating a recipe (New)')
256 257 note_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3, :anchor => 'note-14'},
257 258 :class => 'issue status-1 priority-4 priority-lowest overdue', :title => 'Error 281 when updating a recipe (New)')
258 259
259 260 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
260 261 :class => 'changeset', :title => 'My very first commit')
261 262 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
262 263 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
263 264
264 265 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
265 266 :class => 'document')
266 267
267 268 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
268 269 :class => 'version')
269 270
270 271 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
271 272
272 273 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
273 274
274 275 news_url = {:controller => 'news', :action => 'show', :id => 1}
275 276
276 277 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
277 278
278 279 source_url = '/projects/ecookbook/repository/entry/some/file'
279 280 source_url_with_rev = '/projects/ecookbook/repository/revisions/52/entry/some/file'
280 281 source_url_with_ext = '/projects/ecookbook/repository/entry/some/file.ext'
281 282 source_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/entry/some/file.ext'
282 283
283 284 export_url = '/projects/ecookbook/repository/raw/some/file'
284 285 export_url_with_rev = '/projects/ecookbook/repository/revisions/52/raw/some/file'
285 286 export_url_with_ext = '/projects/ecookbook/repository/raw/some/file.ext'
286 287 export_url_with_rev_and_ext = '/projects/ecookbook/repository/revisions/52/raw/some/file.ext'
287 288
288 289 to_test = {
289 290 # tickets
290 291 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
291 292 # ticket notes
292 293 '#3-14' => note_link,
293 294 '#3#note-14' => note_link,
294 295 # should not ignore leading zero
295 296 '#03' => '#03',
296 297 # changesets
297 298 'r1' => changeset_link,
298 299 'r1.' => "#{changeset_link}.",
299 300 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
300 301 'r1,r2' => "#{changeset_link},#{changeset_link2}",
301 302 # documents
302 303 'document#1' => document_link,
303 304 'document:"Test document"' => document_link,
304 305 # versions
305 306 'version#2' => version_link,
306 307 'version:1.0' => version_link,
307 308 'version:"1.0"' => version_link,
308 309 # source
309 310 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
310 311 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
311 312 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
312 313 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
313 314 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
314 315 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
315 316 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
316 317 'source:/some/file@52' => link_to('source:/some/file@52', source_url_with_rev, :class => 'source'),
317 318 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_rev_and_ext, :class => 'source'),
318 319 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url + "#L110", :class => 'source'),
319 320 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext + "#L110", :class => 'source'),
320 321 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url_with_rev + "#L110", :class => 'source'),
321 322 # export
322 323 'export:/some/file' => link_to('export:/some/file', export_url, :class => 'source download'),
323 324 'export:/some/file.ext' => link_to('export:/some/file.ext', export_url_with_ext, :class => 'source download'),
324 325 'export:/some/file@52' => link_to('export:/some/file@52', export_url_with_rev, :class => 'source download'),
325 326 'export:/some/file.ext@52' => link_to('export:/some/file.ext@52', export_url_with_rev_and_ext, :class => 'source download'),
326 327 # forum
327 328 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
328 329 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
329 330 # message
330 331 'message#4' => link_to('Post 2', message_url, :class => 'message'),
331 332 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
332 333 # news
333 334 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
334 335 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
335 336 # project
336 337 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
337 338 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
338 339 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
339 340 # not found
340 341 '#0123456789' => '#0123456789',
341 342 # invalid expressions
342 343 'source:' => 'source:',
343 344 # url hash
344 345 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
345 346 }
346 347 @project = Project.find(1)
347 348 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
348 349 end
349 350
350 351 def test_escaped_redmine_links_should_not_be_parsed
351 352 to_test = [
352 353 '#3.',
353 354 '#3-14.',
354 355 '#3#-note14.',
355 356 'r1',
356 357 'document#1',
357 358 'document:"Test document"',
358 359 'version#2',
359 360 'version:1.0',
360 361 'version:"1.0"',
361 362 'source:/some/file'
362 363 ]
363 364 @project = Project.find(1)
364 365 to_test.each { |text| assert_equal "<p>#{text}</p>", textilizable("!" + text), "#{text} failed" }
365 366 end
366 367
367 368 def test_cross_project_redmine_links
368 369 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
369 370 :class => 'source')
370 371
371 372 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
372 373 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
373 374
374 375 to_test = {
375 376 # documents
376 377 'document:"Test document"' => 'document:"Test document"',
377 378 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
378 379 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
379 380 # versions
380 381 'version:"1.0"' => 'version:"1.0"',
381 382 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
382 383 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
383 384 # changeset
384 385 'r2' => 'r2',
385 386 'ecookbook:r2' => changeset_link,
386 387 'invalid:r2' => 'invalid:r2',
387 388 # source
388 389 'source:/some/file' => 'source:/some/file',
389 390 'ecookbook:source:/some/file' => source_link,
390 391 'invalid:source:/some/file' => 'invalid:source:/some/file',
391 392 }
392 393 @project = Project.find(3)
393 394 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
394 395 end
395 396
396 397 def test_multiple_repositories_redmine_links
397 398 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
398 399 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
399 400 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
400 401 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
401 402
402 403 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
403 404 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
404 405 svn_changeset_link = link_to('svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
405 406 :class => 'changeset', :title => '')
406 407 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
407 408 :class => 'changeset', :title => '')
408 409
409 410 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
410 411 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
411 412
412 413 to_test = {
413 414 'r2' => changeset_link,
414 415 'svn1|r123' => svn_changeset_link,
415 416 'invalid|r123' => 'invalid|r123',
416 417 'commit:hg1|abcd' => hg_changeset_link,
417 418 'commit:invalid|abcd' => 'commit:invalid|abcd',
418 419 # source
419 420 'source:some/file' => source_link,
420 421 'source:hg1|some/file' => hg_source_link,
421 422 'source:invalid|some/file' => 'source:invalid|some/file',
422 423 }
423 424
424 425 @project = Project.find(1)
425 426 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
426 427 end
427 428
428 429 def test_cross_project_multiple_repositories_redmine_links
429 430 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
430 431 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
431 432 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
432 433 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
433 434
434 435 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
435 436 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
436 437 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
437 438 :class => 'changeset', :title => '')
438 439 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
439 440 :class => 'changeset', :title => '')
440 441
441 442 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
442 443 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
443 444
444 445 to_test = {
445 446 'ecookbook:r2' => changeset_link,
446 447 'ecookbook:svn1|r123' => svn_changeset_link,
447 448 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
448 449 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
449 450 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
450 451 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
451 452 # source
452 453 'ecookbook:source:some/file' => source_link,
453 454 'ecookbook:source:hg1|some/file' => hg_source_link,
454 455 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
455 456 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
456 457 }
457 458
458 459 @project = Project.find(3)
459 460 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
460 461 end
461 462
462 463 def test_redmine_links_git_commit
463 464 changeset_link = link_to('abcd',
464 465 {
465 466 :controller => 'repositories',
466 467 :action => 'revision',
467 468 :id => 'subproject1',
468 469 :rev => 'abcd',
469 470 },
470 471 :class => 'changeset', :title => 'test commit')
471 472 to_test = {
472 473 'commit:abcd' => changeset_link,
473 474 }
474 475 @project = Project.find(3)
475 476 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
476 477 assert r
477 478 c = Changeset.new(:repository => r,
478 479 :committed_on => Time.now,
479 480 :revision => 'abcd',
480 481 :scmid => 'abcd',
481 482 :comments => 'test commit')
482 483 assert( c.save )
483 484 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
484 485 end
485 486
486 487 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
487 488 def test_redmine_links_darcs_commit
488 489 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
489 490 {
490 491 :controller => 'repositories',
491 492 :action => 'revision',
492 493 :id => 'subproject1',
493 494 :rev => '123',
494 495 },
495 496 :class => 'changeset', :title => 'test commit')
496 497 to_test = {
497 498 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
498 499 }
499 500 @project = Project.find(3)
500 501 r = Repository::Darcs.create!(
501 502 :project => @project, :url => '/tmp/test/darcs',
502 503 :log_encoding => 'UTF-8')
503 504 assert r
504 505 c = Changeset.new(:repository => r,
505 506 :committed_on => Time.now,
506 507 :revision => '123',
507 508 :scmid => '20080308225258-98289-abcd456efg.gz',
508 509 :comments => 'test commit')
509 510 assert( c.save )
510 511 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
511 512 end
512 513
513 514 def test_redmine_links_mercurial_commit
514 515 changeset_link_rev = link_to('r123',
515 516 {
516 517 :controller => 'repositories',
517 518 :action => 'revision',
518 519 :id => 'subproject1',
519 520 :rev => '123' ,
520 521 },
521 522 :class => 'changeset', :title => 'test commit')
522 523 changeset_link_commit = link_to('abcd',
523 524 {
524 525 :controller => 'repositories',
525 526 :action => 'revision',
526 527 :id => 'subproject1',
527 528 :rev => 'abcd' ,
528 529 },
529 530 :class => 'changeset', :title => 'test commit')
530 531 to_test = {
531 532 'r123' => changeset_link_rev,
532 533 'commit:abcd' => changeset_link_commit,
533 534 }
534 535 @project = Project.find(3)
535 536 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
536 537 assert r
537 538 c = Changeset.new(:repository => r,
538 539 :committed_on => Time.now,
539 540 :revision => '123',
540 541 :scmid => 'abcd',
541 542 :comments => 'test commit')
542 543 assert( c.save )
543 544 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
544 545 end
545 546
546 547 def test_attachment_links
547 548 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
548 549 to_test = {
549 550 'attachment:error281.txt' => attachment_link
550 551 }
551 552 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
552 553 end
553 554
554 555 def test_wiki_links
555 556 to_test = {
556 557 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
557 558 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
558 559 # title content should be formatted
559 560 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
560 561 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;</a>',
561 562 # link with anchor
562 563 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
563 564 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
564 565 # UTF8 anchor
565 566 '[[Another_page#ВСст|ВСст]]' => %|<a href="/projects/ecookbook/wiki/Another_page##{CGI.escape 'ВСст'}" class="wiki-page">ВСст</a>|,
566 567 # page that doesn't exist
567 568 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
568 569 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
569 570 # link to another project wiki
570 571 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
571 572 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
572 573 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
573 574 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
574 575 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
575 576 # striked through link
576 577 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
577 578 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
578 579 # escaping
579 580 '![[Another page|Page]]' => '[[Another page|Page]]',
580 581 # project does not exist
581 582 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
582 583 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
583 584 }
584 585
585 586 @project = Project.find(1)
586 587 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
587 588 end
588 589
589 590 def test_wiki_links_within_local_file_generation_context
590 591
591 592 to_test = {
592 593 # link to a page
593 594 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
594 595 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
595 596 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
596 597 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
597 598 # page that doesn't exist
598 599 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
599 600 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
600 601 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
601 602 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
602 603 }
603 604
604 605 @project = Project.find(1)
605 606
606 607 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
607 608 end
608 609
609 610 def test_wiki_links_within_wiki_page_context
610 611
611 612 page = WikiPage.find_by_title('Another_page' )
612 613
613 614 to_test = {
614 615 # link to another page
615 616 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
616 617 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
617 618 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
618 619 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
619 620 # link to the current page
620 621 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
621 622 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
622 623 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
623 624 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
624 625 # page that doesn't exist
625 626 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">Unknown page</a>',
626 627 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">404</a>',
627 628 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">Unknown page</a>',
628 629 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">404</a>',
629 630 }
630 631
631 632 @project = Project.find(1)
632 633
633 634 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.new( :text => text, :page => page ), :text) }
634 635 end
635 636
636 637 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
637 638
638 639 to_test = {
639 640 # link to a page
640 641 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
641 642 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
642 643 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
643 644 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
644 645 # page that doesn't exist
645 646 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
646 647 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
647 648 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
648 649 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
649 650 }
650 651
651 652 @project = Project.find(1)
652 653
653 654 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
654 655 end
655 656
656 657 def test_html_tags
657 658 to_test = {
658 659 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
659 660 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
660 661 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
661 662 # do not escape pre/code tags
662 663 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
663 664 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
664 665 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
665 666 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
666 667 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
667 668 # remove attributes except class
668 669 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
669 670 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
670 671 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
671 672 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
672 673 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
673 674 # xss
674 675 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
675 676 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
676 677 }
677 678 to_test.each { |text, result| assert_equal result, textilizable(text) }
678 679 end
679 680
680 681 def test_allowed_html_tags
681 682 to_test = {
682 683 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
683 684 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
684 685 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
685 686 }
686 687 to_test.each { |text, result| assert_equal result, textilizable(text) }
687 688 end
688 689
689 690 def test_pre_tags
690 691 raw = <<-RAW
691 692 Before
692 693
693 694 <pre>
694 695 <prepared-statement-cache-size>32</prepared-statement-cache-size>
695 696 </pre>
696 697
697 698 After
698 699 RAW
699 700
700 701 expected = <<-EXPECTED
701 702 <p>Before</p>
702 703 <pre>
703 704 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
704 705 </pre>
705 706 <p>After</p>
706 707 EXPECTED
707 708
708 709 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
709 710 end
710 711
711 712 def test_pre_content_should_not_parse_wiki_and_redmine_links
712 713 raw = <<-RAW
713 714 [[CookBook documentation]]
714 715
715 716 #1
716 717
717 718 <pre>
718 719 [[CookBook documentation]]
719 720
720 721 #1
721 722 </pre>
722 723 RAW
723 724
724 725 expected = <<-EXPECTED
725 726 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
726 727 <p><a href="/issues/1" class="issue status-1 priority-4 priority-lowest" title="Can&#x27;t print recipes (New)">#1</a></p>
727 728 <pre>
728 729 [[CookBook documentation]]
729 730
730 731 #1
731 732 </pre>
732 733 EXPECTED
733 734
734 735 @project = Project.find(1)
735 736 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
736 737 end
737 738
738 739 def test_non_closing_pre_blocks_should_be_closed
739 740 raw = <<-RAW
740 741 <pre><code>
741 742 RAW
742 743
743 744 expected = <<-EXPECTED
744 745 <pre><code>
745 746 </code></pre>
746 747 EXPECTED
747 748
748 749 @project = Project.find(1)
749 750 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
750 751 end
751 752
752 753 def test_syntax_highlight
753 754 raw = <<-RAW
754 755 <pre><code class="ruby">
755 756 # Some ruby code here
756 757 </code></pre>
757 758 RAW
758 759
759 760 expected = <<-EXPECTED
760 761 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="comment"># Some ruby code here</span></span>
761 762 </code></pre>
762 763 EXPECTED
763 764
764 765 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
765 766 end
766 767
767 768 def test_to_path_param
768 769 assert_equal 'test1/test2', to_path_param('test1/test2')
769 770 assert_equal 'test1/test2', to_path_param('/test1/test2/')
770 771 assert_equal 'test1/test2', to_path_param('//test1/test2/')
771 772 assert_equal nil, to_path_param('/')
772 773 end
773 774
774 775 def test_wiki_links_in_tables
775 776 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
776 777 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
777 778 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
778 779 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
779 780 }
780 781 @project = Project.find(1)
781 782 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
782 783 end
783 784
784 785 def test_text_formatting
785 786 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
786 787 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
787 788 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
788 789 '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>',
789 790 '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',
790 791 }
791 792 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
792 793 end
793 794
794 795 def test_wiki_horizontal_rule
795 796 assert_equal '<hr />', textilizable('---')
796 797 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
797 798 end
798 799
799 800 def test_footnotes
800 801 raw = <<-RAW
801 802 This is some text[1].
802 803
803 804 fn1. This is the foot note
804 805 RAW
805 806
806 807 expected = <<-EXPECTED
807 808 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
808 809 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
809 810 EXPECTED
810 811
811 812 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
812 813 end
813 814
814 815 def test_headings
815 816 raw = 'h1. Some heading'
816 817 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
817 818
818 819 assert_equal expected, textilizable(raw)
819 820 end
820 821
821 822 def test_headings_with_special_chars
822 823 # This test makes sure that the generated anchor names match the expected
823 824 # ones even if the heading text contains unconventional characters
824 825 raw = 'h1. Some heading related to version 0.5'
825 826 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
826 827 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
827 828
828 829 assert_equal expected, textilizable(raw)
829 830 end
830 831
831 832 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
832 833 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
833 834 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
834 835
835 836 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
836 837
837 838 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
838 839 end
839 840
840 841 def test_table_of_content
841 842 raw = <<-RAW
842 843 {{toc}}
843 844
844 845 h1. Title
845 846
846 847 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
847 848
848 849 h2. Subtitle with a [[Wiki]] link
849 850
850 851 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
851 852
852 853 h2. Subtitle with [[Wiki|another Wiki]] link
853 854
854 855 h2. Subtitle with %{color:red}red text%
855 856
856 857 <pre>
857 858 some code
858 859 </pre>
859 860
860 861 h3. Subtitle with *some* _modifiers_
861 862
862 863 h3. Subtitle with @inline code@
863 864
864 865 h1. Another title
865 866
866 867 h3. An "Internet link":http://www.redmine.org/ inside subtitle
867 868
868 869 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
869 870
870 871 RAW
871 872
872 873 expected = '<ul class="toc">' +
873 874 '<li><a href="#Title">Title</a>' +
874 875 '<ul>' +
875 876 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
876 877 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
877 878 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
878 879 '<ul>' +
879 880 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
880 881 '<li><a href="#Subtitle-with-inline-code">Subtitle with inline code</a></li>' +
881 882 '</ul>' +
882 883 '</li>' +
883 884 '</ul>' +
884 885 '</li>' +
885 886 '<li><a href="#Another-title">Another title</a>' +
886 887 '<ul>' +
887 888 '<li>' +
888 889 '<ul>' +
889 890 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
890 891 '</ul>' +
891 892 '</li>' +
892 893 '<li><a href="#Project-Name">Project Name</a></li>' +
893 894 '</ul>' +
894 895 '</li>' +
895 896 '</ul>'
896 897
897 898 @project = Project.find(1)
898 899 assert textilizable(raw).gsub("\n", "").include?(expected)
899 900 end
900 901
901 902 def test_table_of_content_should_generate_unique_anchors
902 903 raw = <<-RAW
903 904 {{toc}}
904 905
905 906 h1. Title
906 907
907 908 h2. Subtitle
908 909
909 910 h2. Subtitle
910 911 RAW
911 912
912 913 expected = '<ul class="toc">' +
913 914 '<li><a href="#Title">Title</a>' +
914 915 '<ul>' +
915 916 '<li><a href="#Subtitle">Subtitle</a></li>' +
916 917 '<li><a href="#Subtitle-2">Subtitle</a></li>'
917 918 '</ul>'
918 919 '</li>' +
919 920 '</ul>'
920 921
921 922 @project = Project.find(1)
922 923 result = textilizable(raw).gsub("\n", "")
923 924 assert_include expected, result
924 925 assert_include '<a name="Subtitle">', result
925 926 assert_include '<a name="Subtitle-2">', result
926 927 end
927 928
928 929 def test_table_of_content_should_contain_included_page_headings
929 930 raw = <<-RAW
930 931 {{toc}}
931 932
932 933 h1. Included
933 934
934 935 {{include(Child_1)}}
935 936 RAW
936 937
937 938 expected = '<ul class="toc">' +
938 939 '<li><a href="#Included">Included</a></li>' +
939 940 '<li><a href="#Child-page-1">Child page 1</a></li>' +
940 941 '</ul>'
941 942
942 943 @project = Project.find(1)
943 944 assert textilizable(raw).gsub("\n", "").include?(expected)
944 945 end
945 946
946 947 def test_section_edit_links
947 948 raw = <<-RAW
948 949 h1. Title
949 950
950 951 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
951 952
952 953 h2. Subtitle with a [[Wiki]] link
953 954
954 955 h2. Subtitle with *some* _modifiers_
955 956
956 957 h2. Subtitle with @inline code@
957 958
958 959 <pre>
959 960 some code
960 961
961 962 h2. heading inside pre
962 963
963 964 <h2>html heading inside pre</h2>
964 965 </pre>
965 966
966 967 h2. Subtitle after pre tag
967 968 RAW
968 969
969 970 @project = Project.find(1)
970 971 set_language_if_valid 'en'
971 972 result = textilizable(raw, :edit_section_links => {:controller => 'wiki', :action => 'edit', :project_id => '1', :id => 'Test'}).gsub("\n", "")
972 973
973 974 # heading that contains inline code
974 975 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
975 976 '<a href="/projects/1/wiki/Test/edit\?section=4"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
976 977 '<a name="Subtitle-with-inline-code"></a>' +
977 978 '<h2 >Subtitle with <code>inline code</code><a href="#Subtitle-with-inline-code" class="wiki-anchor">&para;</a></h2>'),
978 979 result
979 980
980 981 # last heading
981 982 assert_match Regexp.new('<div class="contextual" title="Edit this section">' +
982 983 '<a href="/projects/1/wiki/Test/edit\?section=5"><img alt="Edit" src="/images/edit.png(\?\d+)?" /></a></div>' +
983 984 '<a name="Subtitle-after-pre-tag"></a>' +
984 985 '<h2 >Subtitle after pre tag<a href="#Subtitle-after-pre-tag" class="wiki-anchor">&para;</a></h2>'),
985 986 result
986 987 end
987 988
988 989 def test_default_formatter
989 990 with_settings :text_formatting => 'unknown' do
990 991 text = 'a *link*: http://www.example.net/'
991 992 assert_equal '<p>a *link*: <a class="external" href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
992 993 end
993 994 end
994 995
995 996 def test_due_date_distance_in_words
996 997 to_test = { Date.today => 'Due in 0 days',
997 998 Date.today + 1 => 'Due in 1 day',
998 999 Date.today + 100 => 'Due in about 3 months',
999 1000 Date.today + 20000 => 'Due in over 54 years',
1000 1001 Date.today - 1 => '1 day late',
1001 1002 Date.today - 100 => 'about 3 months late',
1002 1003 Date.today - 20000 => 'over 54 years late',
1003 1004 }
1004 1005 ::I18n.locale = :en
1005 1006 to_test.each do |date, expected|
1006 1007 assert_equal expected, due_date_distance_in_words(date)
1007 1008 end
1008 1009 end
1009 1010
1010 1011 def test_avatar_enabled
1011 1012 with_settings :gravatar_enabled => '1' do
1012 1013 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1013 1014 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
1014 1015 # Default size is 50
1015 1016 assert avatar('jsmith <jsmith@somenet.foo>').include?('size=50')
1016 1017 assert avatar('jsmith <jsmith@somenet.foo>', :size => 24).include?('size=24')
1017 1018 # Non-avatar options should be considered html options
1018 1019 assert avatar('jsmith <jsmith@somenet.foo>', :title => 'John Smith').include?('title="John Smith"')
1019 1020 # The default class of the img tag should be gravatar
1020 1021 assert avatar('jsmith <jsmith@somenet.foo>').include?('class="gravatar"')
1021 1022 assert !avatar('jsmith <jsmith@somenet.foo>', :class => 'picture').include?('class="gravatar"')
1022 1023 assert_nil avatar('jsmith')
1023 1024 assert_nil avatar(nil)
1024 1025 end
1025 1026 end
1026 1027
1027 1028 def test_avatar_disabled
1028 1029 with_settings :gravatar_enabled => '0' do
1029 1030 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
1030 1031 end
1031 1032 end
1032 1033
1033 1034 def test_link_to_user
1034 1035 user = User.find(2)
1035 1036 assert_equal '<a href="/users/2" class="user active">John Smith</a>', link_to_user(user)
1036 1037 end
1037 1038
1038 1039 def test_link_to_user_should_not_link_to_locked_user
1039 1040 with_current_user nil do
1040 1041 user = User.find(5)
1041 1042 assert user.locked?
1042 1043 assert_equal 'Dave2 Lopper2', link_to_user(user)
1043 1044 end
1044 1045 end
1045 1046
1046 1047 def test_link_to_user_should_link_to_locked_user_if_current_user_is_admin
1047 1048 with_current_user User.find(1) do
1048 1049 user = User.find(5)
1049 1050 assert user.locked?
1050 1051 assert_equal '<a href="/users/5" class="user locked">Dave2 Lopper2</a>', link_to_user(user)
1051 1052 end
1052 1053 end
1053 1054
1054 1055 def test_link_to_user_should_not_link_to_anonymous
1055 1056 user = User.anonymous
1056 1057 assert user.anonymous?
1057 1058 t = link_to_user(user)
1058 1059 assert_equal ::I18n.t(:label_user_anonymous), t
1059 1060 end
1060 1061
1061 1062 def test_link_to_project
1062 1063 project = Project.find(1)
1063 1064 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
1064 1065 link_to_project(project)
1065 1066 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
1066 1067 link_to_project(project, :action => 'settings')
1067 1068 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
1068 1069 link_to_project(project, {:only_path => false, :jump => 'blah'})
1069 1070 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
1070 1071 link_to_project(project, {:action => 'settings'}, :class => "project")
1071 1072 end
1072 1073
1073 1074 def test_link_to_legacy_project_with_numerical_identifier_should_use_id
1074 1075 # numeric identifier are no longer allowed
1075 1076 Project.update_all "identifier=25", "id=1"
1076 1077
1077 1078 assert_equal '<a href="/projects/1">eCookbook</a>',
1078 1079 link_to_project(Project.find(1))
1079 1080 end
1080 1081
1081 1082 def test_principals_options_for_select_with_users
1082 1083 User.current = nil
1083 1084 users = [User.find(2), User.find(4)]
1084 1085 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
1085 1086 principals_options_for_select(users)
1086 1087 end
1087 1088
1088 1089 def test_principals_options_for_select_with_selected
1089 1090 User.current = nil
1090 1091 users = [User.find(2), User.find(4)]
1091 1092 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
1092 1093 principals_options_for_select(users, User.find(4))
1093 1094 end
1094 1095
1095 1096 def test_principals_options_for_select_with_users_and_groups
1096 1097 User.current = nil
1097 1098 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
1098 1099 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
1099 1100 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
1100 1101 principals_options_for_select(users)
1101 1102 end
1102 1103
1103 1104 def test_principals_options_for_select_with_empty_collection
1104 1105 assert_equal '', principals_options_for_select([])
1105 1106 end
1106 1107
1107 1108 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
1108 1109 users = [User.find(2), User.find(4)]
1109 1110 User.current = User.find(4)
1110 1111 assert_include '<option value="4">&lt;&lt; me &gt;&gt;</option>', principals_options_for_select(users)
1111 1112 end
1112 1113
1113 1114 def test_stylesheet_link_tag_should_pick_the_default_stylesheet
1114 1115 assert_match 'href="/stylesheets/styles.css"', stylesheet_link_tag("styles")
1115 1116 end
1116 1117
1117 1118 def test_stylesheet_link_tag_for_plugin_should_pick_the_plugin_stylesheet
1118 1119 assert_match 'href="/plugin_assets/foo/stylesheets/styles.css"', stylesheet_link_tag("styles", :plugin => :foo)
1119 1120 end
1120 1121
1121 1122 def test_image_tag_should_pick_the_default_image
1122 1123 assert_match 'src="/images/image.png"', image_tag("image.png")
1123 1124 end
1124 1125
1125 1126 def test_image_tag_should_pick_the_theme_image_if_it_exists
1126 1127 theme = Redmine::Themes.themes.last
1127 1128 theme.images << 'image.png'
1128 1129
1129 1130 with_settings :ui_theme => theme.id do
1130 1131 assert_match %|src="/themes/#{theme.dir}/images/image.png"|, image_tag("image.png")
1131 1132 assert_match %|src="/images/other.png"|, image_tag("other.png")
1132 1133 end
1133 1134 ensure
1134 1135 theme.images.delete 'image.png'
1135 1136 end
1136 1137
1137 1138 def test_image_tag_sfor_plugin_should_pick_the_plugin_image
1138 1139 assert_match 'src="/plugin_assets/foo/images/image.png"', image_tag("image.png", :plugin => :foo)
1139 1140 end
1140 1141
1141 1142 def test_javascript_include_tag_should_pick_the_default_javascript
1142 1143 assert_match 'src="/javascripts/scripts.js"', javascript_include_tag("scripts")
1143 1144 end
1144 1145
1145 1146 def test_javascript_include_tag_for_plugin_should_pick_the_plugin_javascript
1146 1147 assert_match 'src="/plugin_assets/foo/javascripts/scripts.js"', javascript_include_tag("scripts", :plugin => :foo)
1147 1148 end
1148 1149
1149 1150 def test_per_page_links_should_show_usefull_values
1150 1151 set_language_if_valid 'en'
1151 1152 stubs(:link_to).returns("[link]")
1152 1153
1153 1154 with_settings :per_page_options => '10, 25, 50, 100' do
1154 1155 assert_nil per_page_links(10, 3)
1155 1156 assert_nil per_page_links(25, 3)
1156 1157 assert_equal "Per page: 10, [link]", per_page_links(10, 22)
1157 1158 assert_equal "Per page: [link], 25", per_page_links(25, 22)
1158 1159 assert_equal "Per page: [link], [link], 50", per_page_links(50, 22)
1159 1160 assert_equal "Per page: [link], 25, [link]", per_page_links(25, 26)
1160 1161 assert_equal "Per page: [link], 25, [link], [link]", per_page_links(25, 120)
1161 1162 end
1162 1163 end
1163 1164 end
@@ -1,749 +1,750
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../../test_helper', __FILE__)
19 19
20 20 class Redmine::Helpers::GanttHelperTest < ActionView::TestCase
21 21 fixtures :projects, :trackers, :issue_statuses, :issues,
22 22 :journals, :journal_details,
23 23 :enumerations, :users, :issue_categories,
24 24 :projects_trackers,
25 25 :roles,
26 26 :member_roles,
27 27 :members,
28 28 :enabled_modules,
29 29 :workflows,
30 30 :versions,
31 31 :groups_users
32 32
33 33 include ApplicationHelper
34 34 include ProjectsHelper
35 35 include IssuesHelper
36 36 include ERB::Util
37 include Rails.application.routes.url_helpers
37 38
38 39 def setup
39 40 setup_with_controller
40 41 User.current = User.find(1)
41 42 end
42 43
43 44 def today
44 45 @today ||= Date.today
45 46 end
46 47
47 48 # Creates a Gantt chart for a 4 week span
48 49 def create_gantt(project=Project.generate!, options={})
49 50 @project = project
50 51 @gantt = Redmine::Helpers::Gantt.new(options)
51 52 @gantt.project = @project
52 53 @gantt.query = Query.create!(:project => @project, :name => 'Gantt')
53 54 @gantt.view = self
54 55 @gantt.instance_variable_set('@date_from', options[:date_from] || (today - 14))
55 56 @gantt.instance_variable_set('@date_to', options[:date_to] || (today + 14))
56 57 end
57 58
58 59 context "#number_of_rows" do
59 60 context "with one project" do
60 61 should "return the number of rows just for that project"
61 62 end
62 63
63 64 context "with no project" do
64 65 should "return the total number of rows for all the projects, resursively"
65 66 end
66 67
67 68 should "not exceed max_rows option" do
68 69 p = Project.generate!
69 70 5.times do
70 71 Issue.generate!(:project => p)
71 72 end
72 73 create_gantt(p)
73 74 @gantt.render
74 75 assert_equal 6, @gantt.number_of_rows
75 76 assert !@gantt.truncated
76 77 create_gantt(p, :max_rows => 3)
77 78 @gantt.render
78 79 assert_equal 3, @gantt.number_of_rows
79 80 assert @gantt.truncated
80 81 end
81 82 end
82 83
83 84 context "#number_of_rows_on_project" do
84 85 setup do
85 86 create_gantt
86 87 end
87 88
88 89 should "count 0 for an empty the project" do
89 90 assert_equal 0, @gantt.number_of_rows_on_project(@project)
90 91 end
91 92
92 93 should "count the number of issues without a version" do
93 94 @project.issues << Issue.generate!(:project => @project, :fixed_version => nil)
94 95 assert_equal 2, @gantt.number_of_rows_on_project(@project)
95 96 end
96 97
97 98 should "count the number of issues on versions, including cross-project" do
98 99 version = Version.generate!
99 100 @project.versions << version
100 101 @project.issues << Issue.generate!(:project => @project, :fixed_version => version)
101 102 assert_equal 3, @gantt.number_of_rows_on_project(@project)
102 103 end
103 104 end
104 105
105 106 # TODO: more of an integration test
106 107 context "#subjects" do
107 108 setup do
108 109 create_gantt
109 110 @project.enabled_module_names = [:issue_tracking]
110 111 @tracker = Tracker.generate!
111 112 @project.trackers << @tracker
112 113 @version = Version.generate!(:effective_date => (today + 7), :sharing => 'none')
113 114 @project.versions << @version
114 115 @issue = Issue.generate!(:fixed_version => @version,
115 116 :subject => "gantt#line_for_project",
116 117 :tracker => @tracker,
117 118 :project => @project,
118 119 :done_ratio => 30,
119 120 :start_date => (today - 1),
120 121 :due_date => (today + 7))
121 122 @project.issues << @issue
122 123 end
123 124
124 125 context "project" do
125 126 should "be rendered" do
126 127 @output_buffer = @gantt.subjects
127 128 assert_select "div.project-name a", /#{@project.name}/
128 129 end
129 130
130 131 should "have an indent of 4" do
131 132 @output_buffer = @gantt.subjects
132 133 assert_select "div.project-name[style*=left:4px]"
133 134 end
134 135 end
135 136
136 137 context "version" do
137 138 should "be rendered" do
138 139 @output_buffer = @gantt.subjects
139 140 assert_select "div.version-name a", /#{@version.name}/
140 141 end
141 142
142 143 should "be indented 24 (one level)" do
143 144 @output_buffer = @gantt.subjects
144 145 assert_select "div.version-name[style*=left:24px]"
145 146 end
146 147
147 148 context "without assigned issues" do
148 149 setup do
149 150 @version = Version.generate!(:effective_date => (today + 14),
150 151 :sharing => 'none',
151 152 :name => 'empty_version')
152 153 @project.versions << @version
153 154 end
154 155
155 156 should "not be rendered" do
156 157 @output_buffer = @gantt.subjects
157 158 assert_select "div.version-name a", :text => /#{@version.name}/, :count => 0
158 159 end
159 160 end
160 161 end
161 162
162 163 context "issue" do
163 164 should "be rendered" do
164 165 @output_buffer = @gantt.subjects
165 166 assert_select "div.issue-subject", /#{@issue.subject}/
166 167 end
167 168
168 169 should "be indented 44 (two levels)" do
169 170 @output_buffer = @gantt.subjects
170 171 assert_select "div.issue-subject[style*=left:44px]"
171 172 end
172 173
173 174 context "assigned to a shared version of another project" do
174 175 setup do
175 176 p = Project.generate!
176 177 p.enabled_module_names = [:issue_tracking]
177 178 @shared_version = Version.generate!(:sharing => 'system')
178 179 p.versions << @shared_version
179 180 # Reassign the issue to a shared version of another project
180 181 @issue = Issue.generate!(:fixed_version => @shared_version,
181 182 :subject => "gantt#assigned_to_shared_version",
182 183 :tracker => @tracker,
183 184 :project => @project,
184 185 :done_ratio => 30,
185 186 :start_date => (today - 1),
186 187 :due_date => (today + 7))
187 188 @project.issues << @issue
188 189 end
189 190
190 191 should "be rendered" do
191 192 @output_buffer = @gantt.subjects
192 193 assert_select "div.issue-subject", /#{@issue.subject}/
193 194 end
194 195 end
195 196
196 197 context "with subtasks" do
197 198 setup do
198 199 attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version}
199 200 @child1 = Issue.generate!(
200 201 attrs.merge(:subject => 'child1',
201 202 :parent_issue_id => @issue.id,
202 203 :start_date => (today - 1),
203 204 :due_date => (today + 2))
204 205 )
205 206 @child2 = Issue.generate!(
206 207 attrs.merge(:subject => 'child2',
207 208 :parent_issue_id => @issue.id,
208 209 :start_date => today,
209 210 :due_date => (today + 7))
210 211 )
211 212 @grandchild = Issue.generate!(
212 213 attrs.merge(:subject => 'grandchild',
213 214 :parent_issue_id => @child1.id,
214 215 :start_date => (today - 1),
215 216 :due_date => (today + 2))
216 217 )
217 218 end
218 219
219 220 should "indent subtasks" do
220 221 @output_buffer = @gantt.subjects
221 222 # parent task 44px
222 223 assert_select "div.issue-subject[style*=left:44px]", /#{@issue.subject}/
223 224 # children 64px
224 225 assert_select "div.issue-subject[style*=left:64px]", /child1/
225 226 assert_select "div.issue-subject[style*=left:64px]", /child2/
226 227 # grandchild 84px
227 228 assert_select "div.issue-subject[style*=left:84px]", /grandchild/, @output_buffer
228 229 end
229 230 end
230 231 end
231 232 end
232 233
233 234 context "#lines" do
234 235 setup do
235 236 create_gantt
236 237 @project.enabled_module_names = [:issue_tracking]
237 238 @tracker = Tracker.generate!
238 239 @project.trackers << @tracker
239 240 @version = Version.generate!(:effective_date => (today + 7))
240 241 @project.versions << @version
241 242 @issue = Issue.generate!(:fixed_version => @version,
242 243 :subject => "gantt#line_for_project",
243 244 :tracker => @tracker,
244 245 :project => @project,
245 246 :done_ratio => 30,
246 247 :start_date => (today - 1),
247 248 :due_date => (today + 7))
248 249 @project.issues << @issue
249 250 @output_buffer = @gantt.lines
250 251 end
251 252
252 253 context "project" do
253 254 should "be rendered" do
254 255 assert_select "div.project.task_todo"
255 256 assert_select "div.project.starting"
256 257 assert_select "div.project.ending"
257 258 assert_select "div.label.project", /#{@project.name}/
258 259 end
259 260 end
260 261
261 262 context "version" do
262 263 should "be rendered" do
263 264 assert_select "div.version.task_todo"
264 265 assert_select "div.version.starting"
265 266 assert_select "div.version.ending"
266 267 assert_select "div.label.version", /#{@version.name}/
267 268 end
268 269 end
269 270
270 271 context "issue" do
271 272 should "be rendered" do
272 273 assert_select "div.task_todo"
273 274 assert_select "div.task.label", /#{@issue.done_ratio}/
274 275 assert_select "div.tooltip", /#{@issue.subject}/
275 276 end
276 277 end
277 278 end
278 279
279 280 context "#render_project" do
280 281 should "be tested"
281 282 end
282 283
283 284 context "#render_issues" do
284 285 should "be tested"
285 286 end
286 287
287 288 context "#render_version" do
288 289 should "be tested"
289 290 end
290 291
291 292 context "#subject_for_project" do
292 293 setup do
293 294 create_gantt
294 295 end
295 296
296 297 context ":html format" do
297 298 should "add an absolute positioned div" do
298 299 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
299 300 assert_select "div[style*=absolute]"
300 301 end
301 302
302 303 should "use the indent option to move the div to the right" do
303 304 @output_buffer = @gantt.subject_for_project(@project, {:format => :html, :indent => 40})
304 305 assert_select "div[style*=left:40]"
305 306 end
306 307
307 308 should "include the project name" do
308 309 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
309 310 assert_select 'div', :text => /#{@project.name}/
310 311 end
311 312
312 313 should "include a link to the project" do
313 314 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
314 315 assert_select 'a[href=?]', "/projects/#{@project.identifier}", :text => /#{@project.name}/
315 316 end
316 317
317 318 should "style overdue projects" do
318 319 @project.enabled_module_names = [:issue_tracking]
319 320 @project.versions << Version.generate!(:effective_date => (today - 1))
320 321 assert @project.reload.overdue?, "Need an overdue project for this test"
321 322 @output_buffer = @gantt.subject_for_project(@project, {:format => :html})
322 323 assert_select 'div span.project-overdue'
323 324 end
324 325 end
325 326 should "test the PNG format"
326 327 should "test the PDF format"
327 328 end
328 329
329 330 context "#line_for_project" do
330 331 setup do
331 332 create_gantt
332 333 @project.enabled_module_names = [:issue_tracking]
333 334 @tracker = Tracker.generate!
334 335 @project.trackers << @tracker
335 336 @version = Version.generate!(:effective_date => (today - 1))
336 337 @project.versions << @version
337 338 @project.issues << Issue.generate!(:fixed_version => @version,
338 339 :subject => "gantt#line_for_project",
339 340 :tracker => @tracker,
340 341 :project => @project,
341 342 :done_ratio => 30,
342 343 :start_date => (today - 7),
343 344 :due_date => (today + 7))
344 345 end
345 346
346 347 context ":html format" do
347 348 context "todo line" do
348 349 should "start from the starting point on the left" do
349 350 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
350 351 assert_select "div.project.task_todo[style*=left:28px]", true, @output_buffer
351 352 end
352 353
353 354 should "be the total width of the project" do
354 355 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
355 356 assert_select "div.project.task_todo[style*=width:58px]", true, @output_buffer
356 357 end
357 358 end
358 359
359 360 context "late line" do
360 361 should_eventually "start from the starting point on the left" do
361 362 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
362 363 assert_select "div.project.task_late[style*=left:28px]", true, @output_buffer
363 364 end
364 365
365 366 should_eventually "be the total delayed width of the project" do
366 367 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
367 368 assert_select "div.project.task_late[style*=width:30px]", true, @output_buffer
368 369 end
369 370 end
370 371
371 372 context "done line" do
372 373 should_eventually "start from the starting point on the left" do
373 374 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
374 375 assert_select "div.project.task_done[style*=left:28px]", true, @output_buffer
375 376 end
376 377
377 378 should_eventually "Be the total done width of the project" do
378 379 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
379 380 assert_select "div.project.task_done[style*=width:18px]", true, @output_buffer
380 381 end
381 382 end
382 383
383 384 context "starting marker" do
384 385 should "not appear if the starting point is off the gantt chart" do
385 386 # Shift the date range of the chart
386 387 @gantt.instance_variable_set('@date_from', today)
387 388 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
388 389 assert_select "div.project.starting", false, @output_buffer
389 390 end
390 391
391 392 should "appear at the starting point" do
392 393 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
393 394 assert_select "div.project.starting[style*=left:28px]", true, @output_buffer
394 395 end
395 396 end
396 397
397 398 context "ending marker" do
398 399 should "not appear if the starting point is off the gantt chart" do
399 400 # Shift the date range of the chart
400 401 @gantt.instance_variable_set('@date_to', (today - 14))
401 402 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
402 403 assert_select "div.project.ending", false, @output_buffer
403 404 end
404 405
405 406 should "appear at the end of the date range" do
406 407 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
407 408 assert_select "div.project.ending[style*=left:88px]", true, @output_buffer
408 409 end
409 410 end
410 411
411 412 context "status content" do
412 413 should "appear at the far left, even if it's far in the past" do
413 414 @gantt.instance_variable_set('@date_to', (today - 14))
414 415 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
415 416 assert_select "div.project.label", /#{@project.name}/
416 417 end
417 418
418 419 should "show the project name" do
419 420 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
420 421 assert_select "div.project.label", /#{@project.name}/
421 422 end
422 423
423 424 should_eventually "show the percent complete" do
424 425 @output_buffer = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
425 426 assert_select "div.project.label", /0%/
426 427 end
427 428 end
428 429 end
429 430 should "test the PNG format"
430 431 should "test the PDF format"
431 432 end
432 433
433 434 context "#subject_for_version" do
434 435 setup do
435 436 create_gantt
436 437 @project.enabled_module_names = [:issue_tracking]
437 438 @tracker = Tracker.generate!
438 439 @project.trackers << @tracker
439 440 @version = Version.generate!(:effective_date => (today - 1))
440 441 @project.versions << @version
441 442 @project.issues << Issue.generate!(:fixed_version => @version,
442 443 :subject => "gantt#subject_for_version",
443 444 :tracker => @tracker,
444 445 :project => @project,
445 446 :start_date => today)
446 447
447 448 end
448 449
449 450 context ":html format" do
450 451 should "add an absolute positioned div" do
451 452 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
452 453 assert_select "div[style*=absolute]"
453 454 end
454 455
455 456 should "use the indent option to move the div to the right" do
456 457 @output_buffer = @gantt.subject_for_version(@version, {:format => :html, :indent => 40})
457 458 assert_select "div[style*=left:40]"
458 459 end
459 460
460 461 should "include the version name" do
461 462 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
462 463 assert_select 'div', :text => /#{@version.name}/
463 464 end
464 465
465 466 should "include a link to the version" do
466 467 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
467 468 assert_select 'a[href=?]', Regexp.escape("/versions/#{@version.to_param}"), :text => /#{@version.name}/
468 469 end
469 470
470 471 should "style late versions" do
471 472 assert @version.overdue?, "Need an overdue version for this test"
472 473 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
473 474 assert_select 'div span.version-behind-schedule'
474 475 end
475 476
476 477 should "style behind schedule versions" do
477 478 assert @version.behind_schedule?, "Need a behind schedule version for this test"
478 479 @output_buffer = @gantt.subject_for_version(@version, {:format => :html})
479 480 assert_select 'div span.version-behind-schedule'
480 481 end
481 482 end
482 483 should "test the PNG format"
483 484 should "test the PDF format"
484 485 end
485 486
486 487 context "#line_for_version" do
487 488 setup do
488 489 create_gantt
489 490 @project.enabled_module_names = [:issue_tracking]
490 491 @tracker = Tracker.generate!
491 492 @project.trackers << @tracker
492 493 @version = Version.generate!(:effective_date => (today + 7))
493 494 @project.versions << @version
494 495 @project.issues << Issue.generate!(:fixed_version => @version,
495 496 :subject => "gantt#line_for_project",
496 497 :tracker => @tracker,
497 498 :project => @project,
498 499 :done_ratio => 30,
499 500 :start_date => (today - 7),
500 501 :due_date => (today + 7))
501 502 end
502 503
503 504 context ":html format" do
504 505 context "todo line" do
505 506 should "start from the starting point on the left" do
506 507 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
507 508 assert_select "div.version.task_todo[style*=left:28px]", true, @output_buffer
508 509 end
509 510
510 511 should "be the total width of the version" do
511 512 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
512 513 assert_select "div.version.task_todo[style*=width:58px]", true, @output_buffer
513 514 end
514 515 end
515 516
516 517 context "late line" do
517 518 should "start from the starting point on the left" do
518 519 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
519 520 assert_select "div.version.task_late[style*=left:28px]", true, @output_buffer
520 521 end
521 522
522 523 should "be the total delayed width of the version" do
523 524 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
524 525 assert_select "div.version.task_late[style*=width:30px]", true, @output_buffer
525 526 end
526 527 end
527 528
528 529 context "done line" do
529 530 should "start from the starting point on the left" do
530 531 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
531 532 assert_select "div.version.task_done[style*=left:28px]", true, @output_buffer
532 533 end
533 534
534 535 should "be the total done width of the version" do
535 536 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
536 537 assert_select "div.version.task_done[style*=width:16px]", true, @output_buffer
537 538 end
538 539 end
539 540
540 541 context "starting marker" do
541 542 should "not appear if the starting point is off the gantt chart" do
542 543 # Shift the date range of the chart
543 544 @gantt.instance_variable_set('@date_from', today)
544 545 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
545 546 assert_select "div.version.starting", false
546 547 end
547 548
548 549 should "appear at the starting point" do
549 550 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
550 551 assert_select "div.version.starting[style*=left:28px]", true, @output_buffer
551 552 end
552 553 end
553 554
554 555 context "ending marker" do
555 556 should "not appear if the starting point is off the gantt chart" do
556 557 # Shift the date range of the chart
557 558 @gantt.instance_variable_set('@date_to', (today - 14))
558 559 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
559 560 assert_select "div.version.ending", false
560 561 end
561 562
562 563 should "appear at the end of the date range" do
563 564 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
564 565 assert_select "div.version.ending[style*=left:88px]", true, @output_buffer
565 566 end
566 567 end
567 568
568 569 context "status content" do
569 570 should "appear at the far left, even if it's far in the past" do
570 571 @gantt.instance_variable_set('@date_to', (today - 14))
571 572 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
572 573 assert_select "div.version.label", /#{@version.name}/
573 574 end
574 575
575 576 should "show the version name" do
576 577 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
577 578 assert_select "div.version.label", /#{@version.name}/
578 579 end
579 580
580 581 should "show the percent complete" do
581 582 @output_buffer = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
582 583 assert_select "div.version.label", /30%/
583 584 end
584 585 end
585 586 end
586 587 should "test the PNG format"
587 588 should "test the PDF format"
588 589 end
589 590
590 591 context "#subject_for_issue" do
591 592 setup do
592 593 create_gantt
593 594 @project.enabled_module_names = [:issue_tracking]
594 595 @tracker = Tracker.generate!
595 596 @project.trackers << @tracker
596 597 @issue = Issue.generate!(:subject => "gantt#subject_for_issue",
597 598 :tracker => @tracker,
598 599 :project => @project,
599 600 :start_date => (today - 3),
600 601 :due_date => (today - 1))
601 602 @project.issues << @issue
602 603 end
603 604
604 605 context ":html format" do
605 606 should "add an absolute positioned div" do
606 607 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
607 608 assert_select "div[style*=absolute]"
608 609 end
609 610
610 611 should "use the indent option to move the div to the right" do
611 612 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40})
612 613 assert_select "div[style*=left:40]"
613 614 end
614 615
615 616 should "include the issue subject" do
616 617 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
617 618 assert_select 'div', :text => /#{@issue.subject}/
618 619 end
619 620
620 621 should "include a link to the issue" do
621 622 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
622 623 assert_select 'a[href=?]', Regexp.escape("/issues/#{@issue.to_param}"), :text => /#{@tracker.name} ##{@issue.id}/
623 624 end
624 625
625 626 should "style overdue issues" do
626 627 assert @issue.overdue?, "Need an overdue issue for this test"
627 628 @output_buffer = @gantt.subject_for_issue(@issue, {:format => :html})
628 629 assert_select 'div span.issue-overdue'
629 630 end
630 631 end
631 632 should "test the PNG format"
632 633 should "test the PDF format"
633 634 end
634 635
635 636 context "#line_for_issue" do
636 637 setup do
637 638 create_gantt
638 639 @project.enabled_module_names = [:issue_tracking]
639 640 @tracker = Tracker.generate!
640 641 @project.trackers << @tracker
641 642 @version = Version.generate!(:effective_date => (today + 7))
642 643 @project.versions << @version
643 644 @issue = Issue.generate!(:fixed_version => @version,
644 645 :subject => "gantt#line_for_project",
645 646 :tracker => @tracker,
646 647 :project => @project,
647 648 :done_ratio => 30,
648 649 :start_date => (today - 7),
649 650 :due_date => (today + 7))
650 651 @project.issues << @issue
651 652 end
652 653
653 654 context ":html format" do
654 655 context "todo line" do
655 656 should "start from the starting point on the left" do
656 657 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
657 658 assert_select "div.task_todo[style*=left:28px]", true, @output_buffer
658 659 end
659 660
660 661 should "be the total width of the issue" do
661 662 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
662 663 assert_select "div.task_todo[style*=width:58px]", true, @output_buffer
663 664 end
664 665 end
665 666
666 667 context "late line" do
667 668 should "start from the starting point on the left" do
668 669 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
669 670 assert_select "div.task_late[style*=left:28px]", true, @output_buffer
670 671 end
671 672
672 673 should "be the total delayed width of the issue" do
673 674 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
674 675 assert_select "div.task_late[style*=width:30px]", true, @output_buffer
675 676 end
676 677 end
677 678
678 679 context "done line" do
679 680 should "start from the starting point on the left" do
680 681 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
681 682 assert_select "div.task_done[style*=left:28px]", true, @output_buffer
682 683 end
683 684
684 685 should "be the total done width of the issue" do
685 686 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
686 687 # 15 days * 4 px * 30% - 2 px for borders = 16 px
687 688 assert_select "div.task_done[style*=width:16px]", true, @output_buffer
688 689 end
689 690
690 691 should "not be the total done width if the chart starts after issue start date" do
691 692 create_gantt(@project, :date_from => (today - 5))
692 693 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
693 694 assert_select "div.task_done[style*=left:0px]", true, @output_buffer
694 695 assert_select "div.task_done[style*=width:8px]", true, @output_buffer
695 696 end
696 697
697 698 context "for completed issue" do
698 699 setup do
699 700 @issue.done_ratio = 100
700 701 end
701 702
702 703 should "be the total width of the issue" do
703 704 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
704 705 assert_select "div.task_done[style*=width:58px]", true, @output_buffer
705 706 end
706 707
707 708 should "be the total width of the issue with due_date=start_date" do
708 709 @issue.due_date = @issue.start_date
709 710 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
710 711 assert_select "div.task_done[style*=width:2px]", true, @output_buffer
711 712 end
712 713 end
713 714 end
714 715
715 716 context "status content" do
716 717 should "appear at the far left, even if it's far in the past" do
717 718 @gantt.instance_variable_set('@date_to', (today - 14))
718 719 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
719 720 assert_select "div.task.label", true, @output_buffer
720 721 end
721 722
722 723 should "show the issue status" do
723 724 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
724 725 assert_select "div.task.label", /#{@issue.status.name}/
725 726 end
726 727
727 728 should "show the percent complete" do
728 729 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
729 730 assert_select "div.task.label", /30%/
730 731 end
731 732 end
732 733 end
733 734
734 735 should "have an issue tooltip" do
735 736 @output_buffer = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
736 737 assert_select "div.tooltip", /#{@issue.subject}/
737 738 end
738 739 should "test the PNG format"
739 740 should "test the PDF format"
740 741 end
741 742
742 743 context "#to_image" do
743 744 should "be tested"
744 745 end
745 746
746 747 context "#to_pdf" do
747 748 should "be tested"
748 749 end
749 750 end
General Comments 0
You need to be logged in to leave comments. Login now