redmine_rate 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/COPYRIGHT.txt +18 -0
  2. data/CREDITS.txt +5 -0
  3. data/GPL.txt +339 -0
  4. data/README.rdoc +93 -0
  5. data/Rakefile +37 -0
  6. data/VERSION +1 -0
  7. data/app/controllers/rate_caches_controller.rb +35 -0
  8. data/app/controllers/rates_controller.rb +154 -0
  9. data/app/models/rate.rb +161 -0
  10. data/app/views/rate_caches/index.html.erb +21 -0
  11. data/app/views/rates/_form.html.erb +36 -0
  12. data/app/views/rates/_list.html.erb +42 -0
  13. data/app/views/rates/create.js.rjs +2 -0
  14. data/app/views/rates/create_error.js.rjs +5 -0
  15. data/app/views/rates/edit.html.erb +3 -0
  16. data/app/views/rates/index.html.erb +5 -0
  17. data/app/views/rates/new.html.erb +3 -0
  18. data/app/views/rates/show.html.erb +23 -0
  19. data/app/views/users/_membership_rate.html.erb +23 -0
  20. data/app/views/users/_rates.html.erb +17 -0
  21. data/assets/images/database_refresh.png +0 -0
  22. data/config/locales/de.yml +18 -0
  23. data/config/locales/en.yml +18 -0
  24. data/config/locales/fr.yml +20 -0
  25. data/config/locales/ru.yml +9 -0
  26. data/config/routes.rb +4 -0
  27. data/init.rb +49 -0
  28. data/lang/de.yml +9 -0
  29. data/lang/en.yml +9 -0
  30. data/lang/fr.yml +8 -0
  31. data/lib/rate_conversion.rb +16 -0
  32. data/lib/rate_memberships_hook.rb +15 -0
  33. data/lib/rate_project_hook.rb +106 -0
  34. data/lib/rate_sort_helper_patch.rb +102 -0
  35. data/lib/rate_time_entry_patch.rb +66 -0
  36. data/lib/rate_users_helper_patch.rb +37 -0
  37. data/lib/redmine_rate/hooks/plugin_timesheet_view_timesheets_report_header_tags_hook.rb +11 -0
  38. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_group_header_hook.rb +9 -0
  39. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_hook.rb +18 -0
  40. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_sum_hook.rb +17 -0
  41. data/lib/redmine_rate/hooks/plugin_timesheet_views_timesheets_time_entry_row_class_hook.rb +21 -0
  42. data/lib/redmine_rate/hooks/timesheet_hook_helper.rb +14 -0
  43. data/lib/redmine_rate/hooks/view_layouts_base_html_head_hook.rb +9 -0
  44. data/lib/tasks/cache.rake +13 -0
  45. data/lib/tasks/data.rake +163 -0
  46. data/rails/init.rb +1 -0
  47. data/test/functional/rates_controller_test.rb +401 -0
  48. data/test/integration/admin_panel_test.rb +81 -0
  49. data/test/integration/routing_test.rb +16 -0
  50. data/test/test_helper.rb +43 -0
  51. data/test/unit/lib/rate_time_entry_patch_test.rb +77 -0
  52. data/test/unit/lib/rate_users_helper_patch_test.rb +37 -0
  53. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_view_timesheets_report_header_tags_hook_test.rb +26 -0
  54. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_group_header_hook_test.rb +26 -0
  55. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_hook_test.rb +47 -0
  56. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheet_time_entry_sum_hook_test.rb +48 -0
  57. data/test/unit/lib/redmine_rate/hooks/plugin_timesheet_views_timesheets_time_entry_row_class_hook_test.rb +60 -0
  58. data/test/unit/rate_for_test.rb +74 -0
  59. data/test/unit/rate_test.rb +333 -0
  60. metadata +137 -0
