Merge pull request #6072 from consuldemocracy/add_option_id_to_partial_results

Avoid duplicate records in partial results
This commit is contained in:
Sebastia
2025-09-26 15:18:45 +02:00
committed by GitHub
28 changed files with 705 additions and 274 deletions

View File

@@ -1,5 +1,4 @@
<% by_question = @partial_results.group_by(&:question_id) %> <div class="admin-poll-results-question">
<% @poll.questions.each do |question| %>
<h3><%= question.title %></h3> <h3><%= question.title %></h3>
<table class="margin"> <table class="margin">
<thead> <thead>
@@ -10,12 +9,11 @@
</thead> </thead>
<tbody> <tbody>
<% question.question_options.each_with_index do |option, i| %> <% question.question_options.each_with_index do |option, i| %>
<% by_answer = by_question[question.id].present? ? by_question[question.id].group_by(&:answer) : {} %>
<tr id="question_<%= question.id %>_<%= i %>_result"> <tr id="question_<%= question.id %>_<%= i %>_result">
<td><%= option.title %></td> <td><%= option.title %></td>
<td class="text-center"><%= by_answer[option.title].present? ? by_answer[option.title].sum(&:amount) : 0 %></td> <td class="text-center"><%= votes_for(option) %></td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<% end %> </div>

View File

@@ -0,0 +1,12 @@
class Admin::Poll::Results::QuestionComponent < ApplicationComponent
attr_reader :question, :partial_results
def initialize(question, partial_results)
@question = question
@partial_results = partial_results
end
def votes_for(option)
partial_results.where(option: option).sum(:amount)
end
end

View File

@@ -0,0 +1,41 @@
<% provide :main_class, "officing-results-index" %>
<%= back_link_to new_officing_poll_result_path(poll) %>
<h2><%= poll.name %> - <%= t("officing.results.index.results") %></h2>
<% if partial_results.present? %>
<div class="callout primary">
<h3>
<%= booth_assignment.booth.name %> - <%= l partial_results.first.date, format: :long %>
</h3>
</div>
<div class="row">
<div class="small-12 medium-9 column">
<table>
<thead>
<tr>
<th><%= t("officing.results.index.table_whites") %></th>
<th><%= t("officing.results.index.table_nulls") %></th>
<th><%= t("officing.results.index.table_total") %></th>
</tr>
</thead>
<tbody>
<tr>
<td id="white_results"><%= recounts.sum(:white_amount) %></td>
<td id="null_results"><%= recounts.sum(:null_amount) %></td>
<td id="total_results"><%= recounts.sum(:total_amount) %></td>
</tr>
</tbody>
</table>
<% @poll.questions.each do |question| %>
<%= render Admin::Poll::Results::QuestionComponent.new(question, @partial_results) %>
<% end %>
</div>
</div>
<% else %>
<div class="callout primary">
<%= t("officing.results.index.no_results") %>
</div>
<% end %>

View File

@@ -0,0 +1,10 @@
class Officing::Results::IndexComponent < ApplicationComponent
attr_reader :poll, :partial_results, :booth_assignment, :recounts
def initialize(poll, partial_results, booth_assignment, recounts)
@poll = poll
@partial_results = partial_results
@booth_assignment = booth_assignment
@recounts = recounts
end
end

View File

@@ -13,12 +13,6 @@ class Officing::BaseController < ApplicationController
raise CanCan::AccessDenied unless current_user&.poll_officer? raise CanCan::AccessDenied unless current_user&.poll_officer?
end end
def check_officer_assignment
if @officer_assignment.blank?
go_back_to_new(t("officing.results.flash.error_wrong_booth"))
end
end
def load_officer_assignment def load_officer_assignment
@officer_assignments ||= current_user.poll_officer.officer_assignments.where(date: Time.current.to_date) @officer_assignments ||= current_user.poll_officer.officer_assignments.where(date: Time.current.to_date)
end end

View File

@@ -38,21 +38,20 @@ class Officing::ResultsController < Officing::BaseController
params[:questions].each_pair do |question_id, results| params[:questions].each_pair do |question_id, results|
question = @poll.questions.find(question_id) question = @poll.questions.find(question_id)
go_back_to_new if question.blank?
results.each_pair do |answer_index, count| results.each_pair do |option_index, count|
next if count.blank? next if count.blank?
answer = question.question_options.find_by(given_order: answer_index.to_i + 1).title option = question.question_options.find_by(given_order: option_index.to_i + 1)
go_back_to_new if question.blank?
partial_result = ::Poll::PartialResult.find_or_initialize_by( partial_result = ::Poll::PartialResult.find_or_initialize_by(
booth_assignment_id: @officer_assignment.booth_assignment_id, booth_assignment_id: @officer_assignment.booth_assignment_id,
date: Date.current, date: Date.current,
question_id: question_id, question_id: question_id,
answer: answer option_id: option.id
) )
partial_result.officer_assignment_id = @officer_assignment.id partial_result.officer_assignment_id = @officer_assignment.id
partial_result.answer = option.title
partial_result.amount = count.to_i partial_result.amount = count.to_i
partial_result.author = current_user partial_result.author = current_user
partial_result.origin = "booth" partial_result.origin = "booth"
@@ -79,10 +78,10 @@ class Officing::ResultsController < Officing::BaseController
@results << recount @results << recount
end end
def go_back_to_new(alert = nil) def go_back_to_new(alert)
params[:d] = Date.current params[:d] = Date.current
params[:oa] = results_params[:officer_assignment_id] params[:oa] = results_params[:officer_assignment_id]
flash.now[:alert] = (alert || t("officing.results.flash.error_create")) flash.now[:alert] = alert
load_officer_assignments load_officer_assignments
load_partial_results load_partial_results
render :new render :new
@@ -123,4 +122,10 @@ class Officing::ResultsController < Officing::BaseController
def index_params def index_params
params.permit(:booth_assignment_id, :date) params.permit(:booth_assignment_id, :date)
end end
def check_officer_assignment
if @officer_assignment.blank?
go_back_to_new(t("officing.results.flash.error_wrong_booth"))
end
end
end end

View File

