Unify code in debates/legislation vote links

We were using the same code to render links to agree and disagree, so we
can extract a new component for this code.

We're also adding component tests to make it easier to test whether
we're breaking anything while refactoring, although the code is probably
already covered by system tests.

Since the votes mixin was only used in one place, we're removing it and
moving most of its code to a new CSS file for the shared component.
This commit is contained in:
Javi Martín
2022-01-13 17:51:13 +01:00
parent 9a625fe59f
commit 86ad2df46d
12 changed files with 254 additions and 214 deletions

View File

@@ -27,6 +27,7 @@
@import "milestones";
@import "pages";
@import "dashboard";
@import "in_favor_against";
@import "legislation";
@import "legislation_process";
@import "legislation_process_form";

View File

@@ -0,0 +1,97 @@
.in-favor-against {
display: inline-block;
.icon-like,
.icon-unlike {
background: #fff;
border: 2px solid;
border-radius: rem-calc(3);
color: $text-light;
display: inline-block;
font-size: rem-calc(30);
line-height: rem-calc(30);
padding: rem-calc(3) rem-calc(6);
position: relative;
&:hover,
&:active {
color: #fff;
cursor: pointer;
opacity: 1 !important;
}
}
.icon-like {
&:hover,
&:active {
background: $like;
border: 2px solid $like;
}
}
.icon-unlike {
&:hover,
&:active {
background: $unlike;
border: 2px solid $unlike;
}
}
.like,
.unlike {
line-height: rem-calc(48);
vertical-align: super;
text-decoration: none;
.percentage {
color: $text;
display: inline-block;
font-size: $small-font-size;
line-height: $line-height * 2;
padding-right: $line-height / 2;
vertical-align: top;
@include breakpoint(medium) {
display: block;
line-height: $line-height;
padding-right: 0;
}
}
}
.voted {
.icon-like,
.icon-unlike {
color: #fff;
}
.icon-like {
background: $like;
border: 2px solid $like;
}
.icon-unlike {
background: $unlike;
border: 2px solid $unlike;
}
}
.no-voted {
.icon-like,
.icon-unlike {
opacity: 0.3;
}
}
.against {
margin-left: $line-height / 4;
}
.divider {
margin: 0 rem-calc(6);
}
}

View File

