Merge pull request #5118 from consuldemocracy/undo-votes

Allow undoing "like/unlike" votes
This commit is contained in:
Sebastia
2023-10-10 06:44:43 +02:00
committed by GitHub
53 changed files with 630 additions and 248 deletions

View File

@@ -25,10 +25,10 @@
&:not([disabled]) {
&:hover,
&:active {
&:active,
&[aria-pressed=true] {
color: #fff;
cursor: pointer;
opacity: 1 !important;
}
}
}
@@ -38,7 +38,8 @@
&:not([disabled]) {
&:hover,
&:active {
&:active,
&[aria-pressed=true] {
background: $like;
border: 2px solid $like;
}
@@ -50,19 +51,14 @@
&:not([disabled]) {
&:hover,
&:active {
&:active,
&[aria-pressed=true] {
background: $unlike;
border: 2px solid $unlike;
}
}
}
.like,
.unlike {
vertical-align: super;
text-decoration: none;
}
.percentage {
display: inline-block;
font-size: $small-font-size;
@@ -76,22 +72,4 @@
padding-right: 0;
}
}
.voted {
color: #fff;
}
.in-favor .voted {
background: $like;
border: 2px solid $like;
}
.against .voted {
background: $unlike;
border: 2px solid $unlike;
}
.no-voted {
opacity: 0.3;
}
}

View File