@@ -0,0 +1,7 @@
class PollPartialResultOptionFinder < PollOptionFinder
private
def existing_choices
question.partial_results.where(option_id: nil).distinct.pluck(:answer)
end
end

View File

@@ -11,8 +11,8 @@ class Poll::Answer < ApplicationRecord
validates :option, uniqueness: { scope: :author_id }, allow_nil: true validates :option, uniqueness: { scope: :author_id }, allow_nil: true
validate :max_votes validate :max_votes
validates :answer, inclusion: { in: ->(a) { a.question.possible_answers }}, validates :answer, inclusion: { in: ->(poll_answer) { poll_answer.option.possible_answers }},
unless: ->(a) { a.question.blank? } if: ->(poll_answer) { poll_answer.option.present? }
scope :by_author, ->(author_id) { where(author_id: author_id) } scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :by_question, ->(question_id) { where(question_id: question_id) } scope :by_question, ->(question_id) { where(question_id: question_id) }

View File

@@ -5,13 +5,15 @@ class Poll::PartialResult < ApplicationRecord
belongs_to :author, -> { with_hidden }, class_name: "User", inverse_of: :poll_partial_results belongs_to :author, -> { with_hidden }, class_name: "User", inverse_of: :poll_partial_results
belongs_to :booth_assignment belongs_to :booth_assignment
belongs_to :officer_assignment belongs_to :officer_assignment
belongs_to :option, class_name: "Poll::Question::Option"
validates :question, presence: true validates :question, presence: true
validates :author, presence: true validates :author, presence: true
validates :answer, presence: true validates :answer, presence: true
validates :answer, inclusion: { in: ->(a) { a.question.possible_answers }}, validates :answer, inclusion: { in: ->(partial_result) { partial_result.option.possible_answers }},
unless: ->(a) { a.question.blank? } if: ->(partial_result) { partial_result.option.present? }
validates :origin, inclusion: { in: ->(*) { VALID_ORIGINS }} validates :origin, inclusion: { in: ->(*) { VALID_ORIGINS }}
validates :option, uniqueness: { scope: [:booth_assignment_id, :date] }, allow_nil: true
scope :by_author, ->(author_id) { where(author_id: author_id) } scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :by_question, ->(question_id) { where(question_id: question_id) } scope :by_question, ->(question_id) { where(question_id: question_id) }

View File

@@ -49,10 +49,6 @@ class Poll::Question < ApplicationRecord
question_options.max_by(&:total_votes)&.id question_options.max_by(&:total_votes)&.id
end end
def possible_answers
question_options.joins(:translations).pluck(:title)
end
def options_with_read_more? def options_with_read_more?
options_with_read_more.any? options_with_read_more.any?
end end

View File

@@ -12,6 +12,7 @@ class Poll::Question::Option < ApplicationRecord
belongs_to :question, class_name: "Poll::Question" belongs_to :question, class_name: "Poll::Question"
has_many :answers, class_name: "Poll::Answer", dependent: :nullify has_many :answers, class_name: "Poll::Answer", dependent: :nullify
has_many :partial_results, class_name: "Poll::PartialResult", dependent: :nullify
has_many :videos, class_name: "Poll::Question::Option::Video", has_many :videos, class_name: "Poll::Question::Option::Video",
dependent: :destroy, dependent: :destroy,
foreign_key: "answer_id", foreign_key: "answer_id",
@@ -50,4 +51,8 @@ class Poll::Question::Option < ApplicationRecord
def with_read_more? def with_read_more?
description.present? || images.any? || documents.any? || videos.any? description.present? || images.any? || documents.any? || videos.any?
end end
def possible_answers
translations.pluck(:title)
end
end end

View File

@@ -7,6 +7,9 @@
</div> </div>
<% else %> <% else %>
<%= render "admin/poll/results/recount", resource: @booth_assignment %> <%= render "admin/poll/results/recount", resource: @booth_assignment %>
<%= render "admin/poll/results/result" %>
<% @poll.questions.each do |question| %>
<%= render Admin::Poll::Results::QuestionComponent.new(question, @partial_results) %>
<% end %>
<% end %> <% end %>
</div> </div>

View File

@@ -13,7 +13,11 @@
<% if @partial_results.present? %> <% if @partial_results.present? %>
<%= render "recount", resource: @poll %> <%= render "recount", resource: @poll %>
<%= render "result" %>
<% @poll.questions.each do |question| %>
<%= render Admin::Poll::Results::QuestionComponent.new(question, @partial_results) %>
<% end %>
<%= render "results_by_booth" %> <%= render "results_by_booth" %>
<% end %> <% end %>

View File

@@ -1,58 +1 @@
<%= back_link_to new_officing_poll_result_path(@poll) %> <%= render Officing::Results::IndexComponent.new(@poll, @partial_results, @booth_assignment, @recounts) %>
<h2><%= @poll.name %> - <%= t("officing.results.index.results") %></h2>
<% if @partial_results.present? %>
<div class="callout primary">
<h3>
<%= @booth_assignment.booth.name %> - <%= l @partial_results.first.date, format: :long %>
</h3>
</div>
<div class="row">
<div class="small-12 medium-9 column">
<table>
<thead>
<tr>
<th><%= t("officing.results.index.table_whites") %></th>
<th><%= t("officing.results.index.table_nulls") %></th>
<th><%= t("officing.results.index.table_total") %></th>
</tr>
</thead>
<tbody>
<tr>
<td id="white_results"><%= @recounts.sum(:white_amount) %></td>
<td id="null_results"><%= @recounts.sum(:null_amount) %></td>
<td id="total_results"><%= @recounts.sum(:total_amount) %></td>
</tr>
</tbody>
</table>
<% by_question = @partial_results.group_by(&:question_id) %>
<% @poll.questions.each do |question| %>
<h3><%= question.title %></h3>
<table>
<thead>
<tr>
<th><%= t("officing.results.index.table_answer") %></th>
<th><%= t("officing.results.index.table_votes") %></th>
</tr>
</thead>
<tbody>
<% question.question_options.each_with_index do |option, i| %>
<% by_answer = by_question[question.id].present? ? by_question[question.id].group_by(&:answer) : {} %>
<tr id="question_<%= question.id %>_<%= i %>_result">
<td><%= option.title %></td>
<td><%= by_answer[option.title].present? ? by_answer[option.title].first.amount : 0 %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
</div>
</div>
<% else %>
<div class="callout primary">
<%= t("officing.results.index.no_results") %>
</div>
<% end %>