@@ -14,120 +14,6 @@
// 01. Votes and supports
// ----------------------
@mixin votes {
border-top: 1px solid $border;
margin-top: $line-height;
padding: $line-height / 2 0;
position: relative;
@include breakpoint(medium) {
border-left: 1px solid $border;
border-top: 0;
margin-top: 0;
}
.icon-like,
.icon-unlike {
background: #fff;
border: 2px solid;
border-radius: rem-calc(3);
color: $text-light;
display: inline-block;
font-size: rem-calc(30);
line-height: rem-calc(30);
padding: rem-calc(3) rem-calc(6);
position: relative;
&:hover,
&:active {
color: #fff;
cursor: pointer;
opacity: 1 !important;
}
}
.icon-like {
&:hover,
&:active {
background: $like;
border: 2px solid $like;
}
}
.icon-unlike {
&:hover,
&:active {
background: $unlike;
border: 2px solid $unlike;
}
}
.like,
.unlike {
line-height: rem-calc(48);
vertical-align: super;
text-decoration: none;
.percentage {
color: $text;
display: inline-block;
font-size: $small-font-size;
line-height: $line-height * 2;
padding-right: $line-height / 2;
vertical-align: top;
@include breakpoint(medium) {
display: block;
line-height: $line-height;
padding-right: 0;
}
}
}
.voted {
.icon-like,
.icon-unlike {
color: #fff;
}
.icon-like {
background: $like;
border: 2px solid $like;
}
.icon-unlike {
background: $unlike;
border: 2px solid $unlike;
}
}
.no-voted {
.icon-like,
.icon-unlike {
opacity: 0.3;
}
}
.total-votes {
font-weight: bold;
float: right;
line-height: $line-height * 2;
@include breakpoint(medium) {
display: block;
float: none;
}
}
.divider {
margin: 0 rem-calc(6);
}
}
@mixin supports {
padding: $line-height 0;
position: relative;
@@ -713,14 +599,27 @@
.legislation-proposals {
.votes {
@include votes;
border-top: 1px solid $border;
margin-top: $line-height;
padding: $line-height / 2 0;
position: relative;
@include breakpoint(medium) {
border-left: 1px solid $border;
border-top: 0;
margin-top: 0;
text-align: center;
}
.against {
margin-left: $line-height / 4;
.total-votes {
font-weight: bold;
float: right;
line-height: $line-height * 2;
@include breakpoint(medium) {
display: block;
float: none;
}
}
}
}

View File

@@ -1,42 +1,5 @@
<div class="votes">
<div class="in-favor inline-block">
<% if current_user %>
<%= link_to vote_debate_path(debate, value: "yes"),
class: "like #{voted_classes[:in_favor]}", title: t("votes.agree"), method: "post", remote: true do %>
<span class="icon-like">
<span class="show-for-sr"><%= t("votes.agree") %></span>
</span>
<span class="percentage"><%= votes_percentage("likes", debate) %></span>
<% end %>
<% else %>
<div class="like">
<span class="icon-like">
<span class="show-for-sr"><%= t("votes.agree") %></span>
</span>
<span class="percentage"><%= votes_percentage("likes", debate) %></span>
</div>
<% end %>
</div>
<span class="divider"></span>
<div class="against inline-block">
<% if current_user %>
<%= link_to vote_debate_path(debate, value: "no"), class: "unlike #{voted_classes[:against]}", title: t("votes.disagree"), method: "post", remote: true do %>
<span class="icon-unlike">
<span class="show-for-sr"><%= t("votes.disagree") %></span>
</span>
<span class="percentage"><%= votes_percentage("dislikes", debate) %></span>
<% end %>
<% else %>
<div class="unlike">
<span class="icon-unlike">
<span class="show-for-sr"><%= t("votes.disagree") %></span>
</span>
<span class="percentage"><%= votes_percentage("dislikes", debate) %></span>
</div>
<% end %>
</div>
<%= render Shared::InFavorAgainstComponent.new(debate) %>
<span class="total-votes">
<%= t("debates.debate.votes", count: debate.votes_score) %>

View File

@@ -1,6 +1,6 @@
class Debates::VotesComponent < ApplicationComponent
attr_reader :debate
delegate :css_classes_for_vote, :current_user, :link_to_verify_account, :votes_percentage, to: :helpers
delegate :current_user, :link_to_verify_account, to: :helpers
def initialize(debate)
@debate = debate
@@ -8,10 +8,6 @@ class Debates::VotesComponent < ApplicationComponent
private
def voted_classes
@voted_classes ||= css_classes_for_vote(debate)
end
def can_vote?
debate.votable_by?(current_user)
end

View File

@@ -1,43 +1,6 @@
<div class="votes">
<% if proposal.process.proposals_phase.open? %>
<div class="in-favor inline-block">
<% if current_user %>
<%= link_to vote_legislation_process_proposal_path(process_id: proposal.process, id: proposal, value: "yes"),
class: "like #{voted_classes[:in_favor]}", title: t("votes.agree"), method: "post", remote: true do %>
<span class="icon-like">
<span class="show-for-sr"><%= t("votes.agree") %></span>
</span>
<span class="percentage"><%= votes_percentage("likes", proposal) %></span>
<% end %>
<% else %>
<div class="like">
<span class="icon-like">
<span class="show-for-sr"><%= t("votes.agree") %></span>
</span>
<span class="percentage"><%= votes_percentage("likes", proposal) %></span>
</div>
<% end %>
</div>
<span class="divider"></span>
<div class="against inline-block">
<% if current_user %>
<%= link_to vote_legislation_process_proposal_path(process_id: proposal.process, id: proposal, value: "no"), class: "unlike #{voted_classes[:against]}", title: t("votes.disagree"), method: "post", remote: true do %>
<span class="icon-unlike">
<span class="show-for-sr"><%= t("votes.disagree") %></span>
</span>
<span class="percentage"><%= votes_percentage("dislikes", proposal) %></span>
<% end %>
<% else %>
<div class="unlike">
<span class="icon-unlike">
<span class="show-for-sr"><%= t("votes.disagree") %></span>
</span>
<span class="percentage"><%= votes_percentage("dislikes", proposal) %></span>
</div>
<% end %>
</div>
<%= render Shared::InFavorAgainstComponent.new(proposal) %>
<% end %>
<span class="total-votes">

View File

@@ -1,6 +1,6 @@
class Legislation::Proposals::VotesComponent < ApplicationComponent
attr_reader :proposal
delegate :css_classes_for_vote, :current_user, :link_to_verify_account, :votes_percentage, to: :helpers
delegate :current_user, :link_to_verify_account, to: :helpers
def initialize(proposal)
@proposal = proposal
@@ -8,10 +8,6 @@ class Legislation::Proposals::VotesComponent < ApplicationComponent
private
def voted_classes
@voted_classes ||= css_classes_for_vote(proposal)
end
def can_vote?
proposal.votable_by?(current_user)
end

View File

@@ -0,0 +1,40 @@
<div class="in-favor-against">
<div class="in-favor inline-block">
<% if current_user %>
<%= link_to polymorphic_path(votable, action: :vote, value: "yes"),
class: "like #{voted_classes[:in_favor]}", title: t("votes.agree"), method: "post", remote: true do %>
<span class="icon-like">
<span class="show-for-sr"><%= t("votes.agree") %></span>
</span>
<span class="percentage"><%= votes_percentage("likes", votable) %></span>
<% end %>
<% else %>
<div class="like">
<span class="icon-like">
<span class="show-for-sr"><%= t("votes.agree") %></span>
</span>
<span class="percentage"><%= votes_percentage("likes", votable) %></span>
</div>
<% end %>
</div>
<span class="divider"></span>
<div class="against inline-block">
<% if current_user %>
<%= link_to polymorphic_path(votable, action: :vote, value: "no"), class: "unlike #{voted_classes[:against]}", title: t("votes.disagree"), method: "post", remote: true do %>
<span class="icon-unlike">
<span class="show-for-sr"><%= t("votes.disagree") %></span>
</span>
<span class="percentage"><%= votes_percentage("dislikes", votable) %></span>
<% end %>
<% else %>
<div class="unlike">
<span class="icon-unlike">
<span class="show-for-sr"><%= t("votes.disagree") %></span>
</span>
<span class="percentage"><%= votes_percentage("dislikes", votable) %></span>
</div>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,25 @@
class Shared::InFavorAgainstComponent < ApplicationComponent
attr_reader :votable
delegate :current_user, :votes_percentage, to: :helpers
def initialize(votable)
@votable = votable
end
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
end

View File

@@ -12,15 +12,4 @@ module VotesHelper
"#{100 - debate_percentage_of_likes(debate)}%"
end
end
def css_classes_for_vote(votable)
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
end

View File

@@ -0,0 +1,29 @@
require "rails_helper"
describe Debates::VotesComponent do
let(:debate) { create(:debate, title: "What about the 2030 agenda?") }
let(:component) { Debates::VotesComponent.new(debate) }
describe "Agree and disagree links" do
it "is shown as plain text to anonymous users" do
render_inline component
expect(page).to have_content "I agree"
expect(page).to have_content "I disagree"
expect(page).to have_content "You must sign in or sign up to continue."
expect(page).not_to have_link "I agree"
expect(page).not_to have_link "I disagree"
end
it "is shown to identified users" do
sign_in(create(:user))
render_inline component
expect(page).to have_link count: 2
expect(page).to have_link "I agree", title: "I agree"
expect(page).to have_link "I disagree", title: "I disagree"
expect(page).not_to have_content "You must sign in or sign up to continue."
end
end
end

View File

@@ -0,0 +1,42 @@
require "rails_helper"
describe Legislation::Proposals::VotesComponent do
let(:proposal) { create(:legislation_proposal, title: "Require wearing masks at home") }
let(:component) { Legislation::Proposals::VotesComponent.new(proposal) }
describe "Agree and disagree links" do
it "is not shown when the proposals phase isn't open" do
proposal.process.update!(
proposals_phase_start_date: 2.days.ago,
proposals_phase_end_date: Date.yesterday
)
sign_in(create(:user))
render_inline component
expect(page).not_to have_content "I agree"
expect(page).not_to have_content "I disagree"
end
it "is shown as plain text to anonymous users" do
render_inline component
expect(page).to have_content "I agree"
expect(page).to have_content "I disagree"
expect(page).to have_content "You must sign in or sign up to continue."
expect(page).not_to have_link "I agree"
expect(page).not_to have_link "I disagree"
end
it "is shown to identified users" do
sign_in(create(:user))
render_inline component
expect(page).to have_link count: 2
expect(page).to have_link "I agree", title: "I agree"
expect(page).to have_link "I disagree", title: "I disagree"
expect(page).not_to have_content "You must sign in or sign up to continue."
end
end
end