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