View File

@@ -41,7 +41,6 @@ en:
results: results:
flash: flash:
create: "Results saved" create: "Results saved"
error_create: "Results NOT saved. Error in data."
error_wrong_booth: "Wrong booth. Results NOT saved." error_wrong_booth: "Wrong booth. Results NOT saved."
new: new:
title: "%{poll} - Add results" title: "%{poll} - Add results"
@@ -58,8 +57,6 @@ en:
index: index:
no_results: "No results" no_results: "No results"
results: Results results: Results
table_answer: Answer
table_votes: Votes
table_whites: "Totally blank ballots" table_whites: "Totally blank ballots"
table_nulls: "Invalid ballots" table_nulls: "Invalid ballots"
table_total: "Valid ballots" table_total: "Valid ballots"

View File

@@ -41,7 +41,6 @@ es:
results: results:
flash: flash:
create: "Datos guardados" create: "Datos guardados"
error_create: "Resultados NO añadidos. Error en los datos"
error_wrong_booth: "Urna incorrecta. Resultados NO guardados." error_wrong_booth: "Urna incorrecta. Resultados NO guardados."
new: new:
title: "%{poll} - Añadir resultados" title: "%{poll} - Añadir resultados"
@@ -58,8 +57,6 @@ es:
index: index:
no_results: "No hay resultados" no_results: "No hay resultados"
results: Resultados results: Resultados
table_answer: Respuesta
table_votes: Votos
table_whites: "Papeletas totalmente en blanco" table_whites: "Papeletas totalmente en blanco"
table_nulls: "Papeletas nulas" table_nulls: "Papeletas nulas"
table_total: "Papeletas válidas" table_total: "Papeletas válidas"

View File

@@ -0,0 +1,9 @@
class AddOptionIdToPollPartialResults < ActiveRecord::Migration[7.1]
def change
change_table :poll_partial_results do |t|
t.references :option, index: true, foreign_key: { to_table: :poll_question_answers }
t.index [:booth_assignment_id, :date, :option_id], unique: true
end
end
end

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2025_08_14_122241) do ActiveRecord::Schema[7.1].define(version: 2025_09_09_145207) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -1084,9 +1084,12 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_14_122241) do
t.text "amount_log", default: "" t.text "amount_log", default: ""
t.text "officer_assignment_id_log", default: "" t.text "officer_assignment_id_log", default: ""
t.text "author_id_log", default: "" t.text "author_id_log", default: ""
t.bigint "option_id"
t.index ["answer"], name: "index_poll_partial_results_on_answer" t.index ["answer"], name: "index_poll_partial_results_on_answer"
t.index ["author_id"], name: "index_poll_partial_results_on_author_id" t.index ["author_id"], name: "index_poll_partial_results_on_author_id"
t.index ["booth_assignment_id", "date", "option_id"], name: "idx_on_booth_assignment_id_date_option_id_2ffcf6ea3b", unique: true
t.index ["booth_assignment_id", "date"], name: "index_poll_partial_results_on_booth_assignment_id_and_date" t.index ["booth_assignment_id", "date"], name: "index_poll_partial_results_on_booth_assignment_id_and_date"
t.index ["option_id"], name: "index_poll_partial_results_on_option_id"
t.index ["origin"], name: "index_poll_partial_results_on_origin" t.index ["origin"], name: "index_poll_partial_results_on_origin"
t.index ["question_id"], name: "index_poll_partial_results_on_question_id" t.index ["question_id"], name: "index_poll_partial_results_on_question_id"
end end
@@ -1799,6 +1802,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_14_122241) do
add_foreign_key "poll_officer_assignments", "poll_booth_assignments", column: "booth_assignment_id" add_foreign_key "poll_officer_assignments", "poll_booth_assignments", column: "booth_assignment_id"
add_foreign_key "poll_partial_results", "poll_booth_assignments", column: "booth_assignment_id" add_foreign_key "poll_partial_results", "poll_booth_assignments", column: "booth_assignment_id"
add_foreign_key "poll_partial_results", "poll_officer_assignments", column: "officer_assignment_id" add_foreign_key "poll_partial_results", "poll_officer_assignments", column: "officer_assignment_id"
add_foreign_key "poll_partial_results", "poll_question_answers", column: "option_id"
add_foreign_key "poll_partial_results", "poll_questions", column: "question_id" add_foreign_key "poll_partial_results", "poll_questions", column: "question_id"
add_foreign_key "poll_partial_results", "users", column: "author_id" add_foreign_key "poll_partial_results", "users", column: "author_id"
add_foreign_key "poll_question_answer_videos", "poll_question_answers", column: "answer_id" add_foreign_key "poll_question_answer_videos", "poll_question_answers", column: "answer_id"

View File

@@ -3,8 +3,10 @@ namespace :consul do
task execute_release_tasks: ["settings:rename_setting_keys", task execute_release_tasks: ["settings:rename_setting_keys",
"settings:add_new_settings", "settings:add_new_settings",
"cache:clear", "cache:clear",
"execute_release_2.3.0_tasks"] "execute_release_2.4.0_tasks"]
desc "Runs tasks needed to upgrade from 2.2.2 to 2.3.0" desc "Runs tasks needed to upgrade from 2.3.1 to 2.4.0"
task "execute_release_2.3.0_tasks": [] task "execute_release_2.4.0_tasks": [
"polls:populate_partial_results_option_id"
]
end end

View File