@@ -0,0 +1,401 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ class RatesControllerTest < ActionController::TestCase
4
+
5
+ def self.should_be_unauthorized(&block)
6
+ should 'should return a forbidden status code' do
7
+ instance_eval(&block)
8
+ assert_response :forbidden
9
+ end
10
+
11
+ should 'should display the standard unauthorized page' do
12
+ instance_eval(&block)
13
+ assert_template 'common/error'
14
+ end
15
+
16
+ context "with mime type of xml" do
17
+
18
+ should "should return an forbidden error" do
19
+ @request.env["HTTP_ACCEPT"] = "application/xml"
20
+ instance_eval(&block)
21
+ assert_response :forbidden
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ def mock_rate(stubs={})
28
+ @project = Project.generate!
29
+ stubs = {
30
+ :date_in_effect => Date.today,
31
+ :project => @project,
32
+ :amount => 100.0,
33
+ :user => @user
34
+ }.merge(stubs)
35
+ @mock_rate = Rate.generate(stubs)
36
+ end
37
+
38
+ def mock_locked_rate(stubs={})
39
+ @mock_rate = mock_rate
40
+ @mock_rate.time_entries << TimeEntry.generate!
41
+ @mock_rate
42
+ end
43
+
44
+ context "as regular user" do
45
+ setup do
46
+ @user = User.generate!
47
+ @request.session[:user_id] = @user.id
48
+ end
49
+
50
+ context "responding to GET index" do
51
+ should_be_unauthorized { get :index }
52
+ end
53
+
54
+ context "responding to GET show" do
55
+ should_be_unauthorized { get :show, :id => "37" }
56
+ end
57
+
58
+ context "responding to GET new" do
59
+ should_be_unauthorized { get :new }
60
+ end
61
+
62
+ context "responding to GET edit" do
63
+ should_be_unauthorized { get :edit, :id => "37" }
64
+ end
65
+
66
+ context "responding to POST create" do
67
+ should_be_unauthorized { post :create, :rate => {:these => 'params'} }
68
+ end
69
+
70
+ context "responding to PUT update" do
71
+ should_be_unauthorized { put :update, :id => "37", :rate => {:these => 'params'} }
72
+ end
73
+
74
+ context "responding to DELETE destroy" do
75
+ should_be_unauthorized { delete :destroy, :id => "37" }
76
+ end
77
+ end
78
+
79
+
80
+ context "as an administrator" do
81
+
82
+ setup do
83
+ @user = User.generate!(:admin => true)
84
+ @request.session[:user_id] = @user.id
85
+ end
86
+
87
+ context "responding to GET index" do
88
+
89
+ should "should redirect to the homepage" do
90
+ get :index
91
+ assert_redirected_to home_url
92
+ end
93
+
94
+ should "should display an error flash message" do
95
+ get :index
96
+ assert_match /not found/, flash[:error]
97
+ end
98
+
99
+ context "with mime type of xml" do
100
+
101
+ should "should return a 404 error" do
102
+ @request.env["HTTP_ACCEPT"] = "application/xml"
103
+ get :index
104
+ assert_response :not_found
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+
111
+ context "responding to GET index with user" do
112
+ setup do
113
+ mock_rate
114
+ end
115
+
116
+ should "should expose all historic rates for the user as @rates" do
117
+ get :index, :user_id => @user.id
118
+ assert_equal assigns(:rates), [@mock_rate]
119
+ end
120
+
121
+ context "with mime type of xml" do
122
+
123
+ should "should render all rates as xml" do
124
+ @request.env["HTTP_ACCEPT"] = "application/xml"
125
+ get :index, :user_id => @user.id
126
+
127
+ assert_select 'rates' do
128
+ assert_select 'rate' do
129
+ assert_select 'id', :text => @mock_rate.id
130
+ end
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+
139
+ context "responding to GET show" do
140
+ setup do
141
+ mock_rate
142
+ end
143
+
144
+ should "should expose the @requested rate as @rate" do
145
+ get :show, :id => @mock_rate.id
146
+ assert_equal assigns(:rate), @mock_rate
147
+ end
148
+
149
+ context "with mime type of xml" do
150
+
151
+ should "should render the requested rate as xml" do
152
+ @request.env["HTTP_ACCEPT"] = "application/xml"
153
+ get :show, :id => @mock_rate.id
154
+
155
+ assert_select 'rate' do
156
+ assert_select 'id', :text => @mock_rate.id
157
+ assert_select 'amount', :text => /100/
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+
166
+ context "responding to GET new" do
167
+
168
+ should "should redirect to the homepage" do
169
+ get :new
170
+ assert_redirected_to home_url
171
+ end
172
+
173
+ should "should display an error flash message" do
174
+ get :new
175
+ assert_match /not found/, flash[:error]
176
+ end
177
+
178
+ context "with mime type of xml" do
179
+
180
+ should "should return a 404 error" do
181
+ @request.env["HTTP_ACCEPT"] = "application/xml"
182
+ get :new
183
+ assert_response :not_found
184
+ end
185
+
186
+ end
187
+ end
188
+
189
+ context "responding to GET new with user" do
190
+ should 'should be successful' do
191
+ get :new, :user_id => @user.id
192
+ assert_response :success
193
+ end
194
+
195
+ should "should expose a new rate as @rate" do
196
+ get :new, :user_id => @user.id
197
+ assert assigns(:rate)
198
+ assert assigns(:rate).new_record?
199
+ end
200
+
201
+ end
202
+
203
+ context "responding to GET edit" do
204
+ setup do
205
+ mock_rate
206
+ end
207
+
208
+ should "should expose the requested rate as @rate" do
209
+ get :edit, :id => @mock_rate.id
210
+ assert_equal assigns(:rate), @mock_rate
211
+ end
212
+
213
+ context "on a locked rate" do
214
+ setup do
215
+ mock_locked_rate
216
+ end
217
+
218
+ should 'should not have a Update button' do
219
+ get :edit, :id => @mock_rate.id
220
+ assert_select "input[type=submit]", :count => 0
221
+ end
222
+
223
+ should 'should show the locked icon' do
224
+ get :edit, :id => @mock_rate.id
225
+ assert_select "img[src*=locked.png]"
226
+ end
227
+ end
228
+
229
+ end
230
+
231
+ context "responding to POST create" do
232
+
233
+ context "with valid params" do
234
+ setup do
235
+ @project = Project.generate!
236
+ end
237
+
238
+ should "should expose a newly created rate as @rate" do
239
+ post :create, :rate => {:project_id => @project.id, :amount => '50', :date_in_effect => Date.today.to_s, :user_id => @user.id}
240
+ assert assigns(:rate)
241
+ end
242
+
243
+ should "should redirect to the rate list" do
244
+ post :create, :rate => {:project_id => @project.id, :amount => '50', :date_in_effect => Date.today.to_s, :user_id => @user.id}
245
+
246
+ assert_redirected_to rates_url(:user_id => @user.id)
247
+ end
248
+
249
+ should 'should redirect to the back_url if set' do
250
+ back_url = '/rates'
251
+ post :create, :rate => {:project_id => @project.id, :amount => '50', :date_in_effect => Date.today.to_s, :user_id => @user.id}, :back_url => back_url
252
+
253
+ assert_redirected_to back_url
254
+ end
255
+
256
+ end
257
+
258
+ context "with invalid params" do
259
+ should "should expose a newly created but unsaved rate as @rate" do
260
+ post :create, :rate => {}
261
+ assert assigns(:rate).new_record?
262
+ end
263
+
264
+ should "should re-render the 'new' template" do
265
+ post :create, :rate => {}
266
+ assert_template 'new'
267
+ end
268
+
269
+ end
270
+
271
+ end
272
+
273
+ context "responding to PUT udpate" do
274
+
275
+ context "with valid params" do
276
+ setup do
277
+ mock_rate
278
+ end
279
+
280
+ should "should update the requested rate" do
281
+ put :update, :id => @mock_rate.id, :rate => {:amount => '150'}
282
+
283
+ assert_equal 150.0, @mock_rate.reload.amount
284
+ end
285
+
286
+ should "should expose the requested rate as @rate" do
287
+ put :update, :id => @mock_rate.id
288
+
289
+ assert_equal assigns(:rate), @mock_rate
290
+ end
291
+
292
+ should "should redirect to the rate list" do
293
+ put :update, :id => "1"
294
+
295
+ assert_redirected_to rates_url(:user_id => @user.id)
296
+ end
297
+
298
+ should 'should redirect to the back_url if set' do
299
+ back_url = '/rates'
300
+ put :update, :id => "1", :back_url => back_url
301
+
302
+ assert_redirected_to back_url
303
+ end
304
+
305
+ end
306
+
307
+ context "with invalid params" do
308
+ setup do
309
+ mock_rate
310
+ end
311
+
312
+ should "should not update the requested rate" do
313
+ put :update, :id => @mock_rate.id, :rate => {:amount => 'asdf'}
314
+
315
+ assert_equal 100.0, @mock_rate.reload.amount
316
+ end
317
+
318
+ should "should expose the rate as @rate" do
319
+ put :update, :id => @mock_rate.id, :rate => {:amount => 'asdf'}
320
+
321
+ assert_equal assigns(:rate), @mock_rate
322
+ end
323
+
324
+ should "should re-render the 'edit' template" do
325
+ put :update, :id => @mock_rate.id, :rate => {:amount => 'asdf'}
326
+
327
+ assert_template 'edit'
328
+ end
329
+
330
+ end
331
+
332
+ context "on a locked rate" do
333
+ setup do
334
+ mock_locked_rate
335
+ end
336
+
337
+ should "should not save the rate" do
338
+ put :update, :id => @mock_rate.id, :rate => {:amount => '150'}
339
+
340
+ assert_equal 100, @mock_rate.reload.amount
341
+ end
342
+
343
+ should "should set the locked rate as @rate" do
344
+ put :update, :id => @mock_rate.id, :rate => { :amount => 200.0 }
345
+
346
+ assert_equal assigns(:rate), @mock_rate
347
+ end
348
+
349
+ should "should re-render the 'edit' template" do
350
+ put :update, :id => @mock_rate.id
351
+
352
+ assert_template 'edit'
353
+ end
354
+
355
+ should "should render an error message" do
356
+ put :update, :id => @mock_rate.id
357
+
358
+ assert_match /locked/, flash[:error]
359
+ end
360
+ end
361
+
362
+ end
363
+
364
+ context "responding to DELETE destroy" do
365
+ setup do
366
+ mock_rate
367
+ end
368
+
369
+ should "should destroy the requested rate" do
370
+ assert_difference('Rate.count', -1) do
371
+ delete :destroy, :id => @mock_rate.id
372
+ end
373
+ end
374
+
375
+ should "should redirect to the user's rates list" do
376
+ delete :destroy, :id => @mock_rate.id
377
+ assert_redirected_to rates_url(:user_id => @user.id)
378
+ end
379
+
380
+ should 'should redirect to the back_url if set' do
381
+ back_url = '/rates'
382
+ delete :destroy, :id => "1", :back_url => back_url
383
+
384
+ assert_redirected_to back_url
385
+ end
386
+
387
+ context "on a locked rate" do
388
+ setup do
389
+ mock_locked_rate
390
+ end
391
+
392
+ should "should display an error message" do
393
+ delete :destroy, :id => @mock_rate.id
394
+ assert_match /locked/, flash[:error]
395
+ end
396
+ end
397
+
398
+ end
399
+
400
+ end
401
+ end
@@ -0,0 +1,81 @@
1
+ require 'test_helper'
2
+
3
+ class AdminPanelTest < ActionController::IntegrationTest
4
+ include Redmine::I18n
5
+
6
+ def setup
7
+ @last_caching_run = 4.days.ago.to_s
8
+ @last_cache_clearing_run = 7.days.ago.to_s
9
+
10
+ Setting.plugin_redmine_rate = {
11
+ 'last_caching_run' => @last_caching_run,
12
+ 'last_cache_clearing_run' => @last_cache_clearing_run
13
+ }
14
+
15
+ @user = User.generate!(:admin => true, :password => 'rates', :password_confirmation => 'rates')
16
+
17
+ login_as(@user.login, 'rates')
18
+ end
19
+
20
+ context "Rate Caches admin panel" do
21
+ should "be listed in the main Admin section" do
22
+ click_link "Administration"
23
+ assert_response :success
24
+
25
+ assert_select "#admin-menu" do
26
+ assert_select "a.rate-caches"
27
+ end
28
+
29
+ end
30
+
31
+ should "show the last run timestamp for the last caching run" do
32
+ click_link "Administration"
33
+ click_link "Rate Caches"
34
+
35
+ assert_select '#caching-run' do
36
+ assert_select 'p', :text => /#{format_time(@last_caching_run)}/
37
+ end
38
+
39
+ end
40
+
41
+ should "show the last run timestamp for the last cache clearing run" do
42
+ click_link "Administration"
43
+ click_link "Rate Caches"
44
+
45
+ assert_select '#cache-clearing-run' do
46
+ assert_select 'p', :text => /#{format_time(@last_cache_clearing_run)}/
47
+ end
48
+
49
+ end
50
+
51
+ should "have a button to force a caching run" do
52
+ click_link "Administration"
53
+ click_link "Rate Caches"
54
+ click_button "Load Missing Caches"
55
+
56
+ assert_response :success
57
+
58
+ appx_clear_time = Date.today.strftime("%m/%d/%Y")
59
+
60
+ assert_select '#caching-run' do
61
+ assert_select 'p', :text => /#{appx_clear_time}/
62
+ end
63
+
64
+ end
65
+
66
+ should "have a button to force a cache clearing run" do
67
+ click_link "Administration"
68
+ click_link "Rate Caches"
69
+ click_button "Clear and Load All Caches"
70
+
71
+ assert_response :success
72
+
73
+ appx_clear_time = Date.today.strftime("%m/%d/%Y")
74
+
75
+ assert_select '#cache-clearing-run' do
76
+ assert_select 'p', :text => /#{appx_clear_time}/
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,16 @@
1
+ require "#{File.dirname(__FILE__)}/../test_helper"
2
+
3
+ class RoutingTest < ActionController::IntegrationTest
4
+ context "routing rates" do
5
+ should_route :get, "/rates", :controller => "rates", :action => "index"
6
+ should_route :get, "/rates/new", :controller => "rates", :action => "new"
7
+ should_route :get, "/rates/1", :controller => "rates", :action => "show", :id => "1"
8
+ should_route :get, "/rates/1/edit", :controller => "rates", :action => "edit", :id => "1"
9
+
10
+ should_route :post, "/rates", :controller => "rates", :action => "create"
11
+
12
+ should_route :put, "/rates/1", :controller => "rates", :action => "update", :id => "1"
13
+
14
+ should_route :delete, "/rates/1", :controller => "rates", :action => "destroy", :id => "1"
15
+ end
16
+ end
@@ -0,0 +1,43 @@
1
+ # Load the normal Rails helper
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
3
+
4
+ # Ensure that we are using the temporary fixture path
5
+ Engines::Testing.set_fixture_path
6
+
7
+ require "webrat"
8
+
9
+ Webrat.configure do |config|
10
+ config.mode = :rails
11
+ end
12
+
13
+ module IntegrationTestHelper
14
+ def login_as(user="existing", password="existing")
15
+ visit "/login"
16
+ fill_in 'Login', :with => user
17
+ fill_in 'Password', :with => password
18
+ click_button 'login'
19
+ assert_response :success
20
+ assert User.current.logged?
21
+ end
22
+
23
+ def logout
24
+ visit '/logout'
25
+ assert_response :success
26
+ assert !User.current.logged?
27
+ end
28
+
29
+ def assert_forbidden
30
+ assert_response :forbidden
31
+ assert_template 'common/error'
32
+ end
33
+
34
+ def assert_requires_login
35
+ assert_response :success
36
+ assert_template 'account/login'
37
+ end
38
+
39
+ end
40
+
41
+ class ActionController::IntegrationTest
42
+ include IntegrationTestHelper
43
+ end
@@ -0,0 +1,77 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+
3
+ class RateTimeEntryPatchTest < ActiveSupport::TestCase
4
+ def setup
5
+ @user = User.generate!
6
+ @project = Project.generate!
7
+ @date = Date.today.to_s
8
+ @time_entry = TimeEntry.new({:user => @user, :project => @project, :spent_on => @date, :hours => 10.0, :activity => TimeEntryActivity.generate!})
9
+ @rate = Rate.generate!(:user => @user, :project => @project, :date_in_effect => @date, :amount => 200.0)
10
+ end
11
+
12
+ should 'should return 0.0 if there are no rates for the user' do
13
+ @rate.destroy
14
+ assert_equal 0.0, @time_entry.cost
15
+ end
16
+
17
+ context 'should return the product of hours by' do
18
+ should 'the results of Rate.amount_for' do
19
+ assert_equal((200.0 * @time_entry.hours), @time_entry.cost)
20
+ end
21
+
22
+ should 'the assigned rate' do
23
+ rate = Rate.generate!(:user => @user, :project => @project, :date_in_effect => @date, :amount => 100.0)
24
+ @time_entry.rate = rate
25
+ assert_equal rate.amount * @time_entry.hours, @time_entry.cost
26
+ end
27
+
28
+ end
29
+
30
+ context "#cost" do
31
+ setup do
32
+ @time_entry.save!
33
+ end
34
+
35
+ context "without a cache" do
36
+ should "return the calculated cost" do
37
+ @time_entry.update_attribute(:cost, nil)
38
+ assert_equal 2000.0, @time_entry.cost
39
+ end
40
+
41
+ should "cache the cost to the field" do
42
+ @time_entry.update_attribute(:cost, nil)
43
+ @time_entry.cost
44
+
45
+ assert_equal 2000.0, @time_entry.read_attribute(:cost)
46
+ assert_equal 2000.0, @time_entry.reload.read_attribute(:cost)
47
+ end
48
+
49
+ end
50
+
51
+ context "with a cache" do
52
+ setup do
53
+ @time_entry.update_attribute(:cost, 2000.0)
54
+ @time_entry.reload
55
+ end
56
+
57
+ should "return the cached cost" do
58
+ assert_equal 2000.0, @time_entry.read_attribute(:cost)
59
+ assert_equal 2000.0, @time_entry.cost
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ context "before save" do
67
+ should "clear and recalculate the cache" do
68
+ assert_equal nil, @time_entry.read_attribute(:cost)
69
+
70
+ assert @time_entry.save
71
+
72
+ assert_equal 2000.0, @time_entry.read_attribute(:cost)
73
+ end
74
+ end
75
+
76
+
77
+ end
@@ -0,0 +1,37 @@
1
+ require File.dirname(__FILE__) + '/../../test_helper'
2
+
3
+ class UsersHelperWrapper
4
+ include UsersHelper
5
+ end
6
+
7
+ class RateUsersHelperPatchTest < ActiveSupport::TestCase
8
+ should 'should return 3 tabs' do
9
+ helper = UsersHelperWrapper.new
10
+ assert_equal 3, helper.user_settings_tabs.length
11
+ end
12
+
13
+ should 'should include a rate tab at the end' do
14
+ helper = UsersHelperWrapper.new
15
+ rate_tab = helper.user_settings_tabs[-1]
16
+ assert_not_nil rate_tab
17
+ end
18
+
19
+ context 'rate tab' do
20
+ setup do
21
+ helper = UsersHelperWrapper.new
22
+ @rate_tab = helper.user_settings_tabs[-1]
23
+ end
24
+
25
+ should 'should have the name of "rates"' do
26
+ assert_equal 'rates', @rate_tab[:name]
27
+ end
28
+
29
+ should 'should use the rates partial' do
30
+ assert_equal 'users/rates', @rate_tab[:partial]
31
+ end
32
+
33
+ should 'should use the i18n rates label' do
34
+ assert_equal :rate_label_rate_history, @rate_tab[:label]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/../../../../test_helper'
2
+
3
+ class RedmineRate::Hooks::PluginTimesheetViewTimesheetsReportHeaderTagsTest < ActionController::TestCase
4
+ include Redmine::Hook::Helper
5
+
6
+ def controller
7
+ @controller ||= ApplicationController.new
8
+ @controller.response ||= ActionController::TestResponse.new
9
+ @controller
10
+ end
11
+
12
+ def request
13
+ @request ||= ActionController::TestRequest.new
14
+ end
15
+
16
+ def hook(args={})
17
+ call_hook :plugin_timesheet_view_timesheets_report_header_tags, args
18
+ end
19
+
20
+ context "#plugin_timesheet_view_timesheets_report_header_tags" do
21
+ should "return a css string" do
22
+ @response.body = hook
23
+ assert_select "style", :text => /missing-rate/
24
+ end
25
+ end
26
+ end