<%= f.text_field :price, maxlength: 8 %>
+ <% if @heading.budget.approval_voting? %>
+ <%= f.number_field :max_ballot_lines,
+ hint: t("admin.budget_headings.form.max_ballot_lines_info") %>
+
+ <% end %>
+
<%= f.text_field :population,
maxlength: 8,
data: { toggle_focus: "population-info" },
diff --git a/app/views/admin/budget_headings/index.html.erb b/app/views/admin/budget_headings/index.html.erb
index e7cf193f6..0abc51073 100644
--- a/app/views/admin/budget_headings/index.html.erb
+++ b/app/views/admin/budget_headings/index.html.erb
@@ -13,6 +13,9 @@
| <%= link_to heading.name, edit_admin_budget_group_heading_path(@budget, @group, heading) %> |
<%= @budget.formatted_heading_price(heading) %> |
+ <% if @budget.approval_voting? %>
+ <%= heading.max_ballot_lines %> |
+ <% end %>
<%= heading.population %> |
<%= heading.allow_custom_content ? t("admin.shared.true_value") : t("admin.shared.false_value") %>
diff --git a/app/views/admin/budgets/_form.html.erb b/app/views/admin/budgets/_form.html.erb
index 057e676f7..b0c05e38d 100644
--- a/app/views/admin/budgets/_form.html.erb
+++ b/app/views/admin/budgets/_form.html.erb
@@ -16,7 +16,12 @@
<%= f.select :phase, budget_phases_select_options %>
-
+
+
+ <%= f.select :voting_style, budget_voting_styles_select_options %>
+
+
+
<%= f.select :currency_symbol, budget_currency_symbol_select_options %>
diff --git a/app/views/budgets/ballot/_progress_bar.html.erb b/app/views/budgets/ballot/_progress_bar.html.erb
index b3c563240..8a41ef59b 100644
--- a/app/views/budgets/ballot/_progress_bar.html.erb
+++ b/app/views/budgets/ballot/_progress_bar.html.erb
@@ -10,9 +10,5 @@
- <%= t("budgets.progress_bar.assigned") %><%= ballot.formatted_amount_spent(heading) %>
-
- <%= t("budgets.progress_bar.available") %>
- <%= ballot.formatted_amount_available(heading) %>
-
+ <%= render "budgets/ballot/progress_bar/#{ballot.budget.voting_style}", ballot: ballot, heading: heading %>
diff --git a/app/views/budgets/ballot/progress_bar/_approval.html.erb b/app/views/budgets/ballot/progress_bar/_approval.html.erb
new file mode 100644
index 000000000..95961a824
--- /dev/null
+++ b/app/views/budgets/ballot/progress_bar/_approval.html.erb
@@ -0,0 +1,3 @@
+<%= sanitize(t("budgets.progress_bar.votes",
+ count: ballot.amount_spent(heading),
+ limit: ballot.amount_limit(heading))) %>
diff --git a/app/views/budgets/ballot/progress_bar/_knapsack.html.erb b/app/views/budgets/ballot/progress_bar/_knapsack.html.erb
new file mode 100644
index 000000000..b46d6d6de
--- /dev/null
+++ b/app/views/budgets/ballot/progress_bar/_knapsack.html.erb
@@ -0,0 +1,5 @@
+<%= t("budgets.progress_bar.assigned") %><%= ballot.formatted_amount_spent(heading) %>
+
+ <%= t("budgets.progress_bar.available") %>
+ <%= ballot.formatted_amount_available(heading) %>
+
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index f03a38d09..49d3e7bc2 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -127,6 +127,9 @@ ignore_unused:
- "budgets.phase.*"
- "budgets.investments.index.orders.*"
- "budgets.index.section_header.*"
+ - "budgets.ballots.show.amount_available.*"
+ - "budgets.ballots.show.amount_limit.*"
+ - "budgets.ballots.show.amount_spent.*"
- "budgets.investments.index.sidebar.voted_info.*"
- "activerecord.*"
- "activemodel.*"
diff --git a/config/locales/en/activerecord.yml b/config/locales/en/activerecord.yml
index cd9b4c6f7..9ea6e1c51 100644
--- a/config/locales/en/activerecord.yml
+++ b/config/locales/en/activerecord.yml
@@ -141,6 +141,9 @@ en:
description_finished: "Description when the budget is finished"
phase: "Phase"
currency_symbol: "Currency"
+ voting_style: "Final voting style"
+ voting_style_knapsack: "Knapsack"
+ voting_style_approval: "Approval"
budget/translation:
name: "Name"
budget/investment:
@@ -203,6 +206,7 @@ en:
name: "Heading name"
price: "Amount"
population: "Population (optional)"
+ max_ballot_lines: "Votes allowed"
budget/heading/translation:
name: "Heading name"
budget/phase:
diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml
index 61f1eca24..2371bc434 100644
--- a/config/locales/en/admin.yml
+++ b/config/locales/en/admin.yml
@@ -149,6 +149,7 @@ en:
success_notice: "Heading deleted successfully"
unable_notice: "You cannot delete a Heading that has associated investments"
form:
+ max_ballot_lines_info: 'Maximum number of projects a user can vote on this heading during the "Voting projects" phase. Only for budgets using approval voting.'
population_info: "Budget Heading population field is used for Statistic purposes at the end of the Budget to show for each Heading that represents an area with population what percentage voted. The field is optional so you can leave it empty if it doesn't apply."
coordinates_info: "If latitude and longitude are provided, the investments page for this heading will include a map. This map will be centered using those coordinates."
content_blocks_info: "If allow content block is checked, you will be able to create custom content related to this heading from the section Settings > Custom content blocks. This content will appear on the investments page for this heading."
diff --git a/config/locales/en/budgets.yml b/config/locales/en/budgets.yml
index 8047f44ad..758ab8c84 100644
--- a/config/locales/en/budgets.yml
+++ b/config/locales/en/budgets.yml
@@ -4,11 +4,22 @@ en:
show:
title: Your ballot
amount_available:
- knapsack: "You still have %{amount} to invest."
+ knapsack: "You still have %{count} to invest."
+ approval:
+ zero: "You can still cast %{count} votes."
+ one: "You can still cast %{count} vote."
+ other: "You can still cast %{count} votes."
amount_spent:
- knapsack: "Amount spent %{amount}"
+ knapsack: "Amount spent %{count}"
+ approval:
+ zero: "Votes cast: %{count}"
+ one: "Votes cast: %{count}"
+ other: "Votes cast: %{count}"
amount_limit:
- knapsack: "%{amount}"
+ knapsack: "%{count}"
+ approval:
+ one: "You can vote 1 project"
+ other: "You can vote up to %{count} projects"
no_balloted_group_yet: "You have not voted on this group yet, go vote!"
remove: Remove vote
voted:
@@ -25,6 +36,7 @@ en:
not_enough_money: "You have already assigned the available budget. Remember you can %{change_ballot} at any time"
no_ballots_allowed: Selecting phase is closed
different_heading_assigned: "You have already voted a different heading: %{heading_link}"
+ not_enough_available_votes: "You have reached the maximum number of votes allowed"
change_ballot: change your votes
casted_offline: You have already participated offline
groups:
@@ -92,8 +104,12 @@ en:
knapsack:
one: "You voted one proposal with a cost of %{amount_spent}"
other: "You voted %{count} proposals with a cost of %{amount_spent}"
+ approval:
+ one: "You voted one proposal"
+ other: "You voted %{count} proposals"
change_vote_info:
knapsack: "You can %{link} at any time until the close of this phase. No need to spend all the money available."
+ approval: "You can %{link} at any time until the close of this phase."
change_vote_link: "change your vote"
different_heading_assigned: "You have active votes in another heading: %{heading_link}"
change_ballot: "If your change your mind you can remove your votes in %{check_ballot} and start again."
@@ -154,6 +170,10 @@ en:
progress_bar:
assigned: "You have assigned: "
available: "Available budget: "
+ votes:
+ zero: "You have selected 0 projects out of %{limit}"
+ one: "You have selected 1 project out of %{limit}"
+ other: "You have selected %{count} projects out of %{limit}"
show:
group: Group
phase: Actual phase
diff --git a/config/locales/es/activerecord.yml b/config/locales/es/activerecord.yml
index 0e1f39c92..c3bc2c3e9 100644
--- a/config/locales/es/activerecord.yml
+++ b/config/locales/es/activerecord.yml
@@ -143,6 +143,9 @@ es:
description_finished: "Descripción cuando el presupuesto ha finalizado / Resultados"
phase: "Fase"
currency_symbol: "Divisa"
+ voting_style: "Estilo de la votación final"
+ voting_style_knapsack: Bolsa de dinero
+ voting_style_approval: Por aprobación
budget/translation:
name: "Nombre"
budget/investment:
@@ -205,6 +208,7 @@ es:
name: "Nombre de la partida"
price: "Cantidad"
population: "Población (opcional)"
+ max_ballot_lines: "Votos permitidos"
budget/heading/translation:
name: "Nombre de la partida"
budget/phase:
diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml
index 0978bd7f7..9cbf60417 100644
--- a/config/locales/es/admin.yml
+++ b/config/locales/es/admin.yml
@@ -149,6 +149,7 @@ es:
success_notice: "Partida presupuestaria eliminada correctamente"
unable_notice: "No se puede eliminar una partida presupuestaria con proyectos asociados"
form:
+ max_ballot_lines_info: 'Máximo número de proyectos que un usuario puede votar en esta partida durante la fase "Votación final". Solamente se aplica a presupuestos con votación por aprobación.'
population_info: "El campo población de las partidas presupuestarias se usa con fines estadísticos únicamente, con el objetivo de mostrar el porcentaje de votos habidos en cada partida que represente un área con población. Es un campo opcional, así que puedes dejarlo en blanco si no aplica."
coordinates_info: "Si se añaden los campos latitud y longitud, en la página de proyectos de esta partida aparecerá un mapa, que estará centrado en esas coordenadas."
content_blocks_info: "Si se permite el bloque de contenidos, se tendrá la oportunidad de crear bloques de contenido relativos a esta partida desde la sección Configuración > Personalizar bloques. Este contenido aparecerá en la página de proyectos de esta partida."
diff --git a/config/locales/es/budgets.yml b/config/locales/es/budgets.yml
index 9ccc52159..0030ebe59 100644
--- a/config/locales/es/budgets.yml
+++ b/config/locales/es/budgets.yml
@@ -4,11 +4,22 @@ es:
show:
title: Mis votos
amount_available:
- knapsack: "Te quedan %{amount} para invertir"
+ knapsack: "Te quedan %{count} para invertir"
+ approval:
+ zero: "Te quedan 0 votos disponibles"
+ one: "Te queda 1 voto disponible"
+ other: "Te quedan %{count} votos disponibles"
amount_spent:
- knapsack: "Coste total %{amount}"
+ knapsack: "Coste total %{count}"
+ approval:
+ zero: "Votos: %{count}"
+ one: "Votos: %{count}"
+ other: "Votos: %{count}"
amount_limit:
- knapsack: "%{amount}"
+ knapsack: "%{count}"
+ approval:
+ one: "Puedes votar 1 proyecto"
+ other: "Puedes votar hasta %{count} proyectos"
no_balloted_group_yet: "Todavía no has votado proyectos de este grupo, ¡vota!"
remove: Quitar voto
voted:
@@ -25,6 +36,7 @@ es:
not_enough_money: "Ya has asignado el presupuesto disponible. Recuerda que puedes %{change_ballot} en cualquier momento"
no_ballots_allowed: El periodo de votación está cerrado.
different_heading_assigned: "Ya has votado proyectos de otra partida: %{heading_link}"
+ not_enough_available_votes: "No tienes más votos disponibles"
change_ballot: cambiar tus votos
casted_offline: Ya has participado presencialmente
groups:
@@ -92,8 +104,12 @@ es:
knapsack:
one: "Has votado un proyecto por un valor de %{amount_spent}"
other: "Has votado %{count} proyectos por un valor de %{amount_spent}"
+ approval:
+ one: "Has votado un proyecto"
+ other: "Has votado %{count} proyectos"
change_vote_info:
knapsack: "Puedes %{link} en cualquier momento hasta el cierre de esta fase. No hace falta que gastes todo el dinero disponible."
+ approval: "Puedes %{link} en cualquier momento hasta el cierre de esta fase."
change_vote_link: "cambiar tus votos"
different_heading_assigned: "Ya apoyaste proyectos de otra sección del presupuesto: %{heading_link}"
change_ballot: "Si cambias de opinión puedes borrar tus votos en %{check_ballot} y volver a empezar."
@@ -154,6 +170,10 @@ es:
progress_bar:
assigned: "Has asignado: "
available: "Presupuesto disponible: "
+ votes:
+ zero: "Has seleccionado 0 proyectos de %{limit}"
+ one: "Has seleccionado 1 proyecto de %{limit}"
+ other: "Has seleccionado %{count} proyectos de %{limit}"
show:
group: Grupo
phase: Fase actual
diff --git a/db/migrate/20190418114431_add_approval_voting_fields.rb b/db/migrate/20190418114431_add_approval_voting_fields.rb
new file mode 100644
index 000000000..6d48c15ff
--- /dev/null
+++ b/db/migrate/20190418114431_add_approval_voting_fields.rb
@@ -0,0 +1,6 @@
+class AddApprovalVotingFields < ActiveRecord::Migration[4.2]
+ def change
+ add_column :budgets, :voting_style, :string, default: "knapsack"
+ add_column :budget_headings, :max_ballot_lines, :integer, default: 1
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 573774563..f2002efc6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -214,6 +214,7 @@ ActiveRecord::Schema.define(version: 20200519120717) do
t.boolean "allow_custom_content", default: false
t.text "latitude"
t.text "longitude"
+ t.integer "max_ballot_lines", default: 1
t.datetime "created_at"
t.datetime "updated_at"
t.index ["group_id"], name: "index_budget_headings_on_group_id"
@@ -360,6 +361,7 @@ ActiveRecord::Schema.define(version: 20200519120717) do
t.text "description_drafting"
t.text "description_publishing_prices"
t.text "description_informing"
+ t.string "voting_style", default: "knapsack"
end
create_table "campaigns", id: :serial, force: :cascade do |t|
diff --git a/spec/factories/budgets.rb b/spec/factories/budgets.rb
index 877742f5e..0d3b9f18e 100644
--- a/spec/factories/budgets.rb
+++ b/spec/factories/budgets.rb
@@ -55,6 +55,14 @@ FactoryBot.define do
results_enabled { true }
stats_enabled { true }
end
+
+ trait :knapsack do
+ voting_style { "knapsack" }
+ end
+
+ trait :approval do
+ voting_style { "approval" }
+ end
end
factory :budget_group, class: "Budget::Group" do
diff --git a/spec/helpers/budgets_helper_spec.rb b/spec/helpers/budgets_helper_spec.rb
new file mode 100644
index 000000000..832f510d8
--- /dev/null
+++ b/spec/helpers/budgets_helper_spec.rb
@@ -0,0 +1,14 @@
+require "rails_helper"
+
+describe BudgetsHelper do
+ describe "#budget_voting_styles_select_options" do
+ it "provides vote kinds" do
+ types = [
+ ["Knapsack", "knapsack"],
+ ["Approval", "approval"]
+ ]
+
+ expect(budget_voting_styles_select_options).to eq(types)
+ end
+ end
+end
diff --git a/spec/models/budget/ballot/line_spec.rb b/spec/models/budget/ballot/line_spec.rb
index 2c3c8f1e4..c9c8a887d 100644
--- a/spec/models/budget/ballot/line_spec.rb
+++ b/spec/models/budget/ballot/line_spec.rb
@@ -40,6 +40,41 @@ describe Budget::Ballot::Line do
end
end
+ describe "Approval voting" do
+ before do
+ budget.update!(voting_style: "approval")
+ heading.update!(max_ballot_lines: 1)
+ end
+
+ it "is valid if there are votes left" do
+ expect(ballot_line).to be_valid
+ end
+
+ it "is not valid if there are no votes left" do
+ create(:budget_ballot_line, ballot: ballot,
+ investment: create(:budget_investment, :selected, heading: heading))
+
+ expect(ballot_line).not_to be_valid
+ end
+
+ it "is valid if insufficient funds but enough votes" do
+ investment.update!(price: heading.price + 1)
+
+ expect(ballot_line).to be_valid
+ end
+
+ it "validates votes when creating lines at the same time", :race_condition do
+ other_investment = create(:budget_investment, :selected, heading: heading)
+ other_line = build(:budget_ballot_line, ballot: ballot, investment: other_investment)
+
+ [ballot_line, other_line].map do |line|
+ Thread.new { line.save }
+ end.each(&:join)
+
+ expect(Budget::Ballot::Line.count).to be 1
+ end
+ end
+
describe "Selectibility" do
it "is not valid if investment is unselected" do
investment.update!(selected: false)
diff --git a/spec/models/budget/ballot_spec.rb b/spec/models/budget/ballot_spec.rb
index a06aad9d4..64cebae2e 100644
--- a/spec/models/budget/ballot_spec.rb
+++ b/spec/models/budget/ballot_spec.rb
@@ -65,6 +65,20 @@ describe Budget::Ballot do
expect(ballot.amount_spent(heading1)).to eq 50000
expect(ballot.amount_spent(heading2)).to eq 20000
end
+
+ it "returns the votes cast on a specific heading for approval voting" do
+ budget = create(:budget, :approval)
+ heading1 = create(:budget_heading, budget: budget, max_ballot_lines: 2)
+ heading2 = create(:budget_heading, budget: budget, max_ballot_lines: 3)
+ ballot = create(:budget_ballot, budget: budget)
+
+ ballot.investments << create(:budget_investment, :selected, heading: heading1)
+ ballot.investments << create(:budget_investment, :selected, heading: heading1)
+ ballot.investments << create(:budget_investment, :selected, heading: heading2)
+
+ expect(ballot.amount_spent(heading1)).to eq 2
+ expect(ballot.amount_spent(heading2)).to eq 1
+ end
end
describe "#amount_available" do
@@ -91,6 +105,20 @@ describe Budget::Ballot do
expect(ballot.amount_available(heading1)).to eq 500
end
+
+ it "returns the amount of votes left for approval voting" do
+ budget = create(:budget, :approval)
+ heading1 = create(:budget_heading, budget: budget, max_ballot_lines: 2)
+ heading2 = create(:budget_heading, budget: budget, max_ballot_lines: 3)
+ ballot = create(:budget_ballot, budget: budget)
+
+ ballot.investments << create(:budget_investment, :selected, heading: heading1)
+ ballot.investments << create(:budget_investment, :selected, heading: heading1)
+ ballot.investments << create(:budget_investment, :selected, heading: heading2)
+
+ expect(ballot.amount_available(heading1)).to eq 0
+ expect(ballot.amount_available(heading2)).to eq 2
+ end
end
describe "#heading_for_group" do
diff --git a/spec/models/budget/heading_spec.rb b/spec/models/budget/heading_spec.rb
index 61dfa7d2b..a2b77a7d5 100644
--- a/spec/models/budget/heading_spec.rb
+++ b/spec/models/budget/heading_spec.rb
@@ -323,4 +323,13 @@ describe Budget::Heading do
expect(Budget::Heading.allow_custom_content).to eq [translated_heading]
end
end
+
+ describe "#max_ballot_lines" do
+ it "must be at least 1" do
+ expect(build(:budget_heading, max_ballot_lines: 1)).to be_valid
+ expect(build(:budget_heading, max_ballot_lines: 10)).to be_valid
+ expect(build(:budget_heading, max_ballot_lines: -1)).not_to be_valid
+ expect(build(:budget_heading, max_ballot_lines: 0)).not_to be_valid
+ end
+ end
end
diff --git a/spec/models/budget/investment_spec.rb b/spec/models/budget/investment_spec.rb
index 8a1b2ef29..1dad0ca50 100644
--- a/spec/models/budget/investment_spec.rb
+++ b/spec/models/budget/investment_spec.rb
@@ -1086,6 +1086,31 @@ describe Budget::Investment do
expect(inv2.reason_for_not_being_ballotable_by(user, ballot)).to eq(:not_enough_money)
end
+
+ context "Approval voting" do
+ before { budget.update!(phase: "balloting", voting_style: "approval") }
+ let(:group) { create(:budget_group, budget: budget) }
+
+ it "does not reject investments based on available money" do
+ heading = create(:budget_heading, group: group, max_ballot_lines: 2)
+ inv1 = create(:budget_investment, :selected, heading: heading, price: heading.price)
+ inv2 = create(:budget_investment, :selected, heading: heading, price: heading.price)
+ ballot = create(:budget_ballot, user: user, budget: budget, investments: [inv1])
+
+ expect(inv2.reason_for_not_being_ballotable_by(user, ballot)).to be nil
+ end
+
+ it "rejects if not enough available votes" do
+ heading = create(:budget_heading, group: group, max_ballot_lines: 1)
+ inv1 = create(:budget_investment, :selected, heading: heading)
+ inv2 = create(:budget_investment, :selected, heading: heading)
+ ballot = create(:budget_ballot, user: user, budget: budget, investments: [inv1])
+
+ reason = inv2.reason_for_not_being_ballotable_by(user, ballot)
+
+ expect(reason).to eq(:not_enough_available_votes)
+ end
+ end
end
end
end
diff --git a/spec/models/budget_spec.rb b/spec/models/budget_spec.rb
index 3b85e61d9..7a8a37e9d 100644
--- a/spec/models/budget_spec.rb
+++ b/spec/models/budget_spec.rb
@@ -355,4 +355,25 @@ describe Budget do
expect(budget.investments_milestone_tags).to eq(["tag1"])
end
end
+
+ describe "#voting_style" do
+ context "Validations" do
+ it { expect(build(:budget, :approval)).to be_valid }
+ it { expect(build(:budget, :knapsack)).to be_valid }
+ it { expect(build(:budget, voting_style: "Oups!")).not_to be_valid }
+ end
+
+ context "Related supportive methods" do
+ describe "#approval_voting?" do
+ it { expect(build(:budget, :approval).approval_voting?).to be true }
+ it { expect(build(:budget, :knapsack).approval_voting?).to be false }
+ end
+ end
+
+ context "Defaults" do
+ it "defaults to knapsack voting style" do
+ expect(build(:budget).voting_style).to eq "knapsack"
+ end
+ end
+ end
end
diff --git a/spec/system/admin/budget_headings_spec.rb b/spec/system/admin/budget_headings_spec.rb
index e626521cc..b92d4ce6e 100644
--- a/spec/system/admin/budget_headings_spec.rb
+++ b/spec/system/admin/budget_headings_spec.rb
@@ -171,6 +171,30 @@ describe "Admin budget headings" do
expect(page).to have_css(".is-invalid-label", text: "Amount")
expect(page).to have_content "can't be blank"
end
+
+ describe "Max votes is optional", :js do
+ scenario "do no show max_ballot_lines field for knapsack budgets" do
+ visit new_admin_budget_group_heading_path(budget, group)
+
+ expect(page).not_to have_field "Votes allowed"
+ end
+
+ scenario "create heading with max_ballot_lines for appoval budgets" do
+ budget.update!(voting_style: "approval")
+
+ visit new_admin_budget_group_heading_path(budget, group)
+
+ expect(page).to have_field "Votes allowed", with: 1
+
+ fill_in "Heading name", with: "All City"
+ fill_in "Amount", with: "1000"
+ fill_in "Votes allowed", with: 14
+ click_button "Create new heading"
+
+ expect(page).to have_content "Heading created successfully!"
+ within("tr", text: "All City") { expect(page).to have_content 14 }
+ end
+ end
end
context "Edit" do
diff --git a/spec/system/admin/budgets_spec.rb b/spec/system/admin/budgets_spec.rb
index 5ed404756..914c813c8 100644
--- a/spec/system/admin/budgets_spec.rb
+++ b/spec/system/admin/budgets_spec.rb
@@ -102,7 +102,7 @@ describe "Admin budgets" do
end
context "New" do
- scenario "Create budget" do
+ scenario "Create budget - Knapsack voting (default)" do
visit admin_budgets_path
click_link "Create new budget"
@@ -113,6 +113,21 @@ describe "Admin budgets" do
expect(page).to have_content "New participatory budget created successfully!"
expect(page).to have_content "M30 - Summer campaign"
+ expect(Budget.last.voting_style).to eq "knapsack"
+ end
+
+ scenario "Create budget - Approval voting", :js do
+ visit admin_budgets_path
+ click_link "Create new budget"
+
+ fill_in "Name", with: "M30 - Summer campaign"
+ select "Accepting projects", from: "budget[phase]"
+ select "Approval", from: "Final voting style"
+ click_button "Create Budget"
+
+ expect(page).to have_content "New participatory budget created successfully!"
+ expect(page).to have_content "M30 - Summer campaign"
+ expect(Budget.last.voting_style).to eq "approval"
end
scenario "Name is mandatory" do
diff --git a/spec/system/budgets/votes_spec.rb b/spec/system/budgets/votes_spec.rb
index 4291a764c..ed7048ca5 100644
--- a/spec/system/budgets/votes_spec.rb
+++ b/spec/system/budgets/votes_spec.rb
@@ -1,9 +1,10 @@
require "rails_helper"
describe "Votes" do
- describe "Investments" do
- let(:manuela) { create(:user, verified_at: Time.current) }
- let(:budget) { create(:budget, :selecting) }
+ let(:manuela) { create(:user, verified_at: Time.current) }
+
+ context "Investments - Knapsack" do
+ let(:budget) { create(:budget, phase: "selecting") }
let(:group) { create(:budget_group, budget: budget) }
let(:heading) { create(:budget_heading, group: group) }
@@ -194,4 +195,33 @@ describe "Votes" do
end
end
end
+
+ context "Investments - Approval" do
+ let(:budget) { create(:budget, :balloting, :approval) }
+ before { login_as(manuela) }
+
+ scenario "Budget limit is ignored", :js do
+ group = create(:budget_group, budget: budget)
+ heading = create(:budget_heading, group: group, max_ballot_lines: 2)
+ investment1 = create(:budget_investment, :selected, heading: heading, price: heading.price)
+ investment2 = create(:budget_investment, :selected, heading: heading, price: heading.price)
+
+ visit budget_investments_path(budget, heading_id: heading.id)
+
+ add_to_ballot(investment1.title)
+
+ expect(page).to have_content("Remove vote")
+ expect(page).to have_content("You have selected 1 project out of 2")
+
+ within(".budget-investment", text: investment2.title) do
+ find("div.ballot").hover
+
+ expect(page).not_to have_content("You have already assigned the available budget")
+ end
+
+ visit budget_ballot_path(budget)
+
+ expect(page).to have_content("you can change your vote at any time until this phase is closed")
+ end
+ end
end
|