@@ -107,4 +107,95 @@ namespace :polls do
end end
end end
end end
desc "Removes duplicate poll partial results"
task remove_duplicate_partial_results: :environment do
logger = ApplicationLogger.new
duplicate_records_logger = DuplicateRecordsLogger.new
logger.info "Removing duplicate partial results in polls"
Tenant.run_on_each do
Poll::Question.find_each do |question|
manageable_titles = PollPartialResultOptionFinder.new(question).manageable_choices.keys
question.question_options.each do |option|
titles = option.translations.where(title: manageable_titles).select(:title).distinct
groups = question.partial_results.where(option_id: nil, answer: titles)
.select(:booth_assignment_id, :date)
.group(:booth_assignment_id, :date)
.having("count(*) > 1")
.pluck(:booth_assignment_id, :date)
groups.each do |booth_assignment_id, date|
partial_results = question.partial_results.where(
option_id: nil,
booth_assignment_id: booth_assignment_id,
date: date,
answer: titles
)
tenant_info = " on tenant #{Tenant.current_schema}" unless Tenant.default?
amounts_by_id = partial_results.pluck(:id, :amount).to_h
if amounts_by_id.values.uniq.size > 1
log_message = "Found duplicate partial results with different amounts " \
"for question_id #{question.id}, " \
"booth_assignment_id #{booth_assignment_id} " \
"and date #{date}. " \
"Keeping ID #{partial_results.first.id} " \
"with amount #{partial_results.first.amount}. " \
"Deleting partial results with these IDs and amounts: " \
"#{amounts_by_id.except(partial_results.first.id)}" + tenant_info.to_s
logger.info(log_message)
duplicate_records_logger.info(log_message)
end
partial_results.excluding(partial_results.first).each do |partial_result|
partial_result.delete
log_message = "Deleted duplicate record with ID #{partial_result.id} " \
"from the #{Poll::PartialResult.table_name} table " \
"with question_id #{question.id}, " \
"booth_assignment_id #{booth_assignment_id} " \
"and date #{date}" + tenant_info.to_s
logger.info(log_message)
duplicate_records_logger.info(log_message)
end
end
end
end
end
end
desc "populates the poll_partial_results option_id column"
task populate_partial_results_option_id: :remove_duplicate_partial_results do
logger = ApplicationLogger.new
logger.info "Updating option_id column in poll_partial_results"
Tenant.run_on_each do
Poll::Question.find_each do |question|
option_finder = PollPartialResultOptionFinder.new(question)
option_finder.manageable_choices.each do |choice, ids|
question.partial_results.where(option_id: nil, answer: choice).update_all(option_id: ids.first)
end
option_finder.unmanageable_choices.each do |choice, ids|
tenant_info = " on tenant #{Tenant.current_schema}" unless Tenant.default?
if ids.count == 0
logger.warn "Skipping poll_partial_results with the text \"#{choice}\" for the poll_question " \
"with ID #{question.id}. This question has no poll_question_answers " \
"containing the text \"#{choice}\"" + tenant_info.to_s
else
logger.warn "Skipping poll_partial_results with the text \"#{choice}\" for the poll_question " \
"with ID #{question.id}. The text \"#{choice}\" could refer to any of these " \
"IDs in the poll_question_answers table: #{ids.join(", ")}" + tenant_info.to_s
end
end
end
end
end
end end

View File

@@ -0,0 +1,68 @@
require "rails_helper"
describe Admin::Poll::Results::QuestionComponent do
let(:poll) { create(:poll) }
let(:question) { create(:poll_question, poll: poll, title: "What do you want?") }
let!(:option_yes) { create(:poll_question_option, question: question, title: "Yes") }
let!(:option_no) { create(:poll_question_option, question: question, title: "No") }
it "renders question title and headers" do
render_inline Admin::Poll::Results::QuestionComponent.new(question, poll.partial_results)
expect(page).to have_css "h3", text: "What do you want?"
expect(page).to have_css "th", text: "Answer"
expect(page).to have_css "th", text: "Votes"
end
it "renders one row per option" do
render_inline Admin::Poll::Results::QuestionComponent.new(question, poll.partial_results)
expect(page).to have_css "tbody tr", count: 2
expect(page).to have_css "td", text: "Yes"
expect(page).to have_css "td", text: "No"
end
it "sums votes by option" do
create(:poll_partial_result, question: question, option: option_yes, amount: 2, date: Date.current)
create(:poll_partial_result, question: question, option: option_yes, amount: 1, date: Date.yesterday)
create(:poll_partial_result, question: question, option: option_no, amount: 5)
render_inline Admin::Poll::Results::QuestionComponent.new(question, question.partial_results)
page.find("tr#question_#{question.id}_0_result") do |yes_result|
expect(yes_result).to have_css "td", text: "Yes"
expect(yes_result).to have_css "td", text: "3"
end
page.find("tr#question_#{question.id}_1_result") do |no_result|
expect(no_result).to have_css "td", text: "No"
expect(no_result).to have_css "td", text: "5"
end
end
it "shows 0 when an option has no partial results" do
render_inline Admin::Poll::Results::QuestionComponent.new(question, poll.partial_results)
page.find("tr#question_#{question.id}_0_result") do |yes_result|
expect(yes_result).to have_css "td", text: "Yes"
expect(yes_result).to have_css "td", text: "0"
end
page.find("tr#question_#{question.id}_1_result") do |no_result|
expect(no_result).to have_css "td", text: "No"
expect(no_result).to have_css "td", text: "0"
end
end
it "ignores partial results from other questions" do
other_question = create(:poll_question, poll: poll)
create(:poll_question_option, question: other_question, title: "Yes")
create(:poll_question_option, question: other_question, title: "Irrelevant")
create(:poll_partial_result, question: other_question, answer: "Yes", amount: 9)
create(:poll_partial_result, question: other_question, answer: "Irrelevant", amount: 9)
render_inline Admin::Poll::Results::QuestionComponent.new(question, poll.partial_results)
expect(page).to have_css "td", text: "0", count: 2
end
end

View File

@@ -226,7 +226,14 @@ FactoryBot.define do
question factory: [:poll_question, :yes_no] question factory: [:poll_question, :yes_no]
author factory: :user author factory: :user
origin { "web" } origin { "web" }
answer { question.question_options.sample.title } option do
if answer
question.question_options.find_by(title: answer)
else
question.question_options.sample
end
end
after(:build) { |poll_partial_result| poll_partial_result.answer ||= poll_partial_result.option&.title }
end end
factory :poll_recount, class: "Poll::Recount" do factory :poll_recount, class: "Poll::Recount" do

View File