@@ -1705,7 +1705,8 @@ table {
.in-favor button {
@include has-fa-icon(thumbs-up, solid);
&:hover {
&:hover,
&[aria-pressed=true] {
color: $like;
}
}
@@ -1713,7 +1714,8 @@ table {
.against button {
@include has-fa-icon(thumbs-down, solid);
&:hover {
&:hover,
&[aria-pressed=true] {
color: $unlike;
}
}

View File

@@ -3,22 +3,16 @@
 | 
<span class="in-favor">
<%= button_to vote_comment_path(comment, value: "yes"),
method: "post",
remote: can?(:vote, comment),
title: t("votes.agree") do %>
<span class="show-for-sr"><%= t("votes.agree") %></span>
<% end %>
<%= render Shared::VoteButtonComponent.new(comment,
value: "yes",
title: t("votes.agree")) %>
<%= comment.total_likes %>
</span>
<span class="against">
<%= button_to vote_comment_path(comment, value: "no"),
method: "post",
remote: can?(:vote, comment),
title: t("votes.disagree") do %>
<span class="show-for-sr"><%= t("votes.disagree") %></span>
<% end %>
<%= render Shared::VoteButtonComponent.new(comment,
value: "no",
title: t("votes.disagree")) %>
<%= comment.total_dislikes %>
</span>
</div>

View File

@@ -1,6 +1,5 @@
class Comments::VotesComponent < ApplicationComponent
attr_reader :comment
delegate :can?, to: :helpers
def initialize(comment)
@comment = comment

View File

@@ -1,6 +1,6 @@
class Legislation::Proposals::VotesComponent < ApplicationComponent
attr_reader :proposal
delegate :current_user, :link_to_verify_account, to: :helpers
delegate :current_user, :link_to_verify_account, :can?, to: :helpers
def initialize(proposal)
@proposal = proposal
@@ -9,7 +9,7 @@ class Legislation::Proposals::VotesComponent < ApplicationComponent
private
def can_vote?
proposal.votable_by?(current_user)
can?(:create, proposal.votes_for.new(voter: current_user))
end
def cannot_vote_text

View File

@@ -1,25 +1,17 @@
<div class="in-favor-against">
<div class="in-favor">
<%= button_to polymorphic_path(votable, action: :vote, value: "yes"),
class: "like #{voted_classes[:in_favor]}",
title: t("votes.agree"),
"aria-label": agree_aria_label,
method: "post",
remote: true do %>
<span class="show-for-sr"><%= t("votes.agree") %></span>
<% end %>
<%= render Shared::VoteButtonComponent.new(votable,
value: "yes",
"aria-label": agree_aria_label,
title: t("votes.agree")) %>
<span class="percentage"><%= votes_percentage("likes", votable) %></span>
</div>
<div class="against">
<%= button_to polymorphic_path(votable, action: :vote, value: "no"),
class: "unlike #{voted_classes[:against]}",
title: t("votes.disagree"),
"aria-label": disagree_aria_label,
method: "post",
remote: true do %>
<span class="show-for-sr"><%= t("votes.disagree") %></span>
<% end %>
<%= render Shared::VoteButtonComponent.new(votable,
value: "no",
"aria-label": disagree_aria_label,
title: t("votes.disagree")) %>
<span class="percentage"><%= votes_percentage("dislikes", votable) %></span>
</div>
</div>

View File

@@ -1,6 +1,6 @@
class Shared::InFavorAgainstComponent < ApplicationComponent
attr_reader :votable
delegate :current_user, :votes_percentage, to: :helpers
delegate :votes_percentage, to: :helpers
def initialize(votable)
@votable = votable
@@ -8,21 +8,6 @@ class Shared::InFavorAgainstComponent < ApplicationComponent
private
def voted_classes
@voted_classes ||= css_classes_for_vote
end
def css_classes_for_vote
case current_user&.voted_as_when_voted_for(votable)
when true
{ in_favor: "voted", against: "no-voted" }
when false
{ in_favor: "no-voted", against: "voted" }
else
{ in_favor: "", against: "" }
end
end
def agree_aria_label
t("votes.agree_label", title: votable.title)
end

View File

@@ -0,0 +1,3 @@
<%= button_to path, default_options.merge(options) do %>
<span class="show-for-sr"><%= options[:title] %></span>
<% end %>

View File

@@ -0,0 +1,48 @@
class Shared::VoteButtonComponent < ApplicationComponent
attr_reader :votable, :value, :options
delegate :current_user, :can?, to: :helpers
def initialize(votable, value:, **options)
@votable = votable
@value = value
@options = options
end
private
def path
if already_voted?
polymorphic_path(vote)
else
polymorphic_path(vote, value: value)
end
end
def default_options
if already_voted?
{
"aria-pressed": true,
method: :delete,
remote: can?(:destroy, vote)
}
else
{
"aria-pressed": false,
method: :post,
remote: can?(:create, vote)
}
end
end
def vote
@vote ||= Vote.find_or_initialize_by(votable: votable, voter: current_user, vote_flag: parsed_value)
end
def already_voted?
vote.persisted?
end
def parsed_value
value == "yes"
end
end

View File

@@ -0,0 +1,35 @@
module Comments
class VotesController < ApplicationController
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
authorize! :create, Vote.new(voter: current_user, votable: @comment)
@comment.vote_by(voter: current_user, vote: params[:value])
respond_to do |format|
format.js { render :show }
end
end
def destroy
@comment.unvote_by(current_user)
respond_to do |format|
format.js { render :show }
end
end
private
def verify_comments_open!
return if current_user.administrator? || current_user.moderator?
if @comment.commentable.respond_to?(:comments_closed?) && @comment.commentable.comments_closed?
redirect_to polymorphic_path(@comment.commentable), alert: t("comments.comments_closed")
end
end
end
end

View File

@@ -1,8 +1,8 @@
class CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create, :hide, :vote]
before_action :authenticate_user!, only: [:create, :hide]
before_action :load_commentable, only: :create
before_action :verify_resident_for_commentable!, only: :create
before_action :verify_comments_open!, only: [:create, :vote]
before_action :verify_comments_open!, only: [:create]
before_action :build_comment, only: :create
load_and_authorize_resource
@@ -27,11 +27,6 @@ class CommentsController < ApplicationController
end
end
def vote
@comment.vote_by(voter: current_user, vote: params[:value])
respond_with @comment
end
def flag
Flag.flag(current_user, @comment)
set_comment_flags(@comment)

View File

@@ -0,0 +1,24 @@
module Debates
class VotesController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource :debate
load_and_authorize_resource through: :debate, through_association: :votes_for, only: :destroy
def create
authorize! :create, Vote.new(voter: current_user, votable: @debate)
@debate.register_vote(current_user, params[:value])
respond_to do |format|
format.js { render :show }
end
end
def destroy
@debate.unvote_by(current_user)
respond_to do |format|
format.js { render :show }
end
end
end
end

View File

@@ -28,10 +28,6 @@ class DebatesController < ApplicationController
redirect_to debate_path(@debate), status: :moved_permanently if request.path != debate_path(@debate)
end
def vote
@debate.register_vote(current_user, params[:value])
end
def unmark_featured
@debate.update!(featured_at: nil)
redirect_to debates_path

View File

@@ -0,0 +1,29 @@
module Legislation
module Proposals
class VotesController < ApplicationController
before_action :authenticate_user!
load_and_authorize_resource :process, class: "Legislation::Process"
load_and_authorize_resource :proposal, class: "Legislation::Proposal",
through: :process,
id_param: "legislation_proposal_id"
load_and_authorize_resource through: :proposal, through_association: :votes_for, only: :destroy
def create
authorize! :create, Vote.new(voter: current_user, votable: @proposal)
@proposal.vote_by(voter: current_user, vote: params[:value])
respond_to do |format|
format.js { render :show }
end
end
def destroy
@proposal.unvote_by(current_user)
respond_to do |format|
format.js { render :show }
end
end
end
end
end

View File

@@ -37,10 +37,6 @@ class Legislation::ProposalsController < Legislation::BaseController
end
end
def vote
@proposal.register_vote(current_user, params[:value])
end
private
def proposal_params

View File

@@ -81,14 +81,15 @@ module Abilities
can [:create, :destroy], DirectUpload
unless user.organization?
can :vote, Debate
can :vote, Comment
can [:create, :destroy], ActsAsVotable::Vote, voter_id: user.id, votable_type: "Debate"
can [:create, :destroy], ActsAsVotable::Vote, voter_id: user.id, votable_type: "Comment"
end
if user.level_two_or_three_verified?
can :vote, Proposal, &:published?
can :vote, Legislation::Proposal
can [:create, :destroy], ActsAsVotable::Vote, voter_id: user.id, votable_type: "Legislation::Proposal"
can :create, Legislation::Answer
can :create, Budget::Investment, budget: { phase: "accepting" }

View File

@@ -286,7 +286,7 @@ class Budget
def permission_problem(user)
return :not_logged_in unless user
return :organization if user.organization?
return :not_verified unless user.can?(:create, ActsAsVotable::Vote)
return :not_verified unless user.level_two_or_three_verified?
nil
end

View File

@@ -108,14 +108,6 @@ class Legislation::Proposal < ApplicationRecord
author_id == user.id && editable?
end
def votable_by?(user)
user&.level_two_or_three_verified?
end
def register_vote(user, vote_value)
vote_by(voter: user, vote: vote_value) if votable_by?(user)
end
def code
"#{Setting["proposal_code_prefix"]}-#{created_at.strftime("%Y-%m")}-#{id}"
end

View File

@@ -81,7 +81,7 @@
<div id="<%= dom_id(comment) %>_reply" class="reply">
<% unless valuation %>
<div id="<%= dom_id(comment) %>_votes" class="comment-votes float-right">
<%= render "comments/votes", comment: comment %>
<%= render Comments::VotesComponent.new(comment) %>
</div>
<% end %>

View File

@@ -1 +0,0 @@
<%= render Comments::VotesComponent.new(comment) %>

View File

@@ -1 +0,0 @@
$("#<%= dom_id(@comment) %>_votes").html("<%= j render("comments/votes", comment: @comment) %>");

View File

@@ -0,0 +1 @@
$("#<%= dom_id(@comment) %>_votes").html("<%= j render Comments::VotesComponent.new(@comment) %>");

View File

@@ -49,7 +49,7 @@
</div>
<div id="<%= dom_id(debate) %>_votes" class="small-12 medium-3 column">
<%= render "debates/votes", debate: debate %>
<%= render Debates::VotesComponent.new(debate) %>
</div>
</div>

View File

@@ -1 +0,0 @@
<%= render Debates::VotesComponent.new(debate) %>

View File

@@ -57,7 +57,7 @@
<div class="sidebar-divider"></div>
<h2><%= t("votes.supports") %></h2>
<div id="<%= dom_id(@debate) %>_votes">
<%= render "debates/votes", debate: @debate %>
<%= render Debates::VotesComponent.new(@debate) %>
</div>
<%= render "shared/social_share",
share_title: t("debates.show.share"),

View File

@@ -1 +0,0 @@
$("#<%= dom_id(@debate) %>_votes").html("<%= j render("debates/votes", debate: @debate) %>");

View File

@@ -0,0 +1 @@
$("#<%= dom_id(@debate) %>_votes").html("<%= j render Debates::VotesComponent.new(@debate) %>");

View File

@@ -24,7 +24,7 @@
</div>
<div class="comment-votes">
<div id="<%= dom_id(comment) %>_votes" class="comment-votes float-right">
<%= render "comments/votes", comment: comment %>
<%= render Comments::VotesComponent.new(comment) %>
</div>
</div>
</div>

View File

@@ -1,8 +1,9 @@
resources :comments, only: [:create, :show], shallow: true do
resources :comments, only: [:create, :show] do
member do
post :vote
put :flag
put :unflag
put :hide
end
resources :votes, controller: "comments/votes", only: [:create, :destroy]
end

View File

@@ -1,6 +1,5 @@
resources :debates do
member do
post :vote
put :flag
put :unflag
put :mark_featured
@@ -11,4 +10,6 @@ resources :debates do
get :suggest
put "recommendations/disable", only: :index, controller: "debates", action: :disable_recommendations
end
resources :votes, controller: "debates/votes", only: [:create, :destroy]
end

View File

@@ -16,7 +16,6 @@ namespace :legislation do
resources :proposals, except: [:index] do
member do
post :vote
put :flag
put :unflag
end
@@ -25,6 +24,10 @@ namespace :legislation do
end
end
resources :legislation_proposals, path: "proposals", only: [] do
resources :votes, controller: "proposals/votes", only: [:create, :destroy]
end
resources :draft_versions, only: [:show] do
get :go_to_version, on: :collection
get :changes
@@ -41,6 +44,10 @@ resolve "Legislation::Proposal" do |proposal, options|
[proposal.process, :proposal, options.merge(id: proposal)]
end
resolve "Vote" do |vote, options|
[*resource_hierarchy_for(vote.votable), vote, options]
end
resolve "Legislation::Question" do |question, options|
[question.process, :question, options.merge(id: question)]
end

View File

@@ -0,0 +1,65 @@
require "rails_helper"
describe Comments::VotesComponent do
let(:user) { create(:user) }
let(:comment) { create(:comment, user: user) }
let(:component) { Comments::VotesComponent.new(comment) }
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)
render_inline component
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 "have expected values when the against button is pressed" do
comment.vote_by(voter: user, vote: "no")
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='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 "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
end

View File

@@ -25,19 +25,5 @@ describe Debates::VotesComponent do
expect(page).to have_button "I don't agree with What about the 2030 agenda?"
expect(page).not_to have_content "You must sign in or sign up to continue."
end
it "does not include result percentages" do
create(:vote, votable: debate)
sign_in(create(:user))
render_inline component
expect(page).to have_button count: 2
expect(page).to have_button "I agree"
expect(page).to have_button "I disagree"
expect(page).not_to have_button text: "%"
expect(page).not_to have_button text: "100"
expect(page).not_to have_button text: "0"
end
end
end

View File

@@ -38,19 +38,5 @@ describe Legislation::Proposals::VotesComponent do
expect(page).to have_button "I don't agree with Require wearing masks at home"
expect(page).not_to have_content "You must sign in or sign up to continue."
end
it "does not include result percentages" do
create(:vote, votable: proposal)
sign_in(create(:user))
render_inline component
expect(page).to have_button count: 2
expect(page).to have_button "I agree"
expect(page).to have_button "I disagree"
expect(page).not_to have_button text: "%"
expect(page).not_to have_button text: "100"
expect(page).not_to have_button text: "0"
end
end
end

View File

@@ -0,0 +1,88 @@
require "rails_helper"
describe Shared::InFavorAgainstComponent do
let(:debate) { create(:debate) }
let(:component) { Shared::InFavorAgainstComponent.new(debate) }
let(:user) { create(:user) }
describe "Agree and disagree buttons" do
it "can create a vote when the user has not yet voted" do
sign_in user
render_inline component
page.find(".in-favor") do |in_favor_block|
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 "form[action*='votes'][method='post']"
expect(against_block).not_to have_css "input[name='_method']", visible: :all
end
end
it "can undo vote when the user has already voted" do
create(:vote, votable: debate, voter: user)
sign_in user
render_inline component
page.find(".in-favor") do |in_favor_block|
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 "form[action*='votes'][method='post']"
expect(against_block).not_to have_css "input[name='_method']", visible: :all
end
end
it "does not include result percentages" do
create(:vote, votable: debate)
sign_in(user)
render_inline component
expect(page).to have_button count: 2
expect(page).to have_button "I agree"
expect(page).to have_button "I disagree"
expect(page).not_to have_button text: "%"
expect(page).not_to have_button text: "100"
expect(page).not_to have_button text: "0"
end
describe "aria-pressed attribute" do
it "is true when the in-favor button is pressed" do
debate.register_vote(user, "yes")
sign_in(user)
render_inline component
expect(page.find(".in-favor")).to have_css "button[aria-pressed='true']"
expect(page.find(".against")).to have_css "button[aria-pressed='false']"
end
it "is true when the against button is pressed" do
debate.register_vote(user, "no")
sign_in(user)
render_inline component
expect(page.find(".in-favor")).to have_css "button[aria-pressed='false']"
expect(page.find(".against")).to have_css "button[aria-pressed='true']"
end
it "is false when neither the 'in-favor' button nor the 'against' button are pressed" do
sign_in(user)
render_inline component
expect(page.find(".in-favor")).to have_css "button[aria-pressed='false']"
expect(page.find(".against")).to have_css "button[aria-pressed='false']"
end
end
end
end

View File

@@ -0,0 +1,34 @@
require "rails_helper"
describe Comments::VotesController do
let(:comment) { create(:comment) }
describe "POST create" do
it "allows voting" do
sign_in create(:user)
expect do
post :create, xhr: true, params: { comment_id: comment.id, value: "yes" }
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

View File

@@ -0,0 +1,56 @@
require "rails_helper"
describe Debates::VotesController do
describe "POST create" do
it "does not authorize unauthenticated users" do
debate = create(:debate)
post :create, xhr: true, params: { debate_id: debate.id, value: "yes" }
expect(response).to be_unauthorized
end
it "redirects unauthenticated users without JavaScript to the sign in page" do
debate = create(:debate)
post :create, params: { debate_id: debate.id, value: "yes" }
expect(response).to redirect_to new_user_session_path
end
describe "Vote with too many anonymous votes" do
it "allows vote if user is allowed" do
Setting["max_ratio_anon_votes_on_debates"] = 100
debate = create(:debate)
sign_in create(:user)
expect do
post :create, xhr: true, params: { debate_id: debate.id, value: "yes" }
end.to change { debate.reload.votes_for.size }.by(1)
end
it "does not allow voting if user is not allowed" do
Setting["max_ratio_anon_votes_on_debates"] = 0
debate = create(:debate, cached_votes_total: 1000)
sign_in create(:user)
expect do
post :create, xhr: true, params: { debate_id: debate.id, value: "yes" }
end.not_to change { debate.reload.votes_for.size }
end
end
end
describe "DELETE destroy" do
let(:user) { create(:user) }
let(:debate) { create(:debate) }
let!(:vote) { create(:vote, votable: debate, voter: user) }
before { sign_in user }
it "allows undoing a vote if user is allowed" do
expect do
delete :destroy, xhr: true, params: { debate_id: debate.id, id: vote }
end.to change { debate.reload.votes_for.size }.by(-1)
end
end
end

View File

@@ -37,28 +37,6 @@ describe DebatesController do
end
end
describe "Vote with too many anonymous votes" do
it "allows vote if user is allowed" do
Setting["max_ratio_anon_votes_on_debates"] = 100
debate = create(:debate)
sign_in create(:user)
expect do
post :vote, xhr: true, params: { id: debate.id, value: "yes" }
end.to change { debate.reload.votes_for.size }.by(1)
end
it "does not allow vote if user is not allowed" do
Setting["max_ratio_anon_votes_on_debates"] = 0
debate = create(:debate, cached_votes_total: 1000)
sign_in create(:user)
expect do
post :vote, xhr: true, params: { id: debate.id, value: "yes" }
end.not_to change { debate.reload.votes_for.size }
end
end
describe "PUT mark_featured" do
it "ignores query parameters" do
debate = create(:debate)

View File

@@ -0,0 +1,56 @@
require "rails_helper"
describe Legislation::Proposals::VotesController do
let(:legislation_process) { create(:legislation_process) }
let(:proposal) { create(:legislation_proposal, process: legislation_process) }
describe "POST create" do
let(:vote_params) do
{ process_id: legislation_process.id, legislation_proposal_id: proposal.id, value: "yes" }
end
it "does not authorize unauthenticated users" do
post :create, xhr: true, params: vote_params
expect(response).to be_unauthorized
end
it "redirects unauthenticated users without JavaScript to the sign in page" do
post :create, params: vote_params
expect(response).to redirect_to new_user_session_path
end
it "allows vote if user is level_two_or_three_verified" do
sign_in create(:user, :level_two)
expect do
post :create, xhr: true, params: vote_params
end.to change { proposal.reload.votes_for.size }.by(1)
end
it "does not allow voting if user is not level_two_or_three_verified" do
sign_in create(:user)
expect do
post :create, xhr: true, params: vote_params
end.not_to change { proposal.reload.votes_for.size }
end
end
describe "DELETE destroy" do
let(:user) { create(:user, :level_two) }
let!(:vote) { create(:vote, votable: proposal, voter: user) }
let(:vote_params) do
{ process_id: legislation_process.id, legislation_proposal_id: proposal.id, id: vote }
end
it "allows undoing a vote" do
sign_in user
expect do
delete :destroy, xhr: true, params: vote_params
end.to change { proposal.reload.votes_for.size }.by(-1)
end
end
end

View File

@@ -48,7 +48,6 @@ describe Abilities::Administrator do
it { should be_able_to(:index, Debate) }
it { should be_able_to(:show, debate) }
it { should be_able_to(:vote, debate) }
it { should be_able_to(:index, Proposal) }
it { should be_able_to(:show, proposal) }

View File

@@ -7,6 +7,7 @@ describe Abilities::Common do
let(:geozone) { create(:geozone) }
let(:user) { create(:user, geozone: geozone) }
let(:another_user) { create(:user) }
let(:debate) { create(:debate) }
let(:comment) { create(:comment) }
@@ -15,6 +16,7 @@ describe Abilities::Common do
let(:own_comment) { create(:comment, author: user) }
let(:own_proposal) { create(:proposal, author: user) }
let(:own_legislation_proposal) { create(:legislation_proposal, author: user) }
let(:legislation_proposal) { create(:legislation_proposal) }
let(:accepting_budget) { create(:budget, :accepting) }
let(:reviewing_budget) { create(:budget, :reviewing) }
@@ -73,7 +75,10 @@ describe Abilities::Common do
it { should be_able_to(:index, Debate) }
it { should be_able_to(:show, debate) }
it { should be_able_to(:vote, debate) }
it { should be_able_to(:create, user.votes.build(votable: debate)) }
it { should_not be_able_to(:create, another_user.votes.build(votable: debate)) }
it { should be_able_to(:destroy, user.votes.build(votable: debate)) }
it { should_not be_able_to(:destroy, another_user.votes.build(votable: debate)) }
it { should be_able_to(:show, user) }
it { should be_able_to(:edit, user) }
@@ -108,7 +113,10 @@ describe Abilities::Common do
describe "Comment" do
it { should be_able_to(:create, Comment) }
it { should be_able_to(:vote, 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) }
@@ -137,20 +145,16 @@ describe Abilities::Common do
end
describe "follows" do
let(:other_user) { create(:user) }
it { should be_able_to(:create, build(:follow, :followed_proposal, user: user)) }
it { should_not be_able_to(:create, build(:follow, :followed_proposal, user: other_user)) }
it { should_not be_able_to(:create, build(:follow, :followed_proposal, user: another_user)) }
it { should be_able_to(:destroy, create(:follow, :followed_proposal, user: user)) }
it { should_not be_able_to(:destroy, create(:follow, :followed_proposal, user: other_user)) }
it { should_not be_able_to(:destroy, create(:follow, :followed_proposal, user: another_user)) }
end
describe "other users" do
let(:other_user) { create(:user) }
it { should be_able_to(:show, other_user) }
it { should_not be_able_to(:edit, other_user) }
it { should be_able_to(:show, another_user) }
it { should_not be_able_to(:edit, another_user) }
end
describe "editing debates" do
@@ -182,6 +186,21 @@ describe Abilities::Common do
it { should_not be_able_to(:edit, own_legislation_proposal) }
it { should_not be_able_to(:update, own_legislation_proposal) }
describe "vote legislation proposal" do
context "when user is not level_two_or_three_verified" do
it { should_not be_able_to(:create, user.votes.build(votable: legislation_proposal)) }
it { should_not be_able_to(:destroy, user.votes.build(votable: legislation_proposal)) }
end
context "when user is level_two_or_three_verified" do
before { user.update(level_two_verified_at: Date.current) }
it { should be_able_to(:create, user.votes.build(votable: legislation_proposal)) }
it { should_not be_able_to(:create, another_user.votes.build(votable: legislation_proposal)) }
it { should be_able_to(:destroy, user.votes.build(votable: legislation_proposal)) }
it { should_not be_able_to(:destroy, another_user.votes.build(votable: legislation_proposal)) }
end
end
describe "proposals dashboard" do
it { should be_able_to(:dashboard, own_proposal) }
it { should_not be_able_to(:dashboard, proposal) }

View File

@@ -25,7 +25,6 @@ describe Abilities::Moderator do
it { should be_able_to(:index, Debate) }
it { should be_able_to(:show, debate) }
it { should be_able_to(:vote, debate) }
it { should be_able_to(:index, Proposal) }
it { should be_able_to(:show, proposal) }

View File

@@ -8,20 +8,23 @@ describe "Abilities::Organization" do
let(:organization) { create(:organization) }
let(:debate) { create(:debate) }
let(:proposal) { create(:proposal) }
let(:comment) { create(:comment) }
it { should be_able_to(:show, user) }
it { should be_able_to(:edit, user) }
it { should be_able_to(:index, Debate) }
it { should be_able_to(:show, debate) }
it { should_not be_able_to(:vote, debate) }
it { should_not be_able_to(:create, user.votes.build(votable: debate)) }
it { should_not be_able_to(:destroy, user.votes.build(votable: debate)) }
it { should be_able_to(:index, Proposal) }
it { should be_able_to(:show, proposal) }
it { should_not be_able_to(:vote, Proposal) }
it { should be_able_to(:create, Comment) }
it { should_not be_able_to(:vote, 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) }

View File

@@ -16,6 +16,26 @@ describe "Polymorphic routes" do
expect(polymorphic_path(proposal)).to eq legislation_process_proposal_path(process, proposal)
end
it "routes legislation proposals vote" do
process = create(:legislation_process)
proposal = create(:legislation_proposal, process: process)
path = polymorphic_path(Vote.new(votable: proposal), value: true)
expect(path).to eq legislation_process_legislation_proposal_votes_path(process, proposal, value: true)
end
it "routes legislation proposals remove vote" do
process = create(:legislation_process)
proposal = create(:legislation_proposal, process: process)
vote = create(:vote, votable: proposal)
path = polymorphic_path(vote, value: true)
expect(path).to eq legislation_process_legislation_proposal_vote_path(process,
proposal,
vote,
value: true)
end
it "routes legislation questions" do
process = create(:legislation_process)
question = create(:legislation_question, process: process)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -19,37 +19,31 @@ describe "Votes" do
within("#debates") do
within("#debate_#{debate1.id}_votes") do
within(".in-favor") do
expect(page).to have_button class: "voted"
expect(page).not_to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='true']"
end
within(".against") do
expect(page).to have_button class: "no-voted"
expect(page).not_to have_button class: "voted"
expect(page).to have_css "button[aria-pressed='false']"
end
end
within("#debate_#{debate2.id}_votes") do
within(".in-favor") do
expect(page).not_to have_button class: "voted"
expect(page).not_to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='false']"
end
within(".against") do
expect(page).not_to have_button class: "no-voted"
expect(page).not_to have_button class: "voted"
expect(page).to have_css "button[aria-pressed='false']"
end
end
within("#debate_#{debate3.id}_votes") do
within(".in-favor") do
expect(page).to have_button class: "no-voted"
expect(page).not_to have_button class: "voted"
expect(page).to have_css "button[aria-pressed='false']"
end
within(".against") do
expect(page).to have_button class: "voted"
expect(page).not_to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='true']"
end
end
end
@@ -63,56 +57,70 @@ describe "Votes" do
within(".in-favor") do
expect(page).to have_content "0%"
expect(page).not_to have_button class: "voted"
expect(page).not_to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='false']"
end
within(".against") do
expect(page).to have_content "0%"
expect(page).not_to have_button class: "voted"
expect(page).not_to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='false']"
end
end
scenario "Update" do
scenario "Create and update from debate show" do
visit debate_path(create(:debate))
expect(page).to have_content "No votes"
click_button "I agree"
within(".in-favor") do
expect(page).to have_content "100%"
expect(page).to have_button class: "voted"
expect(page).to have_css "button[aria-pressed='true']"
end
within(".against") do
expect(page).to have_content "0%"
expect(page).to have_css "button[aria-pressed='false']"
end
expect(page).to have_content "1 vote"
click_button "I disagree"
within(".in-favor") do
expect(page).to have_content "0%"
expect(page).to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='false']"
end
within(".against") do
expect(page).to have_content "100%"
expect(page).to have_button class: "voted"
expect(page).to have_css "button[aria-pressed='true']"
end
expect(page).to have_content "1 vote"
end
scenario "Trying to vote multiple times" do
scenario "Allow undoing votes" do
visit debate_path(create(:debate))
click_button "I agree"
expect(page).to have_content "1 vote"
expect(page).not_to have_content "No votes"
click_button "I agree"
expect(page).to have_content "No votes"
expect(page).not_to have_content "2 votes"
within(".in-favor") do
expect(page).to have_content "100%"
expect(page).to have_content "0%"
expect(page).to have_css "button[aria-pressed='false']"
end
within(".against") do
expect(page).to have_content "0%"
expect(page).to have_css "button[aria-pressed='false']"
end
end
@@ -127,33 +135,15 @@ describe "Votes" do
within(".in-favor") do
expect(page).to have_content "50%"
expect(page).to have_button class: "voted"
expect(page).to have_css "button[aria-pressed='true']"
end
within(".against") do
expect(page).to have_content "50%"
expect(page).to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='false']"
end
end
scenario "Create from debate show" do
visit debate_path(create(:debate))
click_button "I agree"
within(".in-favor") do
expect(page).to have_content "100%"
expect(page).to have_button class: "voted"
end
within(".against") do
expect(page).to have_content "0%"
expect(page).to have_button class: "no-voted"
end
expect(page).to have_content "1 vote"
end
scenario "Create in index" do
create(:debate)
visit debates_path
@@ -163,12 +153,12 @@ describe "Votes" do
within(".in-favor") do
expect(page).to have_content "100%"
expect(page).to have_button class: "voted"
expect(page).to have_css "button[aria-pressed='true']"
end
within(".against") do
expect(page).to have_content "0%"
expect(page).to have_button class: "no-voted"
expect(page).to have_css "button[aria-pressed='false']"
end
expect(page).to have_content "1 vote"
@@ -354,4 +344,31 @@ describe "Votes" do
expect(page).not_to have_button "Support", disabled: :all
end
end
describe "Legislation Proposals" do
let(:proposal) { create(:legislation_proposal) }
scenario "Allow undoing votes" do
login_as verified
visit legislation_process_proposal_path(proposal.process, proposal)
click_button "I agree"
expect(page).to have_content "1 vote"
expect(page).not_to have_content "No votes"
click_button "I agree"
expect(page).to have_content "No votes"
expect(page).not_to have_content "2 votes"
within(".in-favor") do
expect(page).to have_content "0%"
end
within(".against") do
expect(page).to have_content "0%"
end
end
end
end