diff --git a/app/assets/stylesheets/budgets/executions/heading.scss b/app/assets/stylesheets/budgets/executions/heading.scss
new file mode 100644
index 000000000..605f0debc
--- /dev/null
+++ b/app/assets/stylesheets/budgets/executions/heading.scss
@@ -0,0 +1,20 @@
+.budgets-executions-heading {
+ .budgets-executions-heading-investments {
+ $spacing: rem-calc(map-get($grid-column-gutter, medium));
+
+ @include flex-with-gap($spacing);
+ flex-wrap: wrap;
+
+ > * {
+ margin-bottom: $line-height;
+
+ @include breakpoint(medium) {
+ width: calc(100% / 2 - #{$spacing});
+ }
+
+ @include breakpoint(large) {
+ width: calc(100% / 3 - #{$spacing});
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/budgets/executions/investment.scss b/app/assets/stylesheets/budgets/executions/investment.scss
new file mode 100644
index 000000000..bd8176af1
--- /dev/null
+++ b/app/assets/stylesheets/budgets/executions/investment.scss
@@ -0,0 +1,54 @@
+.budget-executions-investment {
+ @include card;
+ border: 1px solid $border;
+ overflow: hidden;
+ position: relative;
+
+ a {
+ color: inherit;
+ display: block;
+
+ img {
+ height: $line-height * 9;
+ max-width: none;
+ min-width: 100%;
+ transition-duration: 0.3s;
+ transition-property: transform;
+ }
+
+ &:hover {
+ text-decoration: none;
+
+ img {
+ transform: scale(1.05);
+ }
+ }
+ }
+
+ h5 {
+ font-size: $base-font-size;
+ margin-bottom: 0;
+ }
+
+ .budget-execution-info {
+ padding: calc(#{$line-height} / 2);
+ }
+
+ .author {
+ color: $text-medium;
+ font-size: $small-font-size;
+ }
+
+ .budget-execution-content {
+ min-height: $line-height * 3;
+ }
+
+ .price {
+ color: $budget;
+ font-size: rem-calc(24);
+ }
+
+ &:hover:not(:focus-within) {
+ box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.2);
+ }
+}
diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss
index 407b94077..2dd68776b 100644
--- a/app/assets/stylesheets/participation.scss
+++ b/app/assets/stylesheets/participation.scss
@@ -1290,61 +1290,6 @@
}
}
-.budget-execution {
- @include card;
- border: 1px solid $border;
- overflow: hidden;
- position: relative;
-
- a {
- color: inherit;
- display: block;
-
- img {
- height: $line-height * 9;
- max-width: none;
- min-width: 100%;
- transition-duration: 0.3s;
- transition-property: transform;
- }
-
- &:hover {
- text-decoration: none;
-
- img {
- transform: scale(1.05);
- }
- }
- }
-
- h5 {
- font-size: $base-font-size;
- margin-bottom: 0;
- }
-
- .budget-execution-info {
- padding: calc(#{$line-height} / 2);
- }
-
- .author {
- color: $text-medium;
- font-size: $small-font-size;
- }
-
- .budget-execution-content {
- min-height: $line-height * 3;
- }
-
- .price {
- color: $budget;
- font-size: rem-calc(24);
- }
-
- &:hover:not(:focus-within) {
- box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.2);
- }
-}
-
// 07. Proposals successful
// -------------------------
diff --git a/app/components/budgets/executions/heading_component.html.erb b/app/components/budgets/executions/heading_component.html.erb
new file mode 100644
index 000000000..6a1599527
--- /dev/null
+++ b/app/components/budgets/executions/heading_component.html.erb
@@ -0,0 +1,11 @@
+
+
+ <%= heading.name %> (<%= investments.count %>)
+
+
+
+ <% investments.each do |investment| %>
+ <%= render Budgets::Executions::InvestmentComponent.new(investment) %>
+ <% end %>
+
+
diff --git a/app/components/budgets/executions/heading_component.rb b/app/components/budgets/executions/heading_component.rb
new file mode 100644
index 000000000..4f38ac5a7
--- /dev/null
+++ b/app/components/budgets/executions/heading_component.rb
@@ -0,0 +1,8 @@
+class Budgets::Executions::HeadingComponent < ApplicationComponent
+ attr_reader :heading, :investments
+
+ def initialize(heading, investments)
+ @heading = heading
+ @investments = investments
+ end
+end
diff --git a/app/components/budgets/executions/investment_component.html.erb b/app/components/budgets/executions/investment_component.html.erb
new file mode 100644
index 000000000..c990b2193
--- /dev/null
+++ b/app/components/budgets/executions/investment_component.html.erb
@@ -0,0 +1,15 @@
+
+ <%= render Budgets::Executions::ImageComponent.new(investment) %>
+
+
+
+ <%= link_to investment.title,
+ polymorphic_path(investment, anchor: "tab-milestones") %>
+
+ <%= investment.author.name %>
+
+
+ <%= investment.formatted_price %>
+
+
+
diff --git a/app/components/budgets/executions/investment_component.rb b/app/components/budgets/executions/investment_component.rb
new file mode 100644
index 000000000..4ba229d5d
--- /dev/null
+++ b/app/components/budgets/executions/investment_component.rb
@@ -0,0 +1,7 @@
+class Budgets::Executions::InvestmentComponent < ApplicationComponent
+ attr_reader :investment
+
+ def initialize(investment)
+ @investment = investment
+ end
+end
diff --git a/app/components/budgets/executions/investments_component.html.erb b/app/components/budgets/executions/investments_component.html.erb
new file mode 100644
index 000000000..93ffbb281
--- /dev/null
+++ b/app/components/budgets/executions/investments_component.html.erb
@@ -0,0 +1,3 @@
+<% investments_by_heading.each do |heading, investments| %>
+ <%= render Budgets::Executions::HeadingComponent.new(heading, investments) %>
+<% end %>
diff --git a/app/components/budgets/executions/investments_component.rb b/app/components/budgets/executions/investments_component.rb
new file mode 100644
index 000000000..d6f50bac7
--- /dev/null
+++ b/app/components/budgets/executions/investments_component.rb
@@ -0,0 +1,7 @@
+class Budgets::Executions::InvestmentsComponent < ApplicationComponent
+ attr_reader :investments_by_heading
+
+ def initialize(investments_by_heading)
+ @investments_by_heading = investments_by_heading
+ end
+end
diff --git a/app/views/budgets/executions/_investments.html.erb b/app/views/budgets/executions/_investments.html.erb
deleted file mode 100644
index e0f3abfdd..000000000
--- a/app/views/budgets/executions/_investments.html.erb
+++ /dev/null
@@ -1,26 +0,0 @@
-<% @investments_by_heading.each do |heading, investments| %>
-
- <%= heading.name %> (<%= investments.count %>)
-
-
- <% investments.each do |investment| %>
-
-
- <%= render Budgets::Executions::ImageComponent.new(investment) %>
-
-
-
- <%= link_to investment.title,
- budget_investment_path(@budget, investment, anchor: "tab-milestones") %>
-
- <%= investment.author.name %>
-
-
- <%= investment.formatted_price %>
-
-
-
-
- <% end %>
-
-<% end %>
diff --git a/app/views/budgets/executions/show.html.erb b/app/views/budgets/executions/show.html.erb
index 01c16fc44..4db70fb8f 100644
--- a/app/views/budgets/executions/show.html.erb
+++ b/app/views/budgets/executions/show.html.erb
@@ -45,7 +45,7 @@
<%= render Budgets::Executions::FiltersComponent.new(@budget, @statuses) %>
<% if @investments_by_heading.any? %>
- <%= render "budgets/executions/investments" %>
+ <%= render Budgets::Executions::InvestmentsComponent.new(@investments_by_heading) %>
<% else %>
<%= t("budgets.executions.no_winner_investments") %>
diff --git a/spec/components/budgets/executions/image_component_spec.rb b/spec/components/budgets/executions/image_component_spec.rb
index a40d8d1ce..4cead4790 100644
--- a/spec/components/budgets/executions/image_component_spec.rb
+++ b/spec/components/budgets/executions/image_component_spec.rb
@@ -1,13 +1,50 @@
require "rails_helper"
describe Budgets::Executions::ImageComponent do
+ let(:component) { Budgets::Executions::ImageComponent.new(investment) }
+
+ context "investment with image" do
+ let(:investment) { create(:budget_investment, :with_image) }
+
+ it "shows a milestone image if available" do
+ create(:milestone, :with_image, image_title: "Building in progress", milestoneable: investment)
+
+ render_inline component
+
+ expect(page).to have_css "img[alt='Building in progress']"
+ expect(page).not_to have_css "img[alt='#{investment.image.title}']"
+ end
+
+ it "shows the investment image if no milestone image is available" do
+ create(:milestone, :with_image, image_title: "Not related", milestoneable: create(:budget_investment))
+
+ render_inline component
+
+ expect(page).to have_css "img[alt='#{investment.image.title}']"
+ expect(page).not_to have_css "img[alt='Not related']"
+ end
+
+ it "shows the last milestone's image if the investment has multiple milestones with images associated" do
+ create(:milestone, milestoneable: investment)
+ create(:milestone, :with_image, image_title: "First image", milestoneable: investment)
+ create(:milestone, :with_image, image_title: "Second image", milestoneable: investment)
+ create(:milestone, milestoneable: investment)
+
+ render_inline component
+
+ expect(page).to have_css "img[alt='Second image']"
+ expect(page).not_to have_css "img[alt='First image']"
+ expect(page).not_to have_css "img[alt='#{investment.image.title}']"
+ end
+ end
+
context "investment and milestone without image" do
- let(:component) { Budgets::Executions::ImageComponent.new(Budget::Investment.new) }
+ let(:investment) { Budget::Investment.new(title: "New and empty") }
it "shows the default image" do
render_inline component
- expect(page).to have_css "img[src*='budget_execution_no_image']"
+ expect(page).to have_css "img[src*='budget_execution_no_image'][alt='New and empty']"
end
it "shows a custom default image when available" do
@@ -18,7 +55,7 @@ describe Budgets::Executions::ImageComponent do
render_inline component
- expect(page).to have_css "img[src$='logo_header-260x80.png']"
+ expect(page).to have_css "img[src$='logo_header-260x80.png'][alt='New and empty']"
expect(page).not_to have_css "img[src*='budget_execution_no_image']"
end
end
diff --git a/spec/system/budgets/executions_spec.rb b/spec/system/budgets/executions_spec.rb
index 5b721e257..88d21ec02 100644
--- a/spec/system/budgets/executions_spec.rb
+++ b/spec/system/budgets/executions_spec.rb
@@ -83,31 +83,6 @@ describe "Executions" do
end
context "Images" do
- scenario "renders milestone image if available" do
- milestone1 = create(:milestone, :with_image, milestoneable: investment1)
-
- visit budget_path(budget)
-
- click_link "See results"
- click_link "Milestones"
-
- expect(page).to have_content(investment1.title)
- expect(page).to have_css("img[alt='#{milestone1.image.title}']")
- end
-
- scenario "renders investment image if no milestone image is available" do
- create(:milestone, milestoneable: investment2)
- create(:image, imageable: investment2)
-
- visit budget_path(budget)
-
- click_link "See results"
- click_link "Milestones"
-
- expect(page).to have_content(investment2.title)
- expect(page).to have_css("img[alt='#{investment2.image.title}']")
- end
-
scenario "renders default image if no milestone nor investment images are available" do
create(:milestone, milestoneable: investment4)
@@ -116,23 +91,8 @@ describe "Executions" do
click_link "See results"
click_link "Milestones"
- expect(page).to have_content(investment4.title)
- expect(page).to have_css("img[alt='#{investment4.title}']")
- end
-
- scenario "renders last milestone's image if investment has multiple milestones with images associated" do
- create(:milestone, milestoneable: investment1)
- create(:milestone, :with_image, image_title: "First image", milestoneable: investment1)
- create(:milestone, :with_image, image_title: "Second image", milestoneable: investment1)
- create(:milestone, milestoneable: investment1)
-
- visit budget_path(budget)
-
- click_link "See results"
- click_link "Milestones"
-
- expect(page).to have_content(investment1.title)
- expect(page).to have_css("img[alt='Second image']")
+ expect(page).to have_content investment4.title
+ expect(page).to have_css "img[alt='#{investment4.title}']"
end
end
@@ -294,7 +254,7 @@ describe "Executions" do
visit budget_executions_path(budget)
- expect(page).to have_css(".budget-execution", count: 3)
+ expect(page).to have_css(".budget-executions-investment", count: 3)
expect(a_heading.name).to appear_before(m_heading.name)
expect(m_heading.name).to appear_before(z_heading.name)
end