@@ -3,6 +3,8 @@ require "rails_helper"
describe "polls tasks" do describe "polls tasks" do
let(:poll) { create(:poll) } let(:poll) { create(:poll) }
let(:user) { create(:user, :level_two) } let(:user) { create(:user, :level_two) }
let(:booth_assignment) { create(:poll_booth_assignment) }
let(:other_booth_assignment) { create(:poll_booth_assignment) }
describe "polls:remove_duplicate_answers" do describe "polls:remove_duplicate_answers" do
before { Rake::Task["polls:remove_duplicate_answers"].reenable } before { Rake::Task["polls:remove_duplicate_answers"].reenable }
@@ -42,7 +44,7 @@ describe "polls tasks" do
answer_attributes = { question: question, author: user, answer: "Answer A" } answer_attributes = { question: question, author: user, answer: "Answer A" }
create(:poll_answer, answer_attributes.merge(option: option_a)) create(:poll_answer, answer_attributes.merge(option: option_a))
create(:poll_answer, answer_attributes.merge(option: option_b)) insert(:poll_answer, answer_attributes.merge(option_id: option_b.id))
expect(Poll::Answer.count).to eq 2 expect(Poll::Answer.count).to eq 2
@@ -126,10 +128,11 @@ describe "polls tasks" do
answer = create(:poll_answer, question: yes_no_question, author: user, answer: "Yes", option: nil) answer = create(:poll_answer, question: yes_no_question, author: user, answer: "Yes", option: nil)
abc_answer = create(:poll_answer, question: abc_question, author: user, answer: "Answer A", option: nil) abc_answer = create(:poll_answer, question: abc_question, author: user, answer: "Answer A", option: nil)
answer_with_inconsistent_option = create(:poll_answer, question: abc_question, insert(:poll_answer, question: abc_question,
author: user, author: user,
answer: "Answer A", answer: "Answer A",
option: option_b) option_id: option_b.id)
answer_with_inconsistent_option = Poll::Answer.find_by!(option: option_b)
answer_with_invalid_option = build(:poll_answer, question: abc_question, answer_with_invalid_option = build(:poll_answer, question: abc_question,
author: user, author: user,
answer: "Non existing", answer: "Non existing",
@@ -222,4 +225,309 @@ describe "polls tasks" do
end end
end end
end end
describe "polls:remove_duplicate_partial_results" do
before { Rake::Task["polls:remove_duplicate_partial_results"].reenable }
it "removes duplicate partial results" do
question = create(:poll_question_multiple, :abcde, poll: poll, max_votes: 4)
result_attributes = {
question_id: question.id,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Answer A",
option_id: nil
}
other_result_attributes = result_attributes.merge(answer: "Answer B")
result = create(:poll_partial_result, result_attributes)
other_result = create(:poll_partial_result, other_result_attributes)
other_booth_result = create(:poll_partial_result,
result_attributes.merge(booth_assignment_id: other_booth_assignment.id))
2.times { insert(:poll_partial_result, result_attributes) }
insert(:poll_partial_result, other_result_attributes)
insert(:poll_partial_result, result_attributes.merge(booth_assignment_id: other_booth_assignment.id))
expect(Poll::PartialResult.count).to eq 7
Rake.application.invoke_task("polls:remove_duplicate_partial_results")
expect(Poll::PartialResult.count).to eq 3
expect(Poll::PartialResult.all).to match_array [result, other_result, other_booth_result]
end
it "removes duplicate partial results in different languages" do
question = create(:poll_question_multiple, max_votes: 2)
create(:poll_question_option, question: question, title_en: "Yes", title_de: "Ja")
create(:poll_question_option, question: question, title_en: "No", title_de: "Nein")
create(:poll_question_option, question: question, title_en: "Maybe", title_de: "Vielleicht")
create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Yes",
option: nil)
create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Ja",
option: nil)
expect(Poll::PartialResult.count).to eq 2
Rake.application.invoke_task("polls:remove_duplicate_partial_results")
expect(Poll::PartialResult.count).to eq 1
end
it "does not remove duplicate partial results when many options are possible" do
question = create(:poll_question, title: "How do you pronounce it?")
create(:poll_question_option, question: question, title_en: "A", title_es: "EI")
create(:poll_question_option, question: question, title_en: "E", title_es: "I")
create(:poll_question_option, question: question, title_en: "I", title_es: "AI")
create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "I",
option: nil)
create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "AI",
option: nil)
expect(Poll::PartialResult.count).to eq 2
Rake.application.invoke_task("polls:remove_duplicate_partial_results")
expect(Poll::PartialResult.count).to eq 2
end
it "does not remove partial results with the same text and different options" do
question = create(:poll_question_multiple, :abcde, max_votes: 4)
option_a = question.question_options.find_by(title: "Answer A")
option_b = question.question_options.find_by(title: "Answer B")
result_attributes = {
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Answer A"
}
create(:poll_partial_result, result_attributes.merge(option: option_a))
insert(:poll_partial_result, result_attributes.merge(option_id: option_b.id))
expect(Poll::PartialResult.count).to eq 2
Rake.application.invoke_task("polls:remove_duplicate_partial_results")
expect(Poll::PartialResult.count).to eq 2
end
it "removes duplicate partial results on tenants" do
create(:tenant, schema: "partial_results")
Tenant.switch("partial_results") do
question = create(:poll_question_multiple, :abc)
booth_assignment = create(:poll_booth_assignment)
result_attributes = {
question_id: question.id,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Answer A",
option_id: nil
}
create(:poll_partial_result, result_attributes)
insert(:poll_partial_result, result_attributes)
expect(Poll::PartialResult.count).to eq 2
end
Rake.application.invoke_task("polls:remove_duplicate_partial_results")
Tenant.switch("partial_results") do
expect(Poll::PartialResult.count).to eq 1
end
end
it "removes duplicates in different languages even when amounts differ" do
question = create(:poll_question_multiple, max_votes: 2)
create(:poll_question_option, question: question, title_en: "Yes", title_de: "Ja")
create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Yes",
option: nil,
amount: 3)
create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Ja",
option: nil,
amount: 5)
expect(Poll::PartialResult.count).to eq 2
Rake.application.invoke_task("polls:remove_duplicate_partial_results")
expect(Poll::PartialResult.count).to eq 1
end
end
describe "polls:populate_partial_results_option_id" do
before do
Rake::Task["polls:remove_duplicate_partial_results"].reenable
Rake::Task["polls:populate_partial_results_option_id"].reenable
end
it "populates the option_id column of existing partial results when there's one valid option" do
yes_no_question = create(:poll_question, :yes_no, poll: poll)
abc_question = create(:poll_question_multiple, :abc, poll: poll)
option_a = abc_question.question_options.find_by(title: "Answer A")
option_b = abc_question.question_options.find_by(title: "Answer B")
result = create(:poll_partial_result,
question: yes_no_question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Yes",
option: nil)
abc_result = create(:poll_partial_result,
question: abc_question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Answer A",
option: nil)
insert(:poll_partial_result,
question: abc_question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Answer A",
option_id: option_b.id)
inconsistent_result = Poll::PartialResult.find_by!(option: option_b)
invalid_result = build(:poll_partial_result,
question: abc_question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Non existing",
option: nil)
invalid_result.save!(validate: false)
Rake.application.invoke_task("polls:populate_partial_results_option_id")
result.reload
abc_result.reload
inconsistent_result.reload
invalid_result.reload
expect(result.option_id).to eq yes_no_question.question_options.find_by(title: "Yes").id
expect(abc_result.option_id).to eq option_a.id
expect(inconsistent_result.option_id).to eq option_b.id
expect(invalid_result.option_id).to be nil
end
it "does not populate the option_id column when there are several valid options" do
question = create(:poll_question, title: "How do you pronounce it?")
create(:poll_question_option, question: question, title_en: "A", title_es: "EI")
create(:poll_question_option, question: question, title_en: "E", title_es: "I")
create(:poll_question_option, question: question, title_en: "I", title_es: "AI")
result = create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "I",
option: nil)
Rake.application.invoke_task("polls:populate_partial_results_option_id")
result.reload
expect(result.option_id).to be nil
end
it "removes duplicate partial results before populating the option_id column" do
question = create(:poll_question_multiple, :abc)
localized_question = create(:poll_question_multiple)
create(:poll_question_option, question: localized_question, title_en: "Yes", title_de: "Ja")
create(:poll_question_option, question: localized_question, title_en: "No", title_de: "Nein")
create(:poll_question_option, question: localized_question, title_en: "Maybe", title_de: "Vielleicht")
result_attributes = {
question_id: question.id,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Answer A",
option_id: nil
}
result = create(:poll_partial_result, result_attributes)
insert(:poll_partial_result, result_attributes)
localized_result_attributes = {
question: localized_question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
option: nil
}
localized_result = create(:poll_partial_result, localized_result_attributes.merge(answer: "Yes"))
create(:poll_partial_result, localized_result_attributes.merge(answer: "Ja"))
result.reload
localized_result.reload
expect(result.option_id).to be nil
expect(localized_result.option_id).to be nil
Rake.application.invoke_task("polls:populate_partial_results_option_id")
result.reload
localized_result.reload
expect(Poll::PartialResult.count).to eq 2
expect(result.option_id).to eq question.question_options.find_by(title: "Answer A").id
expect(localized_result.option_id).to eq localized_question.question_options.find_by(title: "Yes").id
end
it "populates the option_id column on tenants" do
create(:tenant, schema: "partial_results")
Tenant.switch("partial_results") do
question = create(:poll_question_multiple, :abc)
booth_assignment = create(:poll_booth_assignment)
create(:poll_partial_result,
question: question,
booth_assignment_id: booth_assignment.id,
date: Date.current,
answer: "Answer A",
option: nil)
end
Rake.application.invoke_task("polls:populate_partial_results_option_id")
Tenant.switch("partial_results") do
expect(Poll::Question.count).to eq 1
expect(Poll::PartialResult.count).to eq 1
question = Poll::Question.first
option_a = question.question_options.find_by(title: "Answer A")
expect(Poll::PartialResult.first.option_id).to eq option_a.id
end
end
end
end end

