From 718fcba6d870140987259d75d25b8792be343778 Mon Sep 17 00:00:00 2001 From: taitus Date: Wed, 20 Sep 2023 16:10:36 +0200 Subject: [PATCH] Allow undo votes in comments votes component --- .../comments/votes_component.html.erb | 8 +++---- app/components/comments/votes_component.rb | 24 ++++++++++++++++++- app/controllers/comments/votes_controller.rb | 11 ++++++++- app/models/abilities/common.rb | 2 +- config/routes/comment.rb | 2 +- .../comments/votes_component_spec.rb | 20 ++++++++++++---- .../comments/votes_controller_spec.rb | 19 +++++++++++++++ spec/models/abilities/common_spec.rb | 2 ++ spec/models/abilities/organization_spec.rb | 1 + .../comments/budget_investments_spec.rb | 6 ++--- spec/system/comments/debates_spec.rb | 6 ++--- .../comments/legislation_annotations_spec.rb | 6 ++--- .../comments/legislation_questions_spec.rb | 6 ++--- spec/system/comments/polls_spec.rb | 6 ++--- spec/system/comments/proposals_spec.rb | 6 ++--- spec/system/comments/topics_spec.rb | 12 +++++----- 16 files changed, 101 insertions(+), 36 deletions(-) diff --git a/app/components/comments/votes_component.html.erb b/app/components/comments/votes_component.html.erb index 47d23c276..41df0614e 100644 --- a/app/components/comments/votes_component.html.erb +++ b/app/components/comments/votes_component.html.erb @@ -4,8 +4,8 @@ <%= button_to vote_in_favor_against_path("yes"), - method: "post", - remote: can?(:create, comment.votes_for.new(voter: current_user)), + method: user_already_voted_with("yes") ? "delete" : "post", + remote: remote_submit("yes"), "aria-pressed": pressed?("yes"), title: t("votes.agree") do %> <%= t("votes.agree") %> @@ -15,8 +15,8 @@ <%= button_to vote_in_favor_against_path("no"), - method: "post", - remote: can?(:create, comment.votes_for.new(voter: current_user)), + method: user_already_voted_with("no") ? "delete" : "post", + remote: remote_submit("no"), "aria-pressed": pressed?("no"), title: t("votes.disagree") do %> <%= t("votes.disagree") %> diff --git a/app/components/comments/votes_component.rb b/app/components/comments/votes_component.rb index 9b0dc94dc..143327f24 100644 --- a/app/components/comments/votes_component.rb +++ b/app/components/comments/votes_component.rb @@ -18,6 +18,28 @@ class Comments::VotesComponent < ApplicationComponent end def vote_in_favor_against_path(value) - comment_votes_path(comment, value: value) + if user_already_voted_with(value) + vote = comment.votes_for.find_by!(voter: current_user) + + comment_vote_path(comment, vote, value: value) + else + comment_votes_path(comment, value: value) + end + end + + def user_already_voted_with(value) + current_user&.voted_as_when_voted_for(comment) == parse_vote(value) + end + + def parse_vote(value) + value == "yes" ? true : false + end + + def remote_submit(value) + if user_already_voted_with(value) + can?(:destroy, comment.votes_for.new(voter: current_user)) + else + can?(:create, comment.votes_for.new(voter: current_user)) + end end end diff --git a/app/controllers/comments/votes_controller.rb b/app/controllers/comments/votes_controller.rb index a1f4b4c4a..d8289f9cf 100644 --- a/app/controllers/comments/votes_controller.rb +++ b/app/controllers/comments/votes_controller.rb @@ -1,7 +1,8 @@ module Comments class VotesController < ApplicationController - load_and_authorize_resource :comment before_action :authenticate_user! + load_and_authorize_resource :comment + load_and_authorize_resource through: :comment, through_association: :votes_for, only: :destroy before_action :verify_comments_open! def create @@ -13,6 +14,14 @@ module Comments end end + def destroy + @comment.unvote_by(current_user) + + respond_to do |format| + format.js { render :show } + end + end + private def verify_comments_open! diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 921fa0a16..e14469539 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -82,7 +82,7 @@ module Abilities unless user.organization? can [:create, :destroy], ActsAsVotable::Vote, voter_id: user.id, votable_type: "Debate" - can :create, ActsAsVotable::Vote, voter_id: user.id, votable_type: "Comment" + can [:create, :destroy], ActsAsVotable::Vote, voter_id: user.id, votable_type: "Comment" end if user.level_two_or_three_verified? diff --git a/config/routes/comment.rb b/config/routes/comment.rb index 39f387c4b..d461be5fb 100644 --- a/config/routes/comment.rb +++ b/config/routes/comment.rb @@ -5,5 +5,5 @@ resources :comments, only: [:create, :show] do put :hide end - resources :votes, controller: "comments/votes", only: :create + resources :votes, controller: "comments/votes", only: [:create, :destroy] end diff --git a/spec/components/comments/votes_component_spec.rb b/spec/components/comments/votes_component_spec.rb index 884a6765c..59708d71a 100644 --- a/spec/components/comments/votes_component_spec.rb +++ b/spec/components/comments/votes_component_spec.rb @@ -5,8 +5,8 @@ describe Comments::VotesComponent do let(:comment) { create(:comment, user: user) } let(:component) { Comments::VotesComponent.new(comment) } - describe "aria-pressed attribute" do - it "is true when the in-favor button is pressed" do + describe "aria-pressed and method attributes" do + it "have expected values when the in-favor button is pressed" do comment.vote_by(voter: user, vote: "yes") sign_in(user) @@ -14,14 +14,18 @@ describe Comments::VotesComponent do page.find(".in-favor") do |in_favor_block| expect(in_favor_block).to have_css "button[aria-pressed='true']" + expect(in_favor_block).to have_css "form[action*='votes'][method='post']" + expect(in_favor_block).to have_css "input[name='_method'][value='delete']", visible: :hidden end page.find(".against") do |against_block| expect(against_block).to have_css "button[aria-pressed='false']" + expect(against_block).to have_css "form[action*='votes'][method='post']" + expect(against_block).not_to have_css "input[name='_method']", visible: :all end end - it "is true when the against button is pressed" do + it "have expected values when the against button is pressed" do comment.vote_by(voter: user, vote: "no") sign_in(user) @@ -29,24 +33,32 @@ describe Comments::VotesComponent do page.find(".in-favor") do |in_favor_block| expect(in_favor_block).to have_css "button[aria-pressed='false']" + expect(in_favor_block).to have_css "form[action*='votes'][method='post']" + expect(in_favor_block).not_to have_css "input[name='_method']", visible: :all end page.find(".against") do |against_block| expect(against_block).to have_css "button[aria-pressed='true']" + expect(against_block).to have_css "form[action*='votes'][method='post']" + expect(against_block).to have_css "input[name='_method'][value='delete']", visible: :hidden end end - it "is false when neither the 'in-favor' button nor the 'against' button are pressed" do + it "have expected values when neither the 'in-favor' button nor the 'against' button are pressed" do sign_in(user) render_inline component page.find(".in-favor") do |in_favor_block| expect(in_favor_block).to have_css "button[aria-pressed='false']" + expect(in_favor_block).to have_css "form[action*='votes'][method='post']" + expect(in_favor_block).not_to have_css "input[name='_method']", visible: :all end page.find(".against") do |against_block| expect(against_block).to have_css "button[aria-pressed='false']" + expect(against_block).to have_css "form[action*='votes'][method='post']" + expect(against_block).not_to have_css "input[name='_method']", visible: :all end end end diff --git a/spec/controllers/comments/votes_controller_spec.rb b/spec/controllers/comments/votes_controller_spec.rb index edfbf6361..cc164423f 100644 --- a/spec/controllers/comments/votes_controller_spec.rb +++ b/spec/controllers/comments/votes_controller_spec.rb @@ -12,4 +12,23 @@ describe Comments::VotesController do end.to change { comment.reload.votes_for.size }.by(1) end end + + describe "DELETE destroy" do + let(:user) { create(:user) } + let!(:vote) { create(:vote, votable: comment, voter: user) } + + it "redirects unidentified users to the sign in page" do + delete :destroy, params: { comment_id: comment.id, id: vote } + + expect(response).to redirect_to new_user_session_path + end + + it "allows undoing a vote" do + sign_in user + + expect do + delete :destroy, xhr: true, params: { comment_id: comment.id, id: vote } + end.to change { comment.reload.votes_for.size }.by(-1) + end + end end diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb index 84f2ec341..392d4367b 100644 --- a/spec/models/abilities/common_spec.rb +++ b/spec/models/abilities/common_spec.rb @@ -115,6 +115,8 @@ describe Abilities::Common do it { should be_able_to(:create, Comment) } it { should be_able_to(:create, user.votes.build(votable: comment)) } it { should_not be_able_to(:create, another_user.votes.build(votable: comment)) } + it { should be_able_to(:destroy, user.votes.build(votable: comment)) } + it { should_not be_able_to(:destroy, another_user.votes.build(votable: comment)) } it { should be_able_to(:hide, own_comment) } it { should_not be_able_to(:hide, comment) } diff --git a/spec/models/abilities/organization_spec.rb b/spec/models/abilities/organization_spec.rb index bffea47b7..f38c60b14 100644 --- a/spec/models/abilities/organization_spec.rb +++ b/spec/models/abilities/organization_spec.rb @@ -24,6 +24,7 @@ describe "Abilities::Organization" do it { should be_able_to(:create, Comment) } it { should_not be_able_to(:create, user.votes.build(votable: comment)) } + it { should_not be_able_to(:destroy, user.votes.build(votable: comment)) } it { should_not be_able_to(:read, SDG::Target) } diff --git a/spec/system/comments/budget_investments_spec.rb b/spec/system/comments/budget_investments_spec.rb index f0c39c266..f7f643a6c 100644 --- a/spec/system/comments/budget_investments_spec.rb +++ b/spec/system/comments/budget_investments_spec.rb @@ -578,7 +578,7 @@ describe "Commenting Budget::Investments" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit budget_investment_path(budget, investment) within("#comment_#{comment.id}_votes") do @@ -591,14 +591,14 @@ describe "Commenting Budget::Investments" do click_button "I agree" within(".in-favor") do - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end diff --git a/spec/system/comments/debates_spec.rb b/spec/system/comments/debates_spec.rb index b6550b3d9..3242b8943 100644 --- a/spec/system/comments/debates_spec.rb +++ b/spec/system/comments/debates_spec.rb @@ -614,7 +614,7 @@ describe "Commenting debates" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit debate_path(debate) within("#comment_#{comment.id}_votes") do @@ -626,14 +626,14 @@ describe "Commenting debates" do click_button "I agree" within(".in-favor") do expect(page).not_to have_content "2" - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end diff --git a/spec/system/comments/legislation_annotations_spec.rb b/spec/system/comments/legislation_annotations_spec.rb index aae580f14..63b89850b 100644 --- a/spec/system/comments/legislation_annotations_spec.rb +++ b/spec/system/comments/legislation_annotations_spec.rb @@ -550,7 +550,7 @@ describe "Commenting legislation questions" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit polymorphic_path(annotation) within("#comment_#{comment.id}_votes") do @@ -562,14 +562,14 @@ describe "Commenting legislation questions" do click_button "I agree" within(".in-favor") do expect(page).not_to have_content "2" - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end diff --git a/spec/system/comments/legislation_questions_spec.rb b/spec/system/comments/legislation_questions_spec.rb index 6cc9c3685..bc50f7610 100644 --- a/spec/system/comments/legislation_questions_spec.rb +++ b/spec/system/comments/legislation_questions_spec.rb @@ -534,7 +534,7 @@ describe "Commenting legislation questions" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit legislation_process_question_path(question.process, question) within("#comment_#{comment.id}_votes") do @@ -546,14 +546,14 @@ describe "Commenting legislation questions" do click_button "I agree" within(".in-favor") do expect(page).not_to have_content "2" - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end diff --git a/spec/system/comments/polls_spec.rb b/spec/system/comments/polls_spec.rb index ca0708398..8f3d00759 100644 --- a/spec/system/comments/polls_spec.rb +++ b/spec/system/comments/polls_spec.rb @@ -495,7 +495,7 @@ describe "Commenting polls" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit poll_path(poll) within("#comment_#{comment.id}_votes") do @@ -508,14 +508,14 @@ describe "Commenting polls" do click_button "I agree" within(".in-favor") do - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end diff --git a/spec/system/comments/proposals_spec.rb b/spec/system/comments/proposals_spec.rb index 5d2f42329..a2da45137 100644 --- a/spec/system/comments/proposals_spec.rb +++ b/spec/system/comments/proposals_spec.rb @@ -500,7 +500,7 @@ describe "Commenting proposals" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit proposal_path(proposal) within("#comment_#{comment.id}_votes") do @@ -513,14 +513,14 @@ describe "Commenting proposals" do click_button "I agree" within(".in-favor") do - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end diff --git a/spec/system/comments/topics_spec.rb b/spec/system/comments/topics_spec.rb index e4a603bc7..c41ef234a 100644 --- a/spec/system/comments/topics_spec.rb +++ b/spec/system/comments/topics_spec.rb @@ -547,7 +547,7 @@ describe "Commenting topics from proposals" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit community_topic_path(proposal.community, topic) within("#comment_#{comment.id}_votes") do @@ -560,14 +560,14 @@ describe "Commenting topics from proposals" do click_button "I agree" within(".in-favor") do - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end @@ -1060,7 +1060,7 @@ describe "Commenting topics from budget investments" do end end - scenario "Trying to vote multiple times" do + scenario "Allow undoing votes" do visit community_topic_path(investment.community, topic) within("#comment_#{comment.id}_votes") do @@ -1073,14 +1073,14 @@ describe "Commenting topics from budget investments" do click_button "I agree" within(".in-favor") do - expect(page).to have_content "1" + expect(page).to have_content "0" end within(".against") do expect(page).to have_content "0" end - expect(page).to have_content "1 vote" + expect(page).to have_content "No votes" end end end