Index: test/fixtures/groups_users.yml =================================================================== --- test/fixtures/groups_users.yml (revision 24239) +++ test/fixtures/groups_users.yml (working copy) @@ -5,3 +5,6 @@ groups_users_002: group_id: 11 user_id: 8 +groups_users_003: + group_id: 10 + user_id: 9 \ No newline at end of file Index: test/functional/context_menus_controller_test.rb =================================================================== --- test/functional/context_menus_controller_test.rb (revision 24239) +++ test/functional/context_menus_controller_test.rb (working copy) @@ -314,7 +314,7 @@ def test_context_menu_should_propose_shared_versions_for_issues_from_different_projects @request.session[:user_id] = 2 - version = Version.create!(:name => 'Shared', :sharing => 'system', :project_id => 1) + Version.create!(:name => 'Shared', :sharing => 'system', :project_id => 1) get( :issues, @@ -522,4 +522,108 @@ assert_select 'a.icon-del', :count => 0 end end + + def test_context_menu_multiple_users_should_show_add_user_to_group_links + get :users, params: { ids: [1, 2] } + assert_response :success + + assert_select 'a.submenu', text: I18n.t(:label_add_user_to_group), count: 1 + assert_select 'a.submenu', text: I18n.t(:label_remove_user_from_group), count: 0 + + assert_select 'a.icon.icon-lock span.icon-label', text: 'Lock', count: 1 + assert_select 'a.icon.icon-del span.icon-label', text: 'Delete', count: 1 + + assert_select 'li.folder' do |folders| + rem_folder = folders.find { |li| li.at_css('> a.submenu')&.text&.strip == I18n.t(:label_add_user_to_group) } + assert rem_folder, "a.submenu '#{I18n.t(:label_add_user_to_group)}' not found" + if rem_folder + assert_select rem_folder, '> ul > li', count: 2 + assert_select rem_folder, "ul > li > a[rel='nofollow'][data-method='post']", count: 2 + end + end + end + + def test_context_menu_multiple_users_should_show_remove_add_user_from_group_links + get :users, params: { ids: [1, 8] } + assert_response :success + + assert_select 'a.icon.icon-lock span.icon-label', text: 'Lock', count: 1 + assert_select 'a.icon.icon-del span.icon-label', text: 'Delete', count: 1 + + assert_select 'a.submenu', text: I18n.t(:label_add_user_to_group), count: 1 + assert_select 'a.submenu', text: I18n.t(:label_remove_user_from_group), count: 1 + + assert_select 'li.folder' do |folders| + rem_folder = folders.find { |li| li.at_css('> a.submenu')&.text&.strip == I18n.t(:label_add_user_to_group) } + assert rem_folder, "a.submenu '#{I18n.t(:label_add_user_to_group)}' not found" + if rem_folder + assert_select rem_folder, '> ul > li', count: 2 + assert_select rem_folder, "ul > li > a[rel='nofollow'][data-method='post']", count: 2 + end + + rem_folder = folders.find { |li| li.at_css('> a.submenu')&.text&.strip == I18n.t(:label_remove_user_from_group) } + assert rem_folder, "a.submenu '#{I18n.t(:label_remove_user_from_group)}' not found" + if rem_folder + assert_select rem_folder, '> ul > li', count: 2 + assert_select rem_folder, "ul > li > a[rel='nofollow'][data-method='delete']", count: 2 + end + end + end + + def test_context_menu_single_user_should_show_remove_user_from_group_links + get :users, params: { ids: [8] } + assert_response :success + + assert_select 'a.submenu', text: I18n.t(:label_remove_user_from_group), count: 1 + assert_select 'a.submenu', text: I18n.t(:label_add_user_to_group), count: 0 + + assert_select 'a.icon.icon-lock span.icon-label', text: 'Lock', count: 1 + assert_select 'a.icon.icon-edit span.icon-label', text: 'Edit', count: 1 + assert_select 'a.icon.icon-del span.icon-label', text: 'Delete', count: 1 + + assert_select 'li.folder' do |folders| + rem_folder = folders.find { |li| li.at_css('> a.submenu')&.text&.strip == I18n.t(:label_remove_user_from_group) } + assert rem_folder, "a.submenu '#{I18n.t(:label_remove_user_from_group)}' not found" + if rem_folder + assert_select rem_folder, '> ul > li', count: 2 + assert_select rem_folder, "ul > li > a[rel='nofollow'][data-method='delete']", count: 2 + assert_select rem_folder, 'a[data-confirm]' do |links| + value = links.first['data-confirm'] + assert value.start_with?('Are you sure you want to remove 1 user') + end + end + end + end + + def test_context_menu_mulitple_user_should_show_remove_add_user_from_group_links + get :users, params: { ids: [8, 9] } + assert_response :success + + assert_select 'a.icon.icon-edit span.icon-label', text: 'Edit', count: 0 + assert_select 'a.icon.icon-lock span.icon-label', text: 'Lock', count: 1 + assert_select 'a.icon.icon-del span.icon-label', text: 'Delete', count: 1 + + assert_select 'a.submenu', text: I18n.t(:label_remove_user_from_group), count: 1 + assert_select 'a.submenu', text: I18n.t(:label_add_user_to_group), count: 1 + + assert_select 'li.folder' do |folders| + rem_folder = folders.find { |li| li.at_css('> a.submenu')&.text&.strip == I18n.t(:label_add_user_to_group) } + assert rem_folder, "a.submenu '#{I18n.t(:label_add_user_to_group)}' not found" + if rem_folder + assert_select rem_folder, '> ul > li', count: 1 + assert_select rem_folder, "ul > li > a[rel='nofollow'][data-method='post']", count: 1 + end + + rem_folder = folders.find { |li| li.at_css('> a.submenu')&.text&.strip == I18n.t(:label_remove_user_from_group) } + assert rem_folder, "a.submenu '#{I18n.t(:label_remove_user_from_group)}' not found" + if rem_folder + assert_select rem_folder, '> ul > li', count: 2 + assert_select rem_folder, "ul > li > a[rel='nofollow'][data-method='delete']", count: 2 + assert_select rem_folder, 'a[data-confirm]' do |links| + value = links.first['data-confirm'] + assert value.start_with?('Are you sure you want to remove 2 users') + end + end + end + end end Index: test/functional/groups_controller_test.rb =================================================================== --- test/functional/groups_controller_test.rb (revision 24239) +++ test/functional/groups_controller_test.rb (working copy) @@ -247,10 +247,10 @@ assert_match /John Smith/, response.body end - def test_remove_user + def test_remove_users assert_difference 'Group.find(10).users.count', -1 do delete( - :remove_user, + :remove_users, :params => { :id => 10, :user_id => '8' @@ -259,10 +259,10 @@ end end - def test_xhr_remove_user + def test_xhr_remove_users assert_difference 'Group.find(10).users.count', -1 do delete( - :remove_user, + :remove_users, :params => { :id => 10, :user_id => '8' @@ -274,6 +274,33 @@ end end + def test_remove_users_patch + assert_difference 'Group.find(10).users.count', -2 do + delete( + :remove_users, + :params => { + :id => 10, + :user_ids => ['8', '9'] + } + ) + end + end + + def test_xhr_remove_users_patch + assert_difference 'Group.find(10).users.count', -2 do + delete( + :remove_users, + :params => { + :id => 10, + :user_ids => ['8', '9'] + }, + :xhr => true + ) + assert_response :success + assert_equal 'text/javascript', response.media_type + end + end + def test_autocomplete_for_user get( :autocomplete_for_user, Index: test/functional/issues_controller_test.rb =================================================================== --- test/functional/issues_controller_test.rb (revision 24239) +++ test/functional/issues_controller_test.rb (working copy) @@ -4641,7 +4641,7 @@ assert issue.watched_by?(User.find(3)) assert issue.watched_by?(Group.find(10)) # Watchers notified - assert_equal 3, ActionMailer::Base.deliveries.size + assert_equal 4, ActionMailer::Base.deliveries.size mail = ActionMailer::Base.deliveries[1] assert [mail.to].flatten.include?(User.find(3).mail) mail = ActionMailer::Base.deliveries[2] Index: test/integration/api_test/api_routing_test.rb =================================================================== --- test/integration/api_test/api_routing_test.rb (revision 24239) +++ test/integration/api_test/api_routing_test.rb (working copy) @@ -51,7 +51,8 @@ def test_group_users should_route 'POST /groups/567/users' => 'groups#add_users', :id => '567' - should_route 'DELETE /groups/567/users/12' => 'groups#remove_user', :id => '567', :user_id => '12' + should_route 'DELETE /groups/567/users/12' => 'groups#remove_users', :id => '567', :user_id => '12' + should_route 'DELETE /groups/567/users' => 'groups#remove_users', :id => '567' end def test_issue_categories Index: test/integration/api_test/groups_test.rb =================================================================== --- test/integration/api_test/groups_test.rb (revision 24239) +++ test/integration/api_test/groups_test.rb (working copy) @@ -237,4 +237,40 @@ end assert_not_include User.find(8), group.reload.users end + + test "DELETE /groups/:id/users.xml should remove users from the group" do + group = Group.generate! + group.users << User.find(5) + group.users << User.find(6) + assert_difference 'group.reload.users.count', -2 do + delete( + "/groups/#{group.id}/users.xml", + :params => {:user_ids => [5, 6]}, + :headers => credentials('admin') + ) + assert_response :no_content + assert_equal '', @response.body + end + assert_not_include User.find(5), group.reload.users + assert_not_include User.find(6), group.reload.users + end + + test "DELETE /groups/:id/users.xml removes users and skips unknown user IDs" do + group = Group.generate! + group.users << User.find(5) + group.users << User.find(6) + group.users << User.find(7) + assert_difference 'group.reload.users.count', -2 do + delete( + "/groups/#{group.id}/users.xml", + :params => {:user_ids => [5, 6, 8]}, + :headers => credentials('admin') + ) + assert_response :no_content + assert_equal '', @response.body + end + assert_not_include User.find(5), group.reload.users + assert_not_include User.find(6), group.reload.users + assert_include User.find(7), group.reload.users + end end Index: test/integration/api_test/users_test.rb =================================================================== --- test/integration/api_test/users_test.rb (revision 24239) +++ test/integration/api_test/users_test.rb (working copy) @@ -107,11 +107,11 @@ assert_response :success json = ActiveSupport::JSON.decode(response.body) assert json.key?('users') - assert_equal 1, json['users'].size + assert_equal 2, json['users'].size assert_equal 8, json['users'][0]['id'] # there should be an implicit filter for status = 1 - User.where(id: [2, 8]).update_all status: 3 + User.where(id: [2, 8, 9]).update_all status: 3 get '/users.json', headers: credentials('admin'), params: { name: 'jsmith' } assert_response :success Index: test/integration/routing/groups_test.rb =================================================================== --- test/integration/routing/groups_test.rb (revision 24239) +++ test/integration/routing/groups_test.rb (working copy) @@ -37,6 +37,7 @@ def test_group_users should_route 'GET /groups/567/users/new' => 'groups#new_users', :id => '567' should_route 'POST /groups/567/users' => 'groups#add_users', :id => '567' - should_route 'DELETE /groups/567/users/12' => 'groups#remove_user', :id => '567', :user_id => '12' + should_route 'DELETE /groups/567/users/12' => 'groups#remove_users', :id => '567', :user_id => '12' + should_route 'DELETE /groups/567/users' => 'groups#remove_users', :id => '567' end end Index: test/unit/user_query_test.rb =================================================================== --- test/unit/user_query_test.rb (revision 24239) +++ test/unit/user_query_test.rb (working copy) @@ -132,7 +132,7 @@ q = UserQuery.new name: '_' q.add_filter('is_member_of_group', '=', ['10', '99']) users = find_users_with_query q - assert_equal [8], users.map(&:id) + assert_equal [8, 9], users.map(&:id) end def test_group_filter_not @@ -147,7 +147,7 @@ q = UserQuery.new name: '_' q.add_filter('is_member_of_group', '*', ['']) users = find_users_with_query q - assert_equal [8], users.map(&:id) + assert_equal [8, 9], users.map(&:id) end def test_group_filter_none