View File

@@ -89,17 +89,18 @@ describe Poll::Answer do
expect { answer.save }.not_to raise_error expect { answer.save }.not_to raise_error
end end
it "is valid for answers included in the Poll::Question's question_options list" do it "is valid for answers included in the list of titles for the option" do
question = create(:poll_question) question = create(:poll_question)
create(:poll_question_option, title: "One", question: question) option = create(:poll_question_option, title_en: "One", title_es: "Uno", question: question)
create(:poll_question_option, title: "Two", question: question) create(:poll_question_option, title: "Two", question: question)
create(:poll_question_option, title: "Three", question: question) create(:poll_question_option, title: "Three", question: create(:poll_question, poll: create(:poll)))
expect(build(:poll_answer, question: question, answer: "One")).to be_valid expect(build(:poll_answer, option: option, answer: "One")).to be_valid
expect(build(:poll_answer, question: question, answer: "Two")).to be_valid expect(build(:poll_answer, option: option, answer: "Uno")).to be_valid
expect(build(:poll_answer, question: question, answer: "Three")).to be_valid expect(build(:poll_answer, option: option, answer: "Two")).not_to be_valid
expect(build(:poll_answer, option: option, answer: "Three")).not_to be_valid
expect(build(:poll_answer, question: question, answer: "Four")).not_to be_valid expect(build(:poll_answer, option: option, answer: "Any")).not_to be_valid
end end
end end
end end

View File

