Files
grecia/spec/models/poll/web_vote_spec.rb
Javi Martín a7e1b42b6c Use checkboxes and radio buttons on poll forms
Our original interface to vote in a poll had a few issues:

* Since there was no button to send the form, it wasn't clear that
  selecting an option would automatically store it in the database.
* The interface was almost identical for single-choice questions and
  multiple-choice questions, which made it hard to know which type of
  question we were answering.
* Adding other type of questions, like open answers, was hard since we
  would have to add a different submit button for each answer.

So we're now using radio buttons for single-choice questions and
checkboxes for multiple-choice questions, which are the native controls
designed for these purposes, and a button to send the whole form.

Since we don't have a database table for poll ballots like we have for
budget ballots, we're adding a new `Poll::WebVote` model to manage poll
ballots. We're using WebVote instead of Ballot or Vote because they
could be mistaken with other vote classes.

Note that browsers don't allow removing answers with radio buttons, so
once somebody has voted in a single-choice question, they can't remove
the vote unless they manually edit their HTML. This is the same behavior
we had before commit 7df0e9a96.

As mentioned in c2010f975, we're now adding the `ChangeByZero` rubocop
rule, since we've removed the test that used `and change`.
2025-08-14 13:06:37 +02:00

111 lines
3.8 KiB
Ruby

require "rails_helper"
describe Poll::WebVote do
describe "#update" do
let(:user) { create(:user, :level_two) }
let(:poll) { create(:poll) }
let!(:question) { create(:poll_question, :yes_no, poll: poll) }
let(:option_yes) { question.question_options.find_by(title: "Yes") }
let(:option_no) { question.question_options.find_by(title: "No") }
let(:web_vote) { Poll::WebVote.new(poll, user) }
it "creates a poll_voter with user and poll data" do
expect(poll.voters).to be_blank
expect(question.answers).to be_blank
web_vote.update(question.id.to_s => { option_id: option_yes.id.to_s })
expect(poll.reload.voters.size).to eq 1
expect(question.reload.answers.size).to eq 1
voter = poll.voters.first
answer = question.answers.first
expect(answer.author).to eq user
expect(voter.document_number).to eq user.document_number
expect(voter.poll_id).to eq answer.poll.id
expect(voter.officer_id).to be nil
end
it "updates a poll_voter with user and poll data" do
create(:poll_answer, question: question, author: user, option: option_yes)
web_vote.update(question.id.to_s => { option_id: option_no.id.to_s })
expect(poll.reload.voters.size).to eq 1
expect(question.reload.answers.size).to eq 1
voter = poll.voters.first
answer = question.answers.first
expect(answer.author).to eq user
expect(answer.option).to eq option_no
expect(voter.document_number).to eq answer.author.document_number
expect(voter.poll_id).to eq answer.poll.id
end
it "does not save the answer if the voter is invalid" do
allow_any_instance_of(Poll::Voter).to receive(:valid?).and_return(false)
expect do
web_vote.update(question.id.to_s => { option_id: option_yes.id.to_s })
end.to raise_error(ActiveRecord::RecordInvalid)
expect(poll.voters).to be_blank
expect(question.answers).to be_blank
end
it "does not create voters or answers when leaving everything blank" do
web_vote.update({})
expect(poll.reload.voters.size).to eq 0
expect(question.reload.answers.size).to eq 0
end
it "deletes existing answers and voter when no answers are given" do
create(:poll_answer, question: question, author: user, option: option_yes)
create(:poll_voter, poll: poll, user: user)
web_vote.update({})
expect(poll.reload.voters.size).to eq 0
expect(question.reload.answers.size).to eq 0
end
context "creating answers at the same time", :race_condition do
it "does not create two voters or two answers for two different answers" do
[option_yes, option_no].map do |option|
Thread.new { web_vote.update(question.id.to_s => { option_id: option.id.to_s }) }
end.each(&:join)
expect(Poll::Voter.count).to be 1
expect(Poll::Answer.count).to be 1
end
it "does not create two voters for duplicate answers" do
2.times.map do
Thread.new { web_vote.update(question.id.to_s => { option_id: option_yes.id.to_s }) }
end.each(&:join)
expect(Poll::Voter.count).to be 1
end
it "validates max votes on multiple-answer questions" do
question = create(:poll_question_multiple, :abc, poll: poll, max_votes: 2)
option_a = question.question_options.find_by(title: "Answer A")
option_b = question.question_options.find_by(title: "Answer B")
option_c = question.question_options.find_by(title: "Answer C")
create(:poll_answer, question: question, author: user, option: option_a)
[option_b, option_c].map do |option|
Thread.new do
web_vote.update(question.id.to_s => { option_id: [option_a.id.to_s, option.id.to_s] })
end
end.each(&:join)
expect(Poll::Answer.count).to be 2
end
end
end
end