Extract component to render a poll in the poll index

This is consistent with the way we've got partials to render debates,
proposals and legislation processes on their index pages.

Note that, while adding the tests for the status icon, we're keeping one
system test because it also tests the process of voting. We're adding a
new, similar component test, where the voter is created in the database,
so all possible statuses are tested in the component.
This commit is contained in:
Javi Martín
2024-04-22 03:46:04 +02:00
parent 2b03e3ebc4
commit 765ab758dc
9 changed files with 245 additions and 222 deletions

View File

@@ -47,6 +47,7 @@
@import "layout/**/*";
@import "machine_learning/**/*";
@import "moderation/**/*";
@import "polls/**/*";
@import "proposals/**/*";
@import "relationable/**/*";
@import "sdg/**/*";

View File

@@ -1515,100 +1515,12 @@
position: relative;
}
.poll {
&.with-image {
@include breakpoint(medium) {
padding: 0 calc(#{$line-height} / 2) 0 0;
}
.image-container img {
height: 100%;
max-width: none;
position: absolute;
}
}
}
.poll,
.poll-question {
border: 1px solid $border;
margin-bottom: calc(#{$line-height} / 2);
padding: calc(#{$line-height} / 2);
position: relative;
.icon-poll-answer {
border-top: 0;
border-bottom: 60px solid transparent;
height: 0;
position: absolute;
right: 0;
top: 0;
width: 0;
&.cant-answer::after,
&.not-logged-in::after,
&.already-answer::after,
&.unverified::after {
font-family: "icons" !important;
left: 34px;
position: absolute;
top: 5px;
}
&.cant-answer {
border-right: 60px solid $alert-bg;
&::after {
color: $color-alert;
content: "\74";
}
}
&.not-logged-in {
border-right: 60px solid $info-bg;
&::after {
color: $color-info;
content: "\6f";
}
}
&.unverified {
border-right: 60px solid $warning-bg;
&::after {
color: $color-warning;
content: "\6f";
}
}
&.already-answer {
border-right: 60px solid $success-bg;
&::after {
color: $color-success;
content: "\59";
}
}
}
.dates {
color: $text-medium;
font-size: $small-font-size;
margin-bottom: calc(#{$line-height} / 2);
}
h4 {
font-size: rem-calc(30);
line-height: $line-height * 1.5;
a {
color: inherit;
display: inline-block;
}
}
}
.questions-callout {

View File

@@ -0,0 +1,86 @@
.poll {
&.with-image {
@include breakpoint(medium) {
padding: 0 calc(#{$line-height} / 2) 0 0;
}
.image-container img {
height: 100%;
max-width: none;
position: absolute;
}
}
.icon-poll-answer {
border-top: 0;
border-bottom: 60px solid transparent;
height: 0;
position: absolute;
right: 0;
top: 0;
width: 0;
&.cant-answer::after,
&.not-logged-in::after,
&.already-answer::after,
&.unverified::after {
font-family: "icons" !important;
left: 34px;
position: absolute;
top: 5px;
}
&.cant-answer {
border-right: 60px solid $alert-bg;
&::after {
color: $color-alert;
content: "\74";
}
}
&.not-logged-in {
border-right: 60px solid $info-bg;
&::after {
color: $color-info;
content: "\6f";
}
}
&.unverified {
border-right: 60px solid $warning-bg;
&::after {
color: $color-warning;
content: "\6f";
}
}
&.already-answer {
border-right: 60px solid $success-bg;
&::after {
color: $color-success;
content: "\59";
}
}
}
.dates {
color: $text-medium;
font-size: $small-font-size;
margin-bottom: calc(#{$line-height} / 2);
}
h4 {
font-size: rem-calc(30);
line-height: $line-height * 1.5;
a {
color: inherit;
display: inline-block;
}
}
}

View File

@@ -0,0 +1,66 @@
<div class="poll with-image">
<% if !user_signed_in? %>
<div class="icon-poll-answer not-logged-in" title="<%= t("polls.index.not_logged_in") %>">
<span class="show-for-sr"><%= t("polls.index.not_logged_in") %></span>
</div>
<% elsif user_signed_in? %>
<% if current_user.unverified? %>
<div class="icon-poll-answer unverified" title="<%= t("polls.index.unverified") %>">
<span class="show-for-sr"><%= t("polls.index.unverified") %></span>
</div>
<% elsif cannot?(:answer, poll) %>
<div class="icon-poll-answer cant-answer" title="<%= t("polls.index.cant_answer") %>">
<span class="show-for-sr"><%= t("polls.index.cant_answer") %></span>
</div>
<% elsif !poll.votable_by?(current_user) %>
<div class="icon-poll-answer already-answer" title="<%= t("polls.index.already_answer") %>">
<span class="show-for-sr"><%= t("polls.index.already_answer") %></span>
</div>
<% end %>
<% end %>
<div class="row" data-equalizer data-equalize-on="medium">
<div class="small-12 medium-3 column">
<div class="image-container" data-equalizer-watch>
<% if poll.image.present? %>
<%= image_tag poll.image.variant(:large), alt: poll.image.title.unicode_normalize %>
<% end %>
</div>
</div>
<div class="small-12 medium-6 column" data-equalizer-watch>
<div class="dates"></div>
<% if poll.questions.one? %>
<h4><%= link_to_poll poll.questions.first.title, poll %></h4>
<%= dates %>
<% else %>
<h4><%= link_to_poll poll.name, poll %></h4>
<%= dates %>
<ul class="margin-top">
<% poll.questions.sort_for_list.each do |question| %>
<li><%= question.title %></li>
<% end %>
</ul>
<% end %>
<% if poll.geozones.any? %>
<p>
<small><%= t("polls.index.geozone_info") %></small>
</p>
<% end %>
<ul class="tags">
<% poll.geozones.each do |g| %>
<li><span><%= g.name %></span></li>
<% end %>
</ul>
<%= render SDG::TagListComponent.new(poll, limit: 5, linkable: false) %>
</div>
<div class="small-12 medium-3 column table" data-equalizer-watch>
<div class="table-cell align-middle">
<% if poll.expired? %>
<%= link_to_poll t("polls.index.participate_button_expired"), poll, class: "button hollow expanded" %>
<% else %>
<%= link_to_poll t("polls.index.participate_button"), poll, class: "button hollow expanded" %>
<% end %>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,18 @@
class Polls::PollComponent < ApplicationComponent
attr_reader :poll
use_helpers :cannot?, :user_signed_in?, :current_user, :link_to_poll
def initialize(poll)
@poll = poll
end
private
def dates
if poll.starts_at.blank? || poll.ends_at.blank?
I18n.t("polls.no_dates")
else
I18n.t("polls.dates", open_at: l(poll.starts_at.to_date), closed_at: l(poll.ends_at.to_date))
end
end
end

View File

@@ -1,12 +1,4 @@
module PollsHelper
def poll_dates(poll)
if poll.starts_at.blank? || poll.ends_at.blank?
I18n.t("polls.no_dates")
else
I18n.t("polls.dates", open_at: l(poll.starts_at.to_date), closed_at: l(poll.ends_at.to_date))
end
end
def booth_name_with_location(booth)
location = booth.location.blank? ? "" : " (#{booth.location})"
booth.name + location

View File

@@ -1,68 +1,3 @@
<% poll_group.each do |poll| %>
<div class="poll with-image">
<% if !user_signed_in? %>
<div class="icon-poll-answer not-logged-in" title="<%= t("polls.index.not_logged_in") %>">
<span class="show-for-sr"><%= t("polls.index.not_logged_in") %></span>
</div>
<% elsif user_signed_in? %>
<% if current_user.unverified? %>
<div class="icon-poll-answer unverified" title="<%= t("polls.index.unverified") %>">
<span class="show-for-sr"><%= t("polls.index.unverified") %></span>
</div>
<% elsif cannot?(:answer, poll) %>
<div class="icon-poll-answer cant-answer" title="<%= t("polls.index.cant_answer") %>">
<span class="show-for-sr"><%= t("polls.index.cant_answer") %></span>
</div>
<% elsif !poll.votable_by?(current_user) %>
<div class="icon-poll-answer already-answer" title="<%= t("polls.index.already_answer") %>">
<span class="show-for-sr"><%= t("polls.index.already_answer") %></span>
</div>
<% end %>
<% end %>
<div class="row" data-equalizer data-equalize-on="medium">
<div class="small-12 medium-3 column">
<div class="image-container" data-equalizer-watch>
<% if poll.image.present? %>
<%= image_tag poll.image.variant(:large), alt: poll.image.title.unicode_normalize %>
<% end %>
</div>
</div>
<div class="small-12 medium-6 column" data-equalizer-watch>
<div class="dates"></div>
<% if poll.questions.one? %>
<h4><%= link_to_poll poll.questions.first.title, poll %></h4>
<%= poll_dates(poll) %>
<% else %>
<h4><%= link_to_poll poll.name, poll %></h4>
<%= poll_dates(poll) %>
<ul class="margin-top">
<% poll.questions.sort_for_list.each do |question| %>
<li><%= question.title %></li>
<% end %>
</ul>
<% end %>
<% if poll.geozones.any? %>
<p>
<small><%= t("polls.index.geozone_info") %></small>
</p>
<% end %>
<ul class="tags">
<% poll.geozones.each do |g| %>
<li><span><%= g.name %></span></li>
<% end %>
</ul>
<%= render SDG::TagListComponent.new(poll, limit: 5, linkable: false) %>
</div>
<div class="small-12 medium-3 column table" data-equalizer-watch>
<div class="table-cell align-middle">
<% if poll.expired? %>
<%= link_to_poll t("polls.index.participate_button_expired"), poll, class: "button hollow expanded" %>
<% else %>
<%= link_to_poll t("polls.index.participate_button"), poll, class: "button hollow expanded" %>
<% end %>
</div>
</div>
</div>
</div>
<%= render Polls::PollComponent.new(poll) %>
<% end %>

View File

@@ -0,0 +1,73 @@
require "rails_helper"
describe Polls::PollComponent do
include Rails.application.routes.url_helpers
describe "status message" do
it "asks anonymous users to sign in" do
render_inline Polls::PollComponent.new(create(:poll))
expect(page).to have_css ".not-logged-in", count: 1
expect(page).to have_content "You must sign in or sign up to participate"
end
it "asks unverified users to verify their account" do
sign_in(create(:user))
render_inline Polls::PollComponent.new(create(:poll))
expect(page).to have_css ".unverified", count: 1
expect(page).to have_content "You must verify your account to participate"
end
it "tell users from different geozones that the poll isn't available" do
sign_in(create(:user, :level_two))
render_inline Polls::PollComponent.new(create(:poll, geozone_restricted: true))
expect(page).to have_css ".cant-answer", count: 1
expect(page).to have_content "This poll is not available on your geozone"
end
it "informs users when they've already participated" do
user = create(:user, :level_two)
poll = create(:poll)
create(:poll_voter, user: user, poll: poll)
sign_in(user)
render_inline Polls::PollComponent.new(poll)
expect(page).to have_css ".already-answer", count: 1
expect(page).to have_content "You already have participated in this poll"
end
end
it "shows a link to poll stats if enabled" do
poll = create(:poll, :expired, name: "Poll with stats", stats_enabled: true)
render_inline Polls::PollComponent.new(poll)
expect(page).to have_link "Poll with stats", href: stats_poll_path(poll.slug)
expect(page).to have_link "Poll ended", href: stats_poll_path(poll.slug)
end
it "shows a link to poll results if enabled" do
poll = create(:poll, :expired, name: "Poll with results", stats_enabled: true, results_enabled: true)
render_inline Polls::PollComponent.new(poll)
expect(page).to have_link "Poll with results", href: results_poll_path(poll.slug)
expect(page).to have_link "Poll ended", href: results_poll_path(poll.slug)
end
it "shows SDG tags when that feature is enabled" do
Setting["feature.sdg"] = true
Setting["sdg.process.polls"] = true
poll = create(:poll, sdg_goals: [SDG::Goal[1]], sdg_targets: [SDG::Target["1.1"]])
render_inline Polls::PollComponent.new(poll)
expect(page).to have_css "img[alt='1. No Poverty']"
expect(page).to have_content "target 1.1"
end
end

View File

@@ -87,36 +87,6 @@ describe "Polls" do
expect(page).not_to have_link("Expired")
end
scenario "Displays a message asking anonymous users to sign in" do
create_list(:poll, 3)
visit polls_path
expect(page).to have_css(".not-logged-in", count: 3)
expect(page).to have_content("You must sign in or sign up to participate")
end
scenario "Displays a message asking unverified users to verify their account" do
create_list(:poll, 3)
user = create(:user)
login_as(user)
visit polls_path
expect(page).to have_css(".unverified", count: 3)
expect(page).to have_content("You must verify your account to participate")
end
scenario "Geozone poll" do
create(:poll, geozone_restricted: true)
login_as(create(:user, :level_two))
visit polls_path
expect(page).to have_css(".cant-answer", count: 1)
expect(page).to have_content("This poll is not available on your geozone")
end
scenario "Already participated in a poll" do
poll_with_question = create(:poll)
question = create(:poll_question, :yes_no, poll: poll_with_question)
@@ -133,36 +103,6 @@ describe "Polls" do
expect(page).to have_css(".already-answer", count: 1)
expect(page).to have_content("You already have participated in this poll")
end
scenario "Poll title and button link to stats if enabled" do
poll = create(:poll, :expired, name: "Poll with stats", stats_enabled: true)
visit polls_path(filter: "expired")
expect(page).to have_link("Poll with stats", href: stats_poll_path(poll.slug))
expect(page).to have_link("Poll ended", href: stats_poll_path(poll.slug))
end
scenario "Poll title and button link to results if enabled" do
poll = create(:poll, :expired, name: "Poll with results", stats_enabled: true, results_enabled: true)
visit polls_path(filter: "expired")
expect(page).to have_link("Poll with results", href: results_poll_path(poll.slug))
expect(page).to have_link("Poll ended", href: results_poll_path(poll.slug))
end
scenario "Shows SDG tags when feature is enabled" do
Setting["feature.sdg"] = true
Setting["sdg.process.polls"] = true
create(:poll, sdg_goals: [SDG::Goal[1]], sdg_targets: [SDG::Target["1.1"]])
visit polls_path
expect(page).to have_css "img[alt='1. No Poverty']"
expect(page).to have_content "target 1.1"
end
end
context "Show" do