@@ -2,17 +2,18 @@ require "rails_helper"
describe Poll::PartialResult do describe Poll::PartialResult do
describe "validations" do describe "validations" do
it "validates that the answers are included in the Poll::Question's list" do it "validates that the answers are included in the list of titles for the option" do
question = create(:poll_question) question = create(:poll_question)
create(:poll_question_option, title: "One", question: question) option = create(:poll_question_option, title_en: "One", title_es: "Uno", question: question)
create(:poll_question_option, title: "Two", question: question) create(:poll_question_option, title: "Two", question: question)
create(:poll_question_option, title: "Three", question: question) create(:poll_question_option, title: "Three", question: create(:poll_question, poll: create(:poll)))
expect(build(:poll_partial_result, question: question, answer: "One")).to be_valid expect(build(:poll_partial_result, option: option, answer: "One")).to be_valid
expect(build(:poll_partial_result, question: question, answer: "Two")).to be_valid expect(build(:poll_partial_result, option: option, answer: "Uno")).to be_valid
expect(build(:poll_partial_result, question: question, answer: "Three")).to be_valid expect(build(:poll_partial_result, option: option, answer: "Two")).not_to be_valid
expect(build(:poll_partial_result, option: option, answer: "Three")).not_to be_valid
expect(build(:poll_partial_result, question: question, answer: "Four")).not_to be_valid expect(build(:poll_partial_result, option: option, answer: "Any")).not_to be_valid
end end
it "dynamically validates the valid origins" do it "dynamically validates the valid origins" do
@@ -21,6 +22,75 @@ describe Poll::PartialResult do
expect(build(:poll_partial_result, origin: "custom")).to be_valid expect(build(:poll_partial_result, origin: "custom")).to be_valid
expect(build(:poll_partial_result, origin: "web")).not_to be_valid expect(build(:poll_partial_result, origin: "web")).not_to be_valid
end end
describe "option_id uniqueness" do
let(:booth_assignment) { create(:poll_booth_assignment) }
it "is not valid when there are two identical partial results" do
question = create(:poll_question_multiple, :abc)
option = question.question_options.first
create(:poll_partial_result,
question: question,
booth_assignment: booth_assignment,
date: Date.current,
option: option,
answer: "Answer A")
partial_result = build(:poll_partial_result,
question: question,
booth_assignment: booth_assignment,
date: Date.current,
option: option,
answer: "Answer A")
expect(partial_result).not_to be_valid
expect { partial_result.save(validate: false) }.to raise_error ActiveRecord::RecordNotUnique
end
it "is not valid when there are two results with the same option and different answer" do
question = create(:poll_question_multiple, :abc)
option = question.question_options.first
create(:poll_partial_result,
question: question,
booth_assignment: booth_assignment,
date: Date.current,
option: option,
answer: "Answer A")
partial_result = build(:poll_partial_result,
question: question,
booth_assignment: booth_assignment,
date: Date.current,
option: option,
answer: "Answer B")
expect(partial_result).not_to be_valid
expect { partial_result.save(validate: false) }.to raise_error ActiveRecord::RecordNotUnique
end
it "is valid when there are two identical results and the option is nil" do
question = create(:poll_question_multiple, :abc)
create(:poll_partial_result,
question: question,
booth_assignment: booth_assignment,
date: Date.current,
option: nil,
answer: "Answer A")
partial_result = build(:poll_partial_result,
question: question,
booth_assignment: booth_assignment,
date: Date.current,
option: nil,
answer: "Answer A")
expect(partial_result).to be_valid
expect { partial_result.save }.not_to raise_error
end
end
end end
describe "logging changes" do describe "logging changes" do

View File

@@ -228,7 +228,7 @@ describe "Admin booths assignments", :admin do
expect(page).not_to have_css "#recounts_list" expect(page).not_to have_css "#recounts_list"
end end
scenario "Results for a booth assignment" do scenario "Recounts for a booth assignment" do
poll = create(:poll) poll = create(:poll)
booth_assignment = create(:poll_booth_assignment, poll: poll) booth_assignment = create(:poll_booth_assignment, poll: poll)
other_booth_assignment = create(:poll_booth_assignment, poll: poll) other_booth_assignment = create(:poll_booth_assignment, poll: poll)
@@ -285,30 +285,6 @@ describe "Admin booths assignments", :admin do
click_link "Results" click_link "Results"
expect(page).to have_content(question_1.title)
within("#question_#{question_1.id}_0_result") do
expect(page).to have_content("Yes")
expect(page).to have_content(11)
end
within("#question_#{question_1.id}_1_result") do
expect(page).to have_content("No")
expect(page).to have_content(4)
end
expect(page).to have_content(question_2.title)
within("#question_#{question_2.id}_0_result") do
expect(page).to have_content("Today")
expect(page).to have_content(5)
end
within("#question_#{question_2.id}_1_result") do
expect(page).to have_content("Tomorrow")
expect(page).to have_content(6)
end
within("#white_results") { expect(page).to have_content("21") } within("#white_results") { expect(page).to have_content("21") }
within("#null_results") { expect(page).to have_content("44") } within("#null_results") { expect(page).to have_content("44") }
within("#total_results") { expect(page).to have_content("66") } within("#total_results") { expect(page).to have_content("66") }

View File

