@@ -0,0 +1,69 | |||||
|
1 | # Redmine - project management software | |||
|
2 | # Copyright (C) 2006-2013 Jean-Philippe Lang | |||
|
3 | # | |||
|
4 | # This program is free software; you can redistribute it and/or | |||
|
5 | # modify it under the terms of the GNU General Public License | |||
|
6 | # as published by the Free Software Foundation; either version 2 | |||
|
7 | # of the License, or (at your option) any later version. | |||
|
8 | # | |||
|
9 | # This program is distributed in the hope that it will be useful, | |||
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
12 | # GNU General Public License for more details. | |||
|
13 | # | |||
|
14 | # You should have received a copy of the GNU General Public License | |||
|
15 | # along with this program; if not, write to the Free Software | |||
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
|
17 | ||||
|
18 | require File.expand_path('../../../test_helper', __FILE__) | |||
|
19 | ||||
|
20 | class WatchersHelperTest < ActionView::TestCase | |||
|
21 | include WatchersHelper | |||
|
22 | include Redmine::I18n | |||
|
23 | ||||
|
24 | fixtures :users, :issues | |||
|
25 | ||||
|
26 | def setup | |||
|
27 | super | |||
|
28 | set_language_if_valid('en') | |||
|
29 | User.current = nil | |||
|
30 | end | |||
|
31 | ||||
|
32 | test '#watcher_link with a non-watched object' do | |||
|
33 | expected = link_to( | |||
|
34 | "Watch", | |||
|
35 | "/watchers/watch?object_id=1&object_type=issue", | |||
|
36 | :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off" | |||
|
37 | ) | |||
|
38 | assert_equal expected, watcher_link(Issue.find(1), User.find(1)) | |||
|
39 | end | |||
|
40 | ||||
|
41 | test '#watcher_link with a single objet array' do | |||
|
42 | expected = link_to( | |||
|
43 | "Watch", | |||
|
44 | "/watchers/watch?object_id=1&object_type=issue", | |||
|
45 | :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav-off" | |||
|
46 | ) | |||
|
47 | assert_equal expected, watcher_link([Issue.find(1)], User.find(1)) | |||
|
48 | end | |||
|
49 | ||||
|
50 | test '#watcher_link with a multiple objets array' do | |||
|
51 | expected = link_to( | |||
|
52 | "Watch", | |||
|
53 | "/watchers/watch?object_id%5B%5D=1&object_id%5B%5D=3&object_type=issue", | |||
|
54 | :remote => true, :method => 'post', :class => "issue-bulk-watcher icon icon-fav-off" | |||
|
55 | ) | |||
|
56 | assert_equal expected, watcher_link([Issue.find(1), Issue.find(3)], User.find(1)) | |||
|
57 | end | |||
|
58 | ||||
|
59 | test '#watcher_link with a watched object' do | |||
|
60 | Watcher.create!(:watchable => Issue.find(1), :user => User.find(1)) | |||
|
61 | ||||
|
62 | expected = link_to( | |||
|
63 | "Unwatch", | |||
|
64 | "/watchers/unwatch?object_id=1&object_type=issue", | |||
|
65 | :remote => true, :method => 'post', :class => "issue-1-watcher icon icon-fav" | |||
|
66 | ) | |||
|
67 | assert_equal expected, watcher_link(Issue.find(1), User.find(1)) | |||
|
68 | end | |||
|
69 | end |
@@ -16,23 +16,19 | |||||
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | class WatchersController < ApplicationController |
|
18 | class WatchersController < ApplicationController | |
19 | before_filter :find_project |
|
19 | before_filter :require_login, :find_watchables, :only => [:watch, :unwatch] | |
20 | before_filter :require_login, :check_project_privacy, :only => [:watch, :unwatch] |
|
|||
21 | before_filter :authorize, :only => [:new, :destroy] |
|
|||
22 | accept_api_auth :create, :destroy |
|
|||
23 |
|
20 | |||
24 | def watch |
|
21 | def watch | |
25 | if @watched.respond_to?(:visible?) && !@watched.visible?(User.current) |
|
22 | set_watcher(@watchables, User.current, true) | |
26 | render_403 |
|
|||
27 | else |
|
|||
28 | set_watcher(User.current, true) |
|
|||
29 | end |
|
|||
30 | end |
|
23 | end | |
31 |
|
24 | |||
32 | def unwatch |
|
25 | def unwatch | |
33 | set_watcher(User.current, false) |
|
26 | set_watcher(@watchables, User.current, false) | |
34 | end |
|
27 | end | |
35 |
|
28 | |||
|
29 | before_filter :find_project, :authorize, :only => [:new, :create, :append, :destroy, :autocomplete_for_user] | |||
|
30 | accept_api_auth :create, :destroy | |||
|
31 | ||||
36 | def new |
|
32 | def new | |
37 | end |
|
33 | end | |
38 |
|
34 | |||
@@ -77,7 +73,8 class WatchersController < ApplicationController | |||||
77 | render :layout => false |
|
73 | render :layout => false | |
78 | end |
|
74 | end | |
79 |
|
75 | |||
80 | private |
|
76 | private | |
|
77 | ||||
81 | def find_project |
|
78 | def find_project | |
82 | if params[:object_type] && params[:object_id] |
|
79 | if params[:object_type] && params[:object_id] | |
83 | klass = Object.const_get(params[:object_type].camelcase) |
|
80 | klass = Object.const_get(params[:object_type].camelcase) | |
@@ -91,11 +88,22 private | |||||
91 | render_404 |
|
88 | render_404 | |
92 | end |
|
89 | end | |
93 |
|
90 | |||
94 | def set_watcher(user, watching) |
|
91 | def find_watchables | |
95 | @watched.set_watcher(user, watching) |
|
92 | klass = Object.const_get(params[:object_type].camelcase) rescue nil | |
|
93 | if klass && klass.respond_to?('watched_by') | |||
|
94 | @watchables = klass.find_all_by_id(Array.wrap(params[:object_id])) | |||
|
95 | raise Unauthorized if @watchables.any? {|w| w.respond_to?(:visible?) && !w.visible?} | |||
|
96 | end | |||
|
97 | render_404 unless @watchables.present? | |||
|
98 | end | |||
|
99 | ||||
|
100 | def set_watcher(watchables, user, watching) | |||
|
101 | watchables.each do |watchable| | |||
|
102 | watchable.set_watcher(user, watching) | |||
|
103 | end | |||
96 | respond_to do |format| |
|
104 | respond_to do |format| | |
97 | format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} |
|
105 | format.html { redirect_to_referer_or {render :text => (watching ? 'Watcher added.' : 'Watcher removed.'), :layout => true}} | |
98 |
format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => |
|
106 | format.js { render :partial => 'set_watcher', :locals => {:user => user, :watched => watchables} } | |
99 | end |
|
107 | end | |
100 | end |
|
108 | end | |
101 | end |
|
109 | end |
@@ -24,21 +24,28 module WatchersHelper | |||||
24 | watcher_link(object, user) |
|
24 | watcher_link(object, user) | |
25 | end |
|
25 | end | |
26 |
|
26 | |||
27 | def watcher_link(object, user) |
|
27 | def watcher_link(objects, user) | |
28 |
return '' unless user && user.logged? |
|
28 | return '' unless user && user.logged? | |
29 | watched = object.watched_by?(user) |
|
29 | objects = Array.wrap(objects) | |
30 | css = [watcher_css(object), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') |
|
30 | ||
31 | url = {:controller => 'watchers', |
|
31 | watched = objects.any? {|object| object.watched_by?(user)} | |
32 | :action => (watched ? 'unwatch' : 'watch'), |
|
32 | css = [watcher_css(objects), watched ? 'icon icon-fav' : 'icon icon-fav-off'].join(' ') | |
33 | :object_type => object.class.to_s.underscore, |
|
33 | text = watched ? l(:button_unwatch) : l(:button_watch) | |
34 | :object_id => object.id} |
|
34 | url = { | |
35 | link_to((watched ? l(:button_unwatch) : l(:button_watch)), url, |
|
35 | :controller => 'watchers', | |
36 | :remote => true, :method => 'post', :class => css) |
|
36 | :action => (watched ? 'unwatch' : 'watch'), | |
|
37 | :object_type => objects.first.class.to_s.underscore, | |||
|
38 | :object_id => (objects.size == 1 ? objects.first.id : objects.map(&:id).sort) | |||
|
39 | } | |||
|
40 | ||||
|
41 | link_to text, url, :remote => true, :method => 'post', :class => css | |||
37 | end |
|
42 | end | |
38 |
|
43 | |||
39 | # Returns the css class used to identify watch links for a given +object+ |
|
44 | # Returns the css class used to identify watch links for a given +object+ | |
40 | def watcher_css(object) |
|
45 | def watcher_css(objects) | |
41 | "#{object.class.to_s.underscore}-#{object.id}-watcher" |
|
46 | objects = Array.wrap(objects) | |
|
47 | id = (objects.size == 1 ? objects.first.id : 'bulk') | |||
|
48 | "#{objects.first.class.to_s.underscore}-#{id}-watcher" | |||
42 | end |
|
49 | end | |
43 |
|
50 | |||
44 | # Returns a comma separated list of users watching the given object |
|
51 | # Returns a comma separated list of users watching the given object |
@@ -117,10 +117,11 | |||||
117 | </li> |
|
117 | </li> | |
118 | <% end %> |
|
118 | <% end %> | |
119 |
|
119 | |||
|
120 | <% if User.current.logged? %> | |||
|
121 | <li><%= watcher_link(@issues, User.current) %></li> | |||
|
122 | <% end %> | |||
|
123 | ||||
120 | <% if @issue.present? %> |
|
124 | <% if @issue.present? %> | |
121 | <% if User.current.logged? %> |
|
|||
122 | <li><%= watcher_link(@issue, User.current) %></li> |
|
|||
123 | <% end %> |
|
|||
124 | <% if @can[:log_time] -%> |
|
125 | <% if @can[:log_time] -%> | |
125 | <li><%= context_menu_link l(:button_log_time), new_issue_time_entry_path(@issue), |
|
126 | <li><%= context_menu_link l(:button_log_time), new_issue_time_entry_path(@issue), | |
126 | :class => 'icon-time-add' %></li> |
|
127 | :class => 'icon-time-add' %></li> |
@@ -2,8 +2,9 | |||||
2 |
|
2 | |||
3 | <%= form_tag({:controller => 'watchers', |
|
3 | <%= form_tag({:controller => 'watchers', | |
4 | :action => (watched ? 'create' : 'append'), |
|
4 | :action => (watched ? 'create' : 'append'), | |
5 | :object_type => watched.class.name.underscore, |
|
5 | :object_type => (watched && watched.class.name.underscore), | |
6 |
:object_id => watched |
|
6 | :object_id => watched, | |
|
7 | :project_id => @project}, | |||
7 | :remote => true, |
|
8 | :remote => true, | |
8 | :method => :post, |
|
9 | :method => :post, | |
9 | :id => 'new-watcher-form') do %> |
|
10 | :id => 'new-watcher-form') do %> | |
@@ -11,8 +12,9 | |||||
11 | <p><%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p> |
|
12 | <p><%= label_tag 'user_search', l(:label_user_search) %><%= text_field_tag 'user_search', nil %></p> | |
12 | <%= javascript_tag "observeSearchfield('user_search', 'users_for_watcher', '#{ escape_javascript url_for(:controller => 'watchers', |
|
13 | <%= javascript_tag "observeSearchfield('user_search', 'users_for_watcher', '#{ escape_javascript url_for(:controller => 'watchers', | |
13 | :action => 'autocomplete_for_user', |
|
14 | :action => 'autocomplete_for_user', | |
14 | :object_type => watched.class.name.underscore, |
|
15 | :object_type => (watched && watched.class.name.underscore), | |
15 |
:object_id => watched |
|
16 | :object_id => watched, | |
|
17 | :project_id => @project) }')" %> | |||
16 |
|
18 | |||
17 | <div id="users_for_watcher"> |
|
19 | <div id="users_for_watcher"> | |
18 | <%= principals_check_box_tags 'watcher[user_ids][]', (watched ? watched.addable_watcher_users : User.active.all(:limit => 100)) %> |
|
20 | <%= principals_check_box_tags 'watcher[user_ids][]', (watched ? watched.addable_watcher_users : User.active.all(:limit => 100)) %> |
@@ -127,7 +127,7 Redmine::AccessControl.map do |map| | |||||
127 | map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin |
|
127 | map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin | |
128 | # Watchers |
|
128 | # Watchers | |
129 | map.permission :view_issue_watchers, {}, :read => true |
|
129 | map.permission :view_issue_watchers, {}, :read => true | |
130 | map.permission :add_issue_watchers, {:watchers => :new} |
|
130 | map.permission :add_issue_watchers, {:watchers => [:new, :create, :append, :autocomplete_for_user]} | |
131 | map.permission :delete_issue_watchers, {:watchers => :destroy} |
|
131 | map.permission :delete_issue_watchers, {:watchers => :destroy} | |
132 | end |
|
132 | end | |
133 |
|
133 |
@@ -25,7 +25,7 class WatchersControllerTest < ActionController::TestCase | |||||
25 | User.current = nil |
|
25 | User.current = nil | |
26 | end |
|
26 | end | |
27 |
|
27 | |||
28 | def test_watch |
|
28 | def test_watch_a_single_object | |
29 | @request.session[:user_id] = 3 |
|
29 | @request.session[:user_id] = 3 | |
30 | assert_difference('Watcher.count') do |
|
30 | assert_difference('Watcher.count') do | |
31 | xhr :post, :watch, :object_type => 'issue', :object_id => '1' |
|
31 | xhr :post, :watch, :object_type => 'issue', :object_id => '1' | |
@@ -35,6 +35,27 class WatchersControllerTest < ActionController::TestCase | |||||
35 | assert Issue.find(1).watched_by?(User.find(3)) |
|
35 | assert Issue.find(1).watched_by?(User.find(3)) | |
36 | end |
|
36 | end | |
37 |
|
37 | |||
|
38 | def test_watch_a_collection_with_a_single_object | |||
|
39 | @request.session[:user_id] = 3 | |||
|
40 | assert_difference('Watcher.count') do | |||
|
41 | xhr :post, :watch, :object_type => 'issue', :object_id => ['1'] | |||
|
42 | assert_response :success | |||
|
43 | assert_include '$(".issue-1-watcher")', response.body | |||
|
44 | end | |||
|
45 | assert Issue.find(1).watched_by?(User.find(3)) | |||
|
46 | end | |||
|
47 | ||||
|
48 | def test_watch_a_collection_with_multiple_objects | |||
|
49 | @request.session[:user_id] = 3 | |||
|
50 | assert_difference('Watcher.count', 2) do | |||
|
51 | xhr :post, :watch, :object_type => 'issue', :object_id => ['1', '3'] | |||
|
52 | assert_response :success | |||
|
53 | assert_include '$(".issue-bulk-watcher")', response.body | |||
|
54 | end | |||
|
55 | assert Issue.find(1).watched_by?(User.find(3)) | |||
|
56 | assert Issue.find(3).watched_by?(User.find(3)) | |||
|
57 | end | |||
|
58 | ||||
38 | def test_watch_should_be_denied_without_permission |
|
59 | def test_watch_should_be_denied_without_permission | |
39 | Role.find(2).remove_permission! :view_issues |
|
60 | Role.find(2).remove_permission! :view_issues | |
40 | @request.session[:user_id] = 3 |
|
61 | @request.session[:user_id] = 3 | |
@@ -70,6 +91,20 class WatchersControllerTest < ActionController::TestCase | |||||
70 | assert !Issue.find(1).watched_by?(User.find(3)) |
|
91 | assert !Issue.find(1).watched_by?(User.find(3)) | |
71 | end |
|
92 | end | |
72 |
|
93 | |||
|
94 | def test_unwatch_a_collection_with_multiple_objects | |||
|
95 | @request.session[:user_id] = 3 | |||
|
96 | Watcher.create!(:user_id => 3, :watchable => Issue.find(1)) | |||
|
97 | Watcher.create!(:user_id => 3, :watchable => Issue.find(3)) | |||
|
98 | ||||
|
99 | assert_difference('Watcher.count', -2) do | |||
|
100 | xhr :post, :unwatch, :object_type => 'issue', :object_id => ['1', '3'] | |||
|
101 | assert_response :success | |||
|
102 | assert_include '$(".issue-bulk-watcher")', response.body | |||
|
103 | end | |||
|
104 | assert !Issue.find(1).watched_by?(User.find(3)) | |||
|
105 | assert !Issue.find(3).watched_by?(User.find(3)) | |||
|
106 | end | |||
|
107 | ||||
73 | def test_new |
|
108 | def test_new | |
74 | @request.session[:user_id] = 2 |
|
109 | @request.session[:user_id] = 2 | |
75 | xhr :get, :new, :object_type => 'issue', :object_id => '2' |
|
110 | xhr :get, :new, :object_type => 'issue', :object_id => '2' | |
@@ -77,7 +112,7 class WatchersControllerTest < ActionController::TestCase | |||||
77 | assert_match /ajax-modal/, response.body |
|
112 | assert_match /ajax-modal/, response.body | |
78 | end |
|
113 | end | |
79 |
|
114 | |||
80 | def test_new_for_new_record_with_id |
|
115 | def test_new_for_new_record_with_project_id | |
81 | @request.session[:user_id] = 2 |
|
116 | @request.session[:user_id] = 2 | |
82 | xhr :get, :new, :project_id => 1 |
|
117 | xhr :get, :new, :project_id => 1 | |
83 | assert_response :success |
|
118 | assert_response :success | |
@@ -85,7 +120,7 class WatchersControllerTest < ActionController::TestCase | |||||
85 | assert_match /ajax-modal/, response.body |
|
120 | assert_match /ajax-modal/, response.body | |
86 | end |
|
121 | end | |
87 |
|
122 | |||
88 | def test_new_for_new_record_with_identifier |
|
123 | def test_new_for_new_record_with_project_identifier | |
89 | @request.session[:user_id] = 2 |
|
124 | @request.session[:user_id] = 2 | |
90 | xhr :get, :new, :project_id => 'ecookbook' |
|
125 | xhr :get, :new, :project_id => 'ecookbook' | |
91 | assert_response :success |
|
126 | assert_response :success | |
@@ -117,7 +152,8 class WatchersControllerTest < ActionController::TestCase | |||||
117 | end |
|
152 | end | |
118 |
|
153 | |||
119 | def test_autocomplete_on_watchable_creation |
|
154 | def test_autocomplete_on_watchable_creation | |
120 | xhr :get, :autocomplete_for_user, :q => 'mi' |
|
155 | @request.session[:user_id] = 2 | |
|
156 | xhr :get, :autocomplete_for_user, :q => 'mi', :project_id => 'ecookbook' | |||
121 | assert_response :success |
|
157 | assert_response :success | |
122 | assert_select 'input', :count => 4 |
|
158 | assert_select 'input', :count => 4 | |
123 | assert_select 'input[name=?][value=1]', 'watcher[user_ids][]' |
|
159 | assert_select 'input[name=?][value=1]', 'watcher[user_ids][]' | |
@@ -127,7 +163,8 class WatchersControllerTest < ActionController::TestCase | |||||
127 | end |
|
163 | end | |
128 |
|
164 | |||
129 | def test_autocomplete_on_watchable_update |
|
165 | def test_autocomplete_on_watchable_update | |
130 | xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue' |
|
166 | @request.session[:user_id] = 2 | |
|
167 | xhr :get, :autocomplete_for_user, :q => 'mi', :object_id => '2' , :object_type => 'issue', :project_id => 'ecookbook' | |||
131 | assert_response :success |
|
168 | assert_response :success | |
132 | assert_select 'input', :count => 3 |
|
169 | assert_select 'input', :count => 3 | |
133 | assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' |
|
170 | assert_select 'input[name=?][value=2]', 'watcher[user_ids][]' | |
@@ -139,7 +176,7 class WatchersControllerTest < ActionController::TestCase | |||||
139 | def test_append |
|
176 | def test_append | |
140 | @request.session[:user_id] = 2 |
|
177 | @request.session[:user_id] = 2 | |
141 | assert_no_difference 'Watcher.count' do |
|
178 | assert_no_difference 'Watcher.count' do | |
142 | xhr :post, :append, :watcher => {:user_ids => ['4', '7']} |
|
179 | xhr :post, :append, :watcher => {:user_ids => ['4', '7']}, :project_id => 'ecookbook' | |
143 | assert_response :success |
|
180 | assert_response :success | |
144 | assert_include 'watchers_inputs', response.body |
|
181 | assert_include 'watchers_inputs', response.body | |
145 | assert_include 'issue[watcher_user_ids][]', response.body |
|
182 | assert_include 'issue[watcher_user_ids][]', response.body |
General Comments 0
You need to be logged in to leave comments.
Login now