Show a preview list of investments in the budget landing page

Note one of the tests dealing with random results is a bit flaky; since
it's a permutation selecting 7 objects out of 12, it will fail about
once every 4 million times. We think this is acceptable.

Co-Authored-By: Julian Nicolas Herrero <microweb10@gmail.com>
This commit is contained in:
decabeza
2020-04-21 11:11:17 +02:00
committed by Javi Martín
parent cb0818b7d6
commit 122195e33c
13 changed files with 417 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

View File

@@ -0,0 +1,78 @@
.investments-list {
@include flex-grid-row;
margin-top: $line-height;
> header {
@include grid-column-gutter;
width: 100%;
}
.investments-list-item {
@extend %panel;
@include xy-gutters;
border-radius: rem-calc(6);
display: flex;
flex-direction: column;
padding: 0;
margin-bottom: $line-height / 2;
position: relative;
width: 100%;
@include breakpoint(medium) {
$gap: rem-calc(map-get($grid-margin-gutters, medium));
width: calc(33.33% - #{$gap});
}
img {
border-top-left-radius: rem-calc(6);
border-top-right-radius: rem-calc(6);
height: $line-height * 10;
width: fit-content;
object-fit: cover;
@include breakpoint(medium only) {
height: $line-height * 10;
}
}
.budget-investment-content {
display: flex;
flex: 1;
flex-direction: column;
padding: 0 rem-calc(12) rem-calc(6);
min-height: $line-height * 8;
.investment-project-info {
flex: 1;
margin-bottom: $line-height * 2;
}
}
.read-more {
align-self: flex-end;
}
}
.supports-and-price {
@include has-fa-icon(award, solid, after);
background: $light;
display: flex;
flex-wrap: wrap;
padding: rem-calc(6) rem-calc(12);
.price-title,
.supports-title {
color: #7b7b7b;
font-size: $small-font-size;
text-transform: uppercase;
width: 100%;
}
&::after {
font-size: 2em;
margin-left: auto;
margin-right: 0;
transform: translateY(-25%);
}
}
}

View File

@@ -37,3 +37,5 @@
</div> </div>
</div> </div>
</div> </div>
<%= render Budgets::InvestmentsListComponent.new(budget) %>

View File

@@ -0,0 +1,33 @@
<div id="<%= dom_id(investment) %>" class="investments-list-item">
<% if investment.image.present? %>
<%= image_tag investment.image_url(:large), alt: investment.image.title.unicode_normalize %>
<% else %>
<%= image_tag "budget_investment_no_image.jpg", alt: investment.title %>
<% end %>
<% if investment.should_show_vote_count? || investment.should_show_price? %>
<div class="supports-and-price">
<% if investment.should_show_vote_count? %>
<span class="supports-title"><%= t("budgets.investments.investment.support_title") %></span>
<strong><%= investment.total_votes %></strong>
<% end %>
<% if investment.should_show_price? %>
<span class="price-title"><%= t("budgets.investments.investment.price_title") %></span>
<strong><%= investment.formatted_price %></strong>
<% end %>
</div>
<% end %>
<div class="budget-investment-content">
<% cache [locale_and_user_status(investment), "index", investment, investment.author] do %>
<h3><%= link_to investment.title, namespaced_budget_investment_path(investment) %></h3>
<%= render Budgets::Investments::InfoComponent.new(investment) %>
<%= link_to namespaced_budget_investment_path(investment), class: "read-more" do %>
<small><%= t("budgets.investments.index.read_more") %></small>
<% end %>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,8 @@
class Budgets::InvestmentComponent < ApplicationComponent
delegate :locale_and_user_status, :namespaced_budget_investment_path, to: :helpers
attr_reader :investment
def initialize(investment)
@investment = investment
end
end

View File

@@ -0,0 +1,21 @@
<% if investments.any? %>
<section class="investments-list">
<header>
<h2><%= t("budgets.show.investments_list") %></h2>
</header>
<% investments.each do |investment| %>
<%= render Budgets::InvestmentComponent.new(investment) %>
<% end %>
</section>
<% end %>
<% unless budget.informing? %>
<div class="row margin-top">
<div class="small-12 medium-6 large-4 small-centered column margin-top">
<%= link_to t("budgets.show.see_all_investments"),
budget_investments_path(budget),
class: "button expanded" %>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,24 @@
class Budgets::InvestmentsListComponent < ApplicationComponent
attr_reader :budget
def initialize(budget)
@budget = budget
end
def render?
budget.single_heading?
end
def investments(limit: 9)
case budget.phase
when "accepting", "reviewing"
budget.investments.sample(limit)
when "selecting", "valuating", "publishing_prices"
budget.investments.feasible.sample(limit)
when "balloting", "reviewing_ballots"
budget.investments.selected.sample(limit)
else
budget.investments.none
end
end
end

View File

@@ -130,6 +130,7 @@ en:
random: random random: random
confidence_score: highest rated confidence_score: highest rated
price: by price price: by price
read_more: "Read more"
share: share:
message: "I created the investment project %{title} in %{handle}. Create an investment project too!" message: "I created the investment project %{title} in %{handle}. Create an investment project too!"
show: show:
@@ -161,6 +162,8 @@ en:
confirm_group: confirm_group:
one: "You can only support investments in %{count} district. If you continue you cannot change the election of your district. Are you sure?" one: "You can only support investments in %{count} district. If you continue you cannot change the election of your district. Are you sure?"
other: "You can only support investments in %{count} districts. If you continue you cannot change the election of your district. Are you sure?" other: "You can only support investments in %{count} districts. If you continue you cannot change the election of your district. Are you sure?"
price_title: "Price"
support_title: "Supports"
supports: supports:
one: 1 support one: 1 support
other: "%{count} supports" other: "%{count} supports"
@@ -174,6 +177,8 @@ en:
price: "Total budget" price: "Total budget"
show: show:
see_results: See results see_results: See results
see_all_investments: "See all investments"
investments_list: "List of investments"
results: results:
link: Results link: Results
page_title: "%{budget} - Results" page_title: "%{budget} - Results"

View File

@@ -130,6 +130,7 @@ es:
random: Aleatorios random: Aleatorios
confidence_score: Mejor valorados confidence_score: Mejor valorados
price: Por coste price: Por coste
read_more: "Leer más"
share: share:
message: "He presentado el proyecto %{title} en %{handle}. ¡Presenta un proyecto tú también!" message: "He presentado el proyecto %{title} en %{handle}. ¡Presenta un proyecto tú también!"
show: show:
@@ -161,6 +162,8 @@ es:
confirm_group: confirm_group:
one: "Sólo puedes apoyar proyectos en %{count} distrito. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?" one: "Sólo puedes apoyar proyectos en %{count} distrito. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?"
other: "Sólo puedes apoyar proyectos en %{count} distritos. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?" other: "Sólo puedes apoyar proyectos en %{count} distritos. Si sigues adelante no podrás cambiar la elección de este distrito. ¿Estás seguro/a?"
price_title: "Coste"
support_title: "Apoyos"
supports: supports:
zero: Sin apoyos zero: Sin apoyos
one: 1 apoyo one: 1 apoyo
@@ -174,6 +177,8 @@ es:
price: "Presupuesto total" price: "Presupuesto total"
show: show:
see_results: Ver resultados see_results: Ver resultados
see_all_investments: "Ver todos los proyectos"
investments_list: "Lista de proyectos"
results: results:
link: Resultados link: Resultados
page_title: "%{budget} - Resultados" page_title: "%{budget} - Resultados"

View File

@@ -0,0 +1,59 @@
require "rails_helper"
describe Budgets::InvestmentComponent, type: :component do
let(:user) { create(:user) }
before do
allow(controller).to receive(:current_user).and_return(user)
end
it "shows the investment image when defined" do
investment = create(:budget_investment, :with_image)
render_inline Budgets::InvestmentComponent.new(investment)
expect(page).not_to have_css "img[src*='budget_investment_no_image']"
expect(page).to have_css "img[alt='#{investment.image.title}']"
end
it "shows the default image when investment has not an image defined" do
investment = create(:budget_investment)
render_inline Budgets::InvestmentComponent.new(investment)
expect(page).to have_css "img[src*='budget_investment_no_image']"
end
it "shows supports count when budget is valuating" do
budget = create(:budget, :valuating)
investment = create(:budget_investment, budget: budget)
render_inline Budgets::InvestmentComponent.new(investment)
expect(page).to have_content "Supports"
budget.update!(phase: (Budget::Phase::PHASE_KINDS - ["valuating"]).sample)
render_inline Budgets::InvestmentComponent.new(investment)
expect(page).not_to have_content "Supports"
end
it "shows price when investment is selected and budget prices are published" do
budget = create(:budget, :finished)
investment = create(:budget_investment, :selected, budget: budget)
render_inline Budgets::InvestmentComponent.new(investment)
expect(page).to have_content "Price"
budget.update!(phase: (Budget::Phase::PHASE_KINDS - Budget::Phase::PUBLISHED_PRICES_PHASES).sample)
render_inline Budgets::InvestmentComponent.new(investment)
expect(page).not_to have_content "Price"
end
it "shows investment information" do
investment = create(:budget_investment)
render_inline Budgets::InvestmentComponent.new(investment)
expect(page).to have_link investment.title
expect(page).to have_link "Read more"
expect(page).to have_css ".investment-project-info"
end
end

View File

@@ -0,0 +1,63 @@
require "rails_helper"
describe Budgets::InvestmentsListComponent, type: :component do
describe "#investments_preview_list" do
let(:budget) { create(:budget, :accepting) }
let(:group) { create(:budget_group, budget: budget) }
let(:heading) { create(:budget_heading, group: group) }
let(:component) { Budgets::InvestmentsListComponent.new(budget) }
let!(:normal_investments) { create_list(:budget_investment, 4, heading: heading) }
let!(:feasible_investments) { create_list(:budget_investment, 4, :feasible, heading: heading) }
let!(:selected_investments) { create_list(:budget_investment, 4, :selected, heading: heading) }
it "returns an empty relation if phase is informing or finished" do
%w[informing finished].each do |phase_name|
budget.phase = phase_name
expect(component.investments).to be_empty
end
end
it "returns a maximum 9 investments by default" do
expect(budget.investments.count).to be 12
expect(component.investments.count).to be 9
end
it "returns a different random array of investments every time" do
expect(component.investments(limit: 7)).not_to eq component.investments(limit: 7)
end
it "returns any investments while accepting and reviewing" do
%w[accepting reviewing].each do |phase_name|
budget.phase = phase_name
investments = component.investments(limit: 9)
expect(investments & normal_investments).not_to be_empty
expect(investments & feasible_investments).not_to be_empty
expect(investments & selected_investments).not_to be_empty
end
end
it "returns only feasible investments if phase is selecting, valuating or publishing_prices" do
%w[selecting valuating publishing_prices].each do |phase_name|
budget.phase = phase_name
investments = component.investments(limit: 6)
expect(feasible_investments + selected_investments).to include(*investments)
end
end
it "returns only selected investments if phase is balloting or reviewing_ballots" do
%w[balloting reviewing_ballots].each do |phase_name|
budget.phase = phase_name
investments = component.investments(limit: 3)
expect(selected_investments).to include(*investments)
end
end
end
end

View File

@@ -270,6 +270,123 @@ describe "Budgets" do
expect(page).to have_link "See results" expect(page).to have_link "See results"
end end
scenario "Show link to see all investments" do
budget = create(:budget)
group = create(:budget_group, budget: budget)
heading = create(:budget_heading, group: group)
create_list(:budget_investment, 3, :selected, heading: heading, price: 999)
budget.update!(phase: "informing")
visit budget_path(budget)
expect(page).not_to have_link "See all investments"
%w[accepting reviewing selecting valuating].each do |phase_name|
budget.update!(phase: phase_name)
visit budget_path(budget)
expect(page).to have_link "See all investments",
href: budget_investments_path(budget)
end
%w[publishing_prices balloting reviewing_ballots].each do |phase_name|
budget.update!(phase: phase_name)
visit budget_path(budget)
expect(page).to have_link "See all investments",
href: budget_investments_path(budget)
end
budget.update!(phase: "finished")
visit budget_path(budget)
expect(page).to have_link "See all investments",
href: budget_investments_path(budget)
end
scenario "Show investments list" do
budget = create(:budget)
group = create(:budget_group, budget: budget)
heading = create(:budget_heading, group: group)
create_list(:budget_investment, 3, :selected, heading: heading, price: 999)
%w[informing finished].each do |phase_name|
budget.update!(phase: phase_name)
visit budget_path(budget)
expect(page).not_to have_content "List of investments"
expect(page).not_to have_css ".investments-list"
expect(page).not_to have_css ".budget-investment"
end
%w[accepting reviewing selecting].each do |phase_name|
budget.update!(phase: phase_name)
visit budget_path(budget)
within(".investments-list") do
expect(page).to have_content "List of investments"
expect(page).not_to have_content "SUPPORTS"
expect(page).not_to have_content "PRICE"
end
end
budget.update!(phase: "valuating")
visit budget_path(budget)
within(".investments-list") do
expect(page).to have_content "List of investments"
expect(page).to have_content("SUPPORTS", count: 3)
expect(page).not_to have_content "PRICE"
end
%w[publishing_prices balloting reviewing_ballots].each do |phase_name|
budget.update!(phase: phase_name)
visit budget_path(budget)
within(".investments-list") do
expect(page).to have_content "List of investments"
expect(page).to have_content("PRICE", count: 3)
end
end
end
scenario "Do not show investments list when budget has multiple headings" do
budget = create(:budget)
group = create(:budget_group, budget: budget)
heading_1 = create(:budget_heading, group: group)
create(:budget_heading, group: group)
create_list(:budget_investment, 3, :selected, heading: heading_1, price: 999)
%w[accepting reviewing selecting].each do |phase_name|
budget.update!(phase: phase_name)
visit budget_path(budget)
expect(page).not_to have_css ".investments-list"
end
budget.update!(phase: "valuating")
visit budget_path(budget)
expect(page).not_to have_css ".investments-list"
%w[publishing_prices balloting reviewing_ballots].each do |phase_name|
budget.update!(phase: phase_name)
visit budget_path(budget)
expect(page).not_to have_css ".investments-list"
end
end
end end
context "In Drafting phase" do context "In Drafting phase" do

View File

@@ -54,7 +54,7 @@ describe "Budget Investments" do
unfeasible_investment = create(:budget_investment, :unfeasible, heading: heading) unfeasible_investment = create(:budget_investment, :unfeasible, heading: heading)
visit budget_path(budget) visit budget_path(budget)
click_link "More hospitals" click_link "See all investments"
expect(page).to have_selector("#budget-investments .budget-investment", count: 3) expect(page).to have_selector("#budget-investments .budget-investment", count: 3)
investments.each do |investment| investments.each do |investment|
@@ -1245,7 +1245,7 @@ describe "Budget Investments" do
first(:link, "Participatory budgeting").click first(:link, "Participatory budgeting").click
click_link "More hospitals" click_link "See all investments"
within("#budget_investment_#{investment1.id}") do within("#budget_investment_#{investment1.id}") do
expect(page).to have_content investment1.title expect(page).to have_content investment1.title