@@ -343,46 +343,6 @@ describe "Admin polls", :admin do
expect(page).to have_content "There are no results" expect(page).to have_content "There are no results"
end end
scenario "Show partial results" do
poll = create(:poll)
booth_assignment_1 = create(:poll_booth_assignment, poll: poll)
booth_assignment_2 = create(:poll_booth_assignment, poll: poll)
booth_assignment_3 = create(:poll_booth_assignment, poll: poll)
question_1 = create(:poll_question, poll: poll)
create(:poll_question_option, title: "Oui", question: question_1)
create(:poll_question_option, title: "Non", question: question_1)
question_2 = create(:poll_question, poll: poll)
create(:poll_question_option, title: "Aujourd'hui", question: question_2)
create(:poll_question_option, title: "Demain", question: question_2)
[booth_assignment_1, booth_assignment_2, booth_assignment_3].each do |ba|
create(:poll_partial_result,
booth_assignment: ba,
question: question_1,
answer: "Oui",
amount: 11)
create(:poll_partial_result,
booth_assignment: ba,
question: question_2,
answer: "Demain",
amount: 5)
end
create(:poll_recount,
booth_assignment: booth_assignment_1,
white_amount: 21,
null_amount: 44,
total_amount: 66)
visit admin_poll_results_path(poll)
expect(page).to have_content "Results by booth"
end
scenario "Enable stats and results for booth polls" do scenario "Enable stats and results for booth polls" do
unvoted_poll = create(:poll) unvoted_poll = create(:poll)
@@ -418,8 +378,9 @@ describe "Admin polls", :admin do
expect(page).not_to have_content "Results by booth" expect(page).not_to have_content "Results by booth"
end end
scenario "Results by answer" do scenario "Show results, recount and details by booth" do
poll = create(:poll) poll = create(:poll)
booth_assignment_1 = create(:poll_booth_assignment, poll: poll) booth_assignment_1 = create(:poll_booth_assignment, poll: poll)
booth_assignment_2 = create(:poll_booth_assignment, poll: poll) booth_assignment_2 = create(:poll_booth_assignment, poll: poll)
booth_assignment_3 = create(:poll_booth_assignment, poll: poll) booth_assignment_3 = create(:poll_booth_assignment, poll: poll)
@@ -427,7 +388,7 @@ describe "Admin polls", :admin do
question_1 = create(:poll_question, :yes_no, poll: poll) question_1 = create(:poll_question, :yes_no, poll: poll)
question_2 = create(:poll_question, poll: poll) question_2 = create(:poll_question, poll: poll)
create(:poll_question_option, title: "Today", question: question_2) create(:poll_question_option, title: "Today", question: question_2)
create(:poll_question_option, title: "Tomorrow", question: question_2) create(:poll_question_option, title: "Tomorrow", question: question_2)
[booth_assignment_1, booth_assignment_2, booth_assignment_3].each do |ba| [booth_assignment_1, booth_assignment_2, booth_assignment_3].each do |ba|
@@ -436,12 +397,14 @@ describe "Admin polls", :admin do
question: question_1, question: question_1,
answer: "Yes", answer: "Yes",
amount: 11) amount: 11)
create(:poll_partial_result, create(:poll_partial_result,
booth_assignment: ba, booth_assignment: ba,
question: question_2, question: question_2,
answer: "Tomorrow", answer: "Tomorrow",
amount: 5) amount: 5)
end end
create(:poll_recount, create(:poll_recount,
booth_assignment: booth_assignment_1, booth_assignment: booth_assignment_1,
white_amount: 21, white_amount: 21,
@@ -449,60 +412,21 @@ describe "Admin polls", :admin do
total_amount: 66) total_amount: 66)
visit admin_poll_path(poll) visit admin_poll_path(poll)
click_link "Results" click_link "Results"
expect(page).to have_content(question_1.title) expect(page).to have_content "Results by booth"
question_1.question_options.each_with_index do |option, i|
within("#question_#{question_1.id}_#{i}_result") do
expect(page).to have_content(option.title)
expect(page).to have_content([33, 0][i])
end
end
expect(page).to have_content(question_2.title)
question_2.question_options.each_with_index do |option, i|
within("#question_#{question_2.id}_#{i}_result") do
expect(page).to have_content(option.title)
expect(page).to have_content([0, 15][i])
end
end
within("#white_results") { expect(page).to have_content("21") } within("#white_results") { expect(page).to have_content("21") }
within("#null_results") { expect(page).to have_content("44") } within("#null_results") { expect(page).to have_content("44") }
within("#total_results") { expect(page).to have_content("66") } within("#total_results") { expect(page).to have_content("66") }
end
scenario "Link to results by booth" do expect(page).to have_link("See results", count: 3)
poll = create(:poll)
booth_assignment1 = create(:poll_booth_assignment, poll: poll)
booth_assignment2 = create(:poll_booth_assignment, poll: poll)
question = create(:poll_question, :yes_no, poll: poll) within("#booth_assignment_#{booth_assignment_1.id}_result") do
create(:poll_partial_result,
booth_assignment: booth_assignment1,
question: question,
answer: "Yes",
amount: 5)
create(:poll_partial_result,
booth_assignment: booth_assignment2,
question: question,
answer: "Yes",
amount: 6)
visit admin_poll_path(poll)
click_link "Results"
expect(page).to have_link("See results", count: 2)
within("#booth_assignment_#{booth_assignment1.id}_result") do
click_link "See results" click_link "See results"
end end
expect(page).to have_content booth_assignment1.booth.name expect(page).to have_content booth_assignment_1.booth.name
expect(page).to have_content "Results" expect(page).to have_content "Results"
expect(page).to have_content "Yes" expect(page).to have_content "Yes"
expect(page).to have_content "5" expect(page).to have_content "5"

View File

@@ -123,6 +123,9 @@ describe "Officing Results", :with_frozen_time do
click_link "See results" click_link "See results"
end end
expect(page).to have_content(I18n.l(Date.current.to_date, format: :long))
expect(page).to have_content(booth_name)
within("#question_#{question_1.id}_0_result") do within("#question_#{question_1.id}_0_result") do
expect(page).to have_content("5555") expect(page).to have_content("5555")
expect(page).not_to have_content("7777") expect(page).not_to have_content("7777")
@@ -134,50 +137,4 @@ describe "Officing Results", :with_frozen_time do
within("#question_#{question_1.id}_0_result") { expect(page).to have_content("5555") } within("#question_#{question_1.id}_0_result") { expect(page).to have_content("5555") }
within("#question_#{question_1.id}_1_result") { expect(page).to have_content("200") } within("#question_#{question_1.id}_1_result") { expect(page).to have_content("200") }
end end
scenario "Index lists all questions and answers" do
officer_assignment = poll_officer.officer_assignments.first
booth_assignment = officer_assignment.booth_assignment
booth = booth_assignment.booth
create(
:poll_partial_result,
officer_assignment: officer_assignment,
booth_assignment: booth_assignment,
date: poll.ends_at,
question: question_1,
amount: 33
)
create(
:poll_recount,
officer_assignment: officer_assignment,
booth_assignment: booth_assignment,
date: poll.ends_at,
white_amount: 21,
null_amount: 44,
total_amount: 66
)
visit officing_poll_results_path(poll,
date: I18n.l(poll.ends_at.to_date),
booth_assignment_id: officer_assignment.booth_assignment_id)
expect(page).to have_content(I18n.l(poll.ends_at.to_date, format: :long))
expect(page).to have_content(booth.name)
expect(page).to have_content(question_1.title)
question_1.question_options.each_with_index do |answer, i|
within("#question_#{question_1.id}_#{i}_result") { expect(page).to have_content(answer.title) }
end
expect(page).to have_content(question_2.title)
question_2.question_options.each_with_index do |answer, i|
within("#question_#{question_2.id}_#{i}_result") { expect(page).to have_content(answer.title) }
end
within("#white_results") { expect(page).to have_content("21") }
within("#null_results") { expect(page).to have_content("44") }
within("#total_results") { expect(page).to have_content("66") }
end
end end