From 9af4f201ed7899de5b1d1993f6e22e06c5cbc310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Verg=C3=A9s?= Date: Thu, 26 Oct 2023 18:42:21 +0200 Subject: [PATCH] Export weights (#17) * add label generation for the manifest * tweak exported * add weights to the exporter * fix tests * add graphql entry --- README.md | 9 ++ .../proposal_votes_controller_override.rb | 2 +- .../decidim_awesome/has_vote_weight.rb | 1 + .../decidim_awesome/has_weight_cache.rb | 34 ++++++ .../decidim/decidim_awesome/vote_weight.rb | 6 +- .../proposal_serializer_override.rb | 64 +++++++++++ .../decidim_awesome/proposal_type_override.rb | 18 ++++ config/locales/en.yml | 7 ++ lib/decidim/decidim_awesome/checksums.yml | 4 + lib/decidim/decidim_awesome/engine.rb | 4 +- .../decidim_awesome/voting_manifest.rb | 15 ++- spec/lib/system_checker_spec.rb | 4 +- spec/lib/voting_manifest_spec.rb | 101 +++++++++++++----- spec/models/weight_cache_spec.rb | 43 +++++++- spec/serializers/proposal_serializer_spec.rb | 97 +++++++++++++++++ spec/spec_helper.rb | 2 +- spec/types/proposal_type_spec.rb | 44 ++++++++ 17 files changed, 416 insertions(+), 39 deletions(-) create mode 100644 app/serializers/concerns/decidim/decidim_awesome/proposal_serializer_override.rb create mode 100644 app/types/concerns/decidim/decidim_awesome/proposal_type_override.rb create mode 100644 spec/serializers/proposal_serializer_spec.rb create mode 100644 spec/types/proposal_type_spec.rb diff --git a/README.md b/README.md index a67de76da..f27f0d254 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ if Decidim::DecidimAwesome.enabled?(:weighted_proposal_voting) voting.show_votes_count_view = "decidim/decidim_awesome/voting/no_admins_vote/show_votes_count" # voting.show_votes_count_view = "" # hide votes count if needed voting.proposal_m_cell_footer = "decidim/decidim_awesome/voting/no_admins_vote/proposal_m_cell_footer" + # define a weight validator (optional, by default all weights are valid) voting.weight_validator do |weight, context| # don't allow admins to vote next if context[:user].admin? @@ -194,6 +195,14 @@ if Decidim::DecidimAwesome.enabled?(:weighted_proposal_voting) weight.in? [1, 2, 3, 5] end + + # optionally, define a label generator block + # by default labels are extracted from a I18n key following this rule + # "decidim.decidim_awesome.votings.manifests.{MANIFEST_NAME}.weight_{WEIGHT}" + # + # voting.label_generator do |weight, context| + # "Weight #{weight.round}" + # end end end ``` diff --git a/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb b/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb index 6f9b81c00..42802e560 100644 --- a/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb +++ b/app/controllers/concerns/decidim/decidim_awesome/proposals/proposal_votes_controller_override.rb @@ -55,7 +55,7 @@ def vote_manifest end def weight - params[:weight].to_i + params[:weight].to_i if params.has_key?(:weight) end end end diff --git a/app/models/concerns/decidim/decidim_awesome/has_vote_weight.rb b/app/models/concerns/decidim/decidim_awesome/has_vote_weight.rb index 8128b5d17..c6dcbf771 100644 --- a/app/models/concerns/decidim/decidim_awesome/has_vote_weight.rb +++ b/app/models/concerns/decidim/decidim_awesome/has_vote_weight.rb @@ -9,6 +9,7 @@ module HasVoteWeight has_one :vote_weight, foreign_key: "proposal_vote_id", class_name: "Decidim::DecidimAwesome::VoteWeight", dependent: :destroy delegate :weight, to: :vote_weight, allow_nil: true + delegate :update_vote_weight_totals!, to: :vote_weight, allow_nil: true def weight=(new_weight) vote_weight = VoteWeight.find_or_initialize_by(vote: self) diff --git a/app/models/concerns/decidim/decidim_awesome/has_weight_cache.rb b/app/models/concerns/decidim/decidim_awesome/has_weight_cache.rb index 308de9598..09928a2bf 100644 --- a/app/models/concerns/decidim/decidim_awesome/has_weight_cache.rb +++ b/app/models/concerns/decidim/decidim_awesome/has_weight_cache.rb @@ -11,6 +11,40 @@ module HasWeightCache def weight_count(weight) (weight_cache && weight_cache.totals[weight.to_s]) || 0 end + + def vote_weights + @vote_weights ||= all_vote_weights.to_h { |weight| [manifest&.label_for(weight) || weight.to_s, weight_count(weight)] } + end + + def manifest + @manifest ||= DecidimAwesome.voting_registry.find(component.settings.awesome_voting_manifest) + end + + def all_vote_weights + @all_vote_weights ||= self.class.all_vote_weights_for(component) + end + + def update_vote_weights! + weight_cache ||= Decidim::DecidimAwesome::WeightCache.find_or_initialize_by(proposal: self) + weight_cache.totals = {} + votes.each do |vote| + weight_cache.totals[vote.weight] ||= 0 + weight_cache.totals[vote.weight] += 1 + end + weight_cache.save! + self.weight_cache = weight_cache + @vote_weights = nil + @all_vote_weights = nil + end + + # collects all different weights stored along the different proposals in a different component + def self.all_vote_weights_for(component) + Decidim::DecidimAwesome::VoteWeight.where( + proposal_vote_id: Decidim::Proposals::ProposalVote.where( + proposal: Decidim::Proposals::Proposal.where(component: component) + ) + ).pluck(:weight) + end end end end diff --git a/app/models/decidim/decidim_awesome/vote_weight.rb b/app/models/decidim/decidim_awesome/vote_weight.rb index ae5e19250..69d5bc032 100644 --- a/app/models/decidim/decidim_awesome/vote_weight.rb +++ b/app/models/decidim/decidim_awesome/vote_weight.rb @@ -8,10 +8,10 @@ class VoteWeight < ApplicationRecord delegate :proposal, to: :vote - after_destroy :update_vote_weight_totals - after_save :update_vote_weight_totals + after_destroy :update_vote_weight_totals! + after_save :update_vote_weight_totals! - def update_vote_weight_totals + def update_vote_weight_totals! cache = Decidim::DecidimAwesome::WeightCache.find_or_initialize_by(proposal: proposal) cache.totals = cache.totals || {} diff --git a/app/serializers/concerns/decidim/decidim_awesome/proposal_serializer_override.rb b/app/serializers/concerns/decidim/decidim_awesome/proposal_serializer_override.rb new file mode 100644 index 000000000..00f7d6a23 --- /dev/null +++ b/app/serializers/concerns/decidim/decidim_awesome/proposal_serializer_override.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module ProposalSerializerOverride + extend ActiveSupport::Concern + + included do + # Public: Exports a hash with the serialized data for this proposal. + def serialize + { + id: proposal.id, + category: { + id: proposal.category.try(:id), + name: proposal.category.try(:name) || empty_translatable + }, + scope: { + id: proposal.scope.try(:id), + name: proposal.scope.try(:name) || empty_translatable + }, + participatory_space: { + id: proposal.participatory_space.id, + url: Decidim::ResourceLocatorPresenter.new(proposal.participatory_space).url + }, + component: { id: component.id }, + title: proposal.title, + body: convert_to_plain_text(proposal.body), + address: proposal.address, + latitude: proposal.latitude, + longitude: proposal.longitude, + state: proposal.state.to_s, + reference: proposal.reference, + answer: ensure_translatable(proposal.answer), + supports: proposal.proposal_votes_count, + weights: proposal_vote_weights, + endorsements: { + total_count: proposal.endorsements.size, + user_endorsements: user_endorsements + }, + comments: proposal.comments_count, + attachments: proposal.attachments.size, + followers: proposal.follows.size, + published_at: proposal.published_at, + url: url, + meeting_urls: meetings, + related_proposals: related_proposals, + is_amend: proposal.emendation?, + original_proposal: { + title: proposal&.amendable&.title, + url: original_proposal_url + } + } + end + + private + + def proposal_vote_weights + proposal.update_vote_weights! + proposal.vote_weights + end + end + end + end +end diff --git a/app/types/concerns/decidim/decidim_awesome/proposal_type_override.rb b/app/types/concerns/decidim/decidim_awesome/proposal_type_override.rb new file mode 100644 index 000000000..7da9c3010 --- /dev/null +++ b/app/types/concerns/decidim/decidim_awesome/proposal_type_override.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module ProposalTypeOverride + extend ActiveSupport::Concern + + included do + field :vote_weights, GraphQL::Types::JSON, description: "The corresponding weights count to the proposal votes", null: true + + def vote_weights + current_component = object.component + object.vote_weights unless current_component.current_settings.votes_hidden? + end + end + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index a29148bab..fe7328113 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -491,6 +491,13 @@ en: validators: too_much_caps: Is using too many capital letters (over %{percent}% of the text) + votings: + manifests: + three_flags: + weight_0: Abstain + weight_1: Red + weight_2: Yellow + weight_3: Green layouts: decidim: admin: diff --git a/lib/decidim/decidim_awesome/checksums.yml b/lib/decidim/decidim_awesome/checksums.yml index 49878f320..402187c87 100644 --- a/lib/decidim/decidim_awesome/checksums.yml +++ b/lib/decidim/decidim_awesome/checksums.yml @@ -23,6 +23,10 @@ decidim-core: decidim-0.25: 63532a04fcf07cf92b789833a9421416 decidim-0.26: 636943ccb2e994fe0124dc87e72e49e9 decidim-proposals: + /lib/decidim/proposals/proposal_serializer.rb: + decidim-0.26: 2d817775f79a19cfcf9cfae8e5b68c5a + /lib/decidim/api/proposal_type.rb: + decidim-0.26: e38e4875e408ec4f658e28725c5a94d4 /app/forms/decidim/proposals/proposal_wizard_create_step_form.rb: decidim-0.24: db69edd0ba8ffa3965a5c44a6bfaba8d decidim-0.27: b495d37088ecebcbe4ac9563bb3498d5 diff --git a/lib/decidim/decidim_awesome/engine.rb b/lib/decidim/decidim_awesome/engine.rb index 496e70cf5..397eaf92f 100644 --- a/lib/decidim/decidim_awesome/engine.rb +++ b/lib/decidim/decidim_awesome/engine.rb @@ -59,6 +59,8 @@ class Engine < ::Rails::Engine Decidim::Proposals::ProposalVote.include(Decidim::DecidimAwesome::HasVoteWeight) # add vote weight cache to proposal Decidim::Proposals::Proposal.include(Decidim::DecidimAwesome::HasWeightCache) + Decidim::Proposals::ProposalSerializer.include(Decidim::DecidimAwesome::ProposalSerializerOverride) + Decidim::Proposals::ProposalType.include(Decidim::DecidimAwesome::ProposalTypeOverride) end Decidim::MenuPresenter.include(Decidim::DecidimAwesome::MenuPresenterOverride) @@ -103,7 +105,7 @@ class Engine < ::Rails::Engine # voting.show_votes_count_view = "" # hide votes count if needed voting.proposal_m_cell_footer = "decidim/decidim_awesome/voting/three_flags/proposal_m_cell_footer" voting.weight_validator do |weight, _context| - weight.in? [1, 2, 3] + weight.in? [0, 1, 2, 3] end end diff --git a/lib/decidim/decidim_awesome/voting_manifest.rb b/lib/decidim/decidim_awesome/voting_manifest.rb index 1b602980a..696aebb68 100644 --- a/lib/decidim/decidim_awesome/voting_manifest.rb +++ b/lib/decidim/decidim_awesome/voting_manifest.rb @@ -38,10 +38,23 @@ def weight_validator(&block) # validates the weight using the Proc defined by weight_validator # Receives the weight and a context with the user and the proposal to be voted def valid_weight?(weight, context = {}) - return unless @on_weight_validation + return true unless @on_weight_validation @on_weight_validation.call(weight, **context) end + + # registers an optional label generator block + def label_generator(&block) + @on_label_generation = block + end + + # returns the label used in export or other places for a given weight + # defaults to a I18n key scoped under decidim_awesome + def label_for(weight, context = {}) + return I18n.t("decidim.decidim_awesome.votings.manifests.#{name}.weight_#{weight}", default: "weight_#{weight}") unless @on_label_generation + + @on_label_generation.call(weight, **context) + end end end end diff --git a/spec/lib/system_checker_spec.rb b/spec/lib/system_checker_spec.rb index e771fb315..d51b0d599 100644 --- a/spec/lib/system_checker_spec.rb +++ b/spec/lib/system_checker_spec.rb @@ -20,8 +20,8 @@ module Decidim::DecidimAwesome expect(subject.overrides["decidim-core"].files.length).to eq(6) end - it "has 9 modified files in proposals" do - expect(subject.overrides["decidim-proposals"].files.length).to eq(9) + it "has 11 modified files in proposals" do + expect(subject.overrides["decidim-proposals"].files.length).to eq(11) end context "when file" do diff --git a/spec/lib/voting_manifest_spec.rb b/spec/lib/voting_manifest_spec.rb index 00ed9460c..fe5356a03 100644 --- a/spec/lib/voting_manifest_spec.rb +++ b/spec/lib/voting_manifest_spec.rb @@ -7,14 +7,6 @@ module Decidim::DecidimAwesome subject { described_class.new(name: name) } let(:name) { :test } - before do - subject.weight_validator do |weight, context| - next if context && context[:already_voted_this_weight] - - weight.in? [1, 2, 3, 4, 5] - end - end - it { is_expected.to be_valid } context "when no name" do @@ -23,40 +15,91 @@ module Decidim::DecidimAwesome it { is_expected.to be_invalid } end - it "validates weights" do + it "any weight is valid" do + expect(subject).to be_valid_weight(0) expect(subject).to be_valid_weight(1) - expect(subject).to be_valid_weight(3) - expect(subject).to be_valid_weight(5) - expect(subject).not_to be_valid_weight(0) - expect(subject).not_to be_valid_weight(1.5) - expect(subject).not_to be_valid_weight(6) + expect(subject).to be_valid_weight(1.5) + expect(subject).to be_valid_weight(-1.5) + end + + it "returns automatic labels" do + expect(subject.label_for(0)).to eq("weight_0") + expect(subject.label_for(1)).to eq("weight_1") + expect(subject.label_for(1.5)).to eq("weight_1.5") + expect(subject.label_for(-1.5)).to eq("weight_-1.5") + end + + context "when I18n labels exist" do + let(:name) { :three_flags } + + it "returns I18n labels" do + expect(subject.label_for(0)).to eq("Abstain") + expect(subject.label_for(1)).to eq("Red") + expect(subject.label_for(2)).to eq("Yellow") + expect(subject.label_for(3)).to eq("Green") + expect(subject.label_for(3.5)).to eq("weight_3.5") + end + end + + context "when label_generator block" do + before do + subject.label_generator do |weight, _context| + "custom weight: #{weight.round}" + end + end + + it "returns custom labels" do + expect(subject.label_for(0)).to eq("custom weight: 0") + expect(subject.label_for(1)).to eq("custom weight: 1") + expect(subject.label_for(1.5)).to eq("custom weight: 2") + expect(subject.label_for(-1.5)).to eq("custom weight: -2") + end end - context "when context is set" do - let(:voted) { false } - let(:context) do - { - foo: "bar", - already_voted_this_weight: voted - } + context "when weight_validator block" do + before do + subject.weight_validator do |weight, context| + next if context && context[:already_voted_this_weight] + + weight.in? [1, 2, 3, 4, 5] + end end it "validates weights" do - expect(subject).to be_valid_weight(1, context) - expect(subject).not_to be_valid_weight(0, context) - expect(subject).to be_valid_weight(5, context) - expect(subject).not_to be_valid_weight(6, context) + expect(subject).to be_valid_weight(1) + expect(subject).to be_valid_weight(3) + expect(subject).to be_valid_weight(5) + expect(subject).not_to be_valid_weight(0) + expect(subject).not_to be_valid_weight(1.5) + expect(subject).not_to be_valid_weight(6) end - context "and voted" do - let(:voted) { true } + context "when context is set" do + let(:voted) { false } + let(:context) do + { + foo: "bar", + already_voted_this_weight: voted + } + end it "validates weights" do - expect(subject).not_to be_valid_weight(1, context) + expect(subject).to be_valid_weight(1, context) expect(subject).not_to be_valid_weight(0, context) - expect(subject).not_to be_valid_weight(5, context) + expect(subject).to be_valid_weight(5, context) expect(subject).not_to be_valid_weight(6, context) end + + context "and voted" do + let(:voted) { true } + + it "validates weights" do + expect(subject).not_to be_valid_weight(1, context) + expect(subject).not_to be_valid_weight(0, context) + expect(subject).not_to be_valid_weight(5, context) + expect(subject).not_to be_valid_weight(6, context) + end + end end end end diff --git a/spec/models/weight_cache_spec.rb b/spec/models/weight_cache_spec.rb index 26b44a54e..ef3b4c5b4 100644 --- a/spec/models/weight_cache_spec.rb +++ b/spec/models/weight_cache_spec.rb @@ -139,7 +139,7 @@ module Decidim::DecidimAwesome end end - # this is un unlikely scenario as voting removes and creates new vote weights, just in case... + # this is an unlikely scenario where voting removes and creates new vote weights, just in case... describe "updated" do let!(:vote_weight1) { create(:awesome_vote_weight, vote: create(:proposal_vote, proposal: proposal), weight: 1) } let!(:vote_weight2) { create(:awesome_vote_weight, vote: create(:proposal_vote, proposal: proposal), weight: 2) } @@ -172,5 +172,46 @@ module Decidim::DecidimAwesome end end end + + describe "all_vote_weights" do + let!(:weight_cache) { create(:awesome_weight_cache, proposal: proposal) } + let!(:another_weight_cache) { create(:awesome_weight_cache, proposal: another_proposal) } + let!(:unrelated_another_weight_cache) { create(:awesome_weight_cache, :with_votes) } + let(:another_proposal) { create(:proposal, component: proposal.component) } + let!(:votes) do + vote = create(:proposal_vote, proposal: proposal, author: create(:user, organization: proposal.organization)) + create(:awesome_vote_weight, vote: vote, weight: 1) + end + let!(:other_votes) do + vote = create(:proposal_vote, proposal: another_proposal, author: create(:user, organization: proposal.organization)) + create(:awesome_vote_weight, vote: vote, weight: 2) + end + + it "returns all vote weights for a component" do + expect(proposal.all_vote_weights).to match_array([1, 2]) + expect(another_proposal.all_vote_weights).to match_array([1, 2]) + expect(proposal.vote_weights).to eq({ "1" => 1, "2" => 0 }) + expect(another_proposal.vote_weights).to eq({ "1" => 0, "2" => 1 }) + end + + context "when wrong cache exists" do + before do + # rubocop:disable Rails/SkipsModelValidations: + # we don't want to trigger the active record hooks + weight_cache.update_columns(totals: { "3" => 1, "4" => 1 }) + # rubocop:enable Rails/SkipsModelValidations: + end + + it "returns all vote weights for a component" do + expect(proposal.weight_cache.totals).to eq({ "3" => 1, "4" => 1 }) + expect(proposal.vote_weights).to eq({ "1" => 0, "2" => 0 }) + proposal.update_vote_weights! + expect(proposal.vote_weights).to eq({ "1" => 1, "2" => 0 }) + expect(another_proposal.vote_weights).to eq({ "1" => 0, "2" => 1 }) + expect(proposal.weight_cache.totals).to eq({ "1" => 1 }) + expect(another_proposal.weight_cache.totals).to eq({ "2" => 1 }) + end + end + end end end diff --git a/spec/serializers/proposal_serializer_spec.rb b/spec/serializers/proposal_serializer_spec.rb new file mode 100644 index 000000000..8d9371386 --- /dev/null +++ b/spec/serializers/proposal_serializer_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim::Proposals + describe ProposalSerializer do + subject do + described_class.new(proposal) + end + + let!(:proposal) { create(:proposal, :accepted, component: component) } + let!(:another_proposal) { create(:proposal, :accepted, component: component) } + let!(:weight_cache) { create(:awesome_weight_cache, proposal: proposal) } + let(:weights) do + { + "0" => 1, + "3" => 2 + } + end + let!(:votes) do + weights.each do |weight, count| + count.times do + vote = create(:proposal_vote, proposal: proposal, author: create(:user, organization: proposal.organization)) + create(:awesome_vote_weight, vote: vote, weight: weight) + end + end + end + let!(:another_weight_cache) { create(:awesome_weight_cache, :with_votes, proposal: another_proposal) } + let(:participatory_process) { component.participatory_space } + let(:component) { create :proposal_component, settings: settings } + let(:settings) do + { + awesome_voting_manifest: manifest + } + end + let(:labeled_weights) do + { + "Abstain" => 1, + "Red" => 0, + "Yellow" => 0, + "Green" => 2, + "weight_4" => 0, + "weight_5" => 0 + } + end + let(:manifest) { :three_flags } + + let!(:proposals_component) { create(:component, manifest_name: "proposals", participatory_space: participatory_process) } + + describe "#serialize" do + let(:serialized) { subject.serialize } + + it "serializes the id" do + expect(serialized).to include(id: proposal.id) + end + + it "serializes the amount of supports" do + expect(serialized).to include(supports: proposal.proposal_votes_count) + end + + it "serializes the weights" do + expect(serialized).to include(weights: labeled_weights) + end + + context "when no manifest" do + let(:manifest) { nil } + + it "serializes the weights" do + expect(serialized).to include(weights: { "0" => 1, "1" => 0, "2" => 0, "3" => 2, "4" => 0, "5" => 0 }) + end + end + + context "when vote_cache is outdated" do + let(:wrong_weights) do + { "1" => 101, "2" => 102, "3" => 103, "4" => 104, "5" => 105 } + end + let(:labeled_wrong_weights) do + { "Abstain" => 0, "Red" => 101, "Yellow" => 102, "Green" => 103, "weight_4" => 104, "weight_5" => 105 } + end + + before do + # rubocop:disable Rails/SkipsModelValidations: + # we don't want to trigger the active record hooks + weight_cache.update_columns(totals: wrong_weights) + # rubocop:enable Rails/SkipsModelValidations: + end + + it "serializes the weights" do + expect(proposal.vote_weights).to eq(labeled_wrong_weights) + expect(serialized).to include(weights: labeled_weights) + weight_cache.reload + expect(proposal.reload.vote_weights).to eq(labeled_weights) + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 922236f73..37e83c393 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,4 +10,4 @@ def legacy_version? Decidim::DecidimAwesome.legacy_version? -end \ No newline at end of file +end diff --git a/spec/types/proposal_type_spec.rb b/spec/types/proposal_type_spec.rb new file mode 100644 index 000000000..818129562 --- /dev/null +++ b/spec/types/proposal_type_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require "spec_helper" +require "decidim/api/test/type_context" + +module Decidim::Proposals + describe ProposalType, type: :graphql do + include_context "with a graphql class type" + let(:component) { create(:proposal_component) } + let!(:weight_cache) { create(:awesome_weight_cache, :with_votes, proposal: model) } + let(:model) { create :proposal, component: component } + + describe "id" do + let(:query) { "{ id }" } + + it "returns the proposal's id" do + expect(response["id"]).to eq(model.id.to_s) + end + end + + describe "voteCount/voteWeights" do + let(:query) { "{ voteCount voteWeights }" } + + context "when votes are not hidden" do + it "returns the amount of votes for this proposal" do + expect(response["voteCount"]).to eq(5) + end + + it "returns the weights of votes for this proposal" do + expect(response["voteWeights"]).to eq({ "1" => 1, "2" => 1, "3" => 1, "4" => 1, "5" => 1 }) + end + end + + context "when votes are hidden" do + let(:component) { create(:proposal_component, :with_votes_hidden) } + + it "returns nil" do + expect(response["voteCount"]).to be_nil + expect(response["voteWeights"]).to be_nil + end + end + end + end +end