Files
grecia/spec/models/poll/question_spec.rb
taitus f3050a1aa5 Manage correctly results and stats for open-ended questions
Note that we are not including Poll::PartialResults for open-ended
questions resutls. The reason is that we do not contemplate the
possibility of there being open questions in booths. Manually
counting and introducing the votes in the system is not feasible.
2025-10-16 14:26:30 +02:00

275 lines
11 KiB
Ruby

require "rails_helper"
RSpec.describe Poll::Question do
let(:poll_question) { build(:poll_question) }
describe "Concerns" do
it_behaves_like "acts as paranoid", :poll_question
it_behaves_like "globalizable", :poll_question
end
describe "#poll_question_id" do
it "is invalid if a poll is not selected" do
poll_question.poll_id = nil
expect(poll_question).not_to be_valid
end
it "is valid if a poll is selected" do
poll_question.poll_id = 1
expect(poll_question).to be_valid
end
end
describe "#copy_attributes_from_proposal" do
before { create_list(:geozone, 3) }
let(:proposal) { create(:proposal) }
it "copies the attributes from the proposal" do
poll_question.copy_attributes_from_proposal(proposal)
expect(poll_question.author).to eq(proposal.author)
expect(poll_question.author_visible_name).to eq(proposal.author.name)
expect(poll_question.proposal_id).to eq(proposal.id)
expect(poll_question.title).to eq(proposal.title)
end
context "locale with non-underscored name" do
it "correctly creates a translation" do
I18n.with_locale(:"pt-BR") do
poll_question.copy_attributes_from_proposal(proposal)
end
translation = poll_question.translations.first
expect(poll_question.title).to eq(proposal.title)
expect(translation.title).to eq(proposal.title)
expect(translation.locale).to eq(:"pt-BR")
end
end
end
describe "#options_total_votes" do
let!(:question) { create(:poll_question) }
let!(:option_yes) { create(:poll_question_option, question: question, title_en: "Yes", title_es: "") }
let!(:option_no) { create(:poll_question_option, question: question, title_en: "No", title_es: "No") }
before do
create(:poll_answer, question: question, option: option_yes, answer: "")
create(:poll_answer, question: question, option: option_yes, answer: "Yes")
create(:poll_answer, question: question, option: option_no, answer: "No")
end
it "includes answers in every language" do
expect(question.options_total_votes).to eq 3
end
it "includes partial results counted by option_id" do
booth_assignment = create(:poll_booth_assignment, poll: question.poll)
create(:poll_partial_result,
booth_assignment: booth_assignment,
question: question,
option: option_yes,
amount: 4)
expect(question.options_total_votes).to eq 7
end
it "does not include votes from other questions even with same answer text" do
other_question = create(:poll_question, poll: question.poll)
other_option_yes = create(:poll_question_option,
question: other_question,
title_en: "Yes",
title_es: "")
create(:poll_partial_result, question: other_question, option: other_option_yes, amount: 4)
create(:poll_answer, question: other_question, option: other_option_yes, answer: "Yes")
expect(question.options_total_votes).to eq 3
end
end
describe "#find_or_initialize_user_answer" do
let(:user) { create(:user) }
let(:other_user) { create(:user) }
context "unique question" do
let(:question) { create(:poll_question_unique, :abc) }
let(:answer_a) { question.question_options.find_by(title: "Answer A") }
let(:answer_b) { question.question_options.find_by(title: "Answer B") }
it "finds the existing answer for the same user" do
existing_answer = create(:poll_answer, question: question, author: user, option: answer_a)
create(:poll_answer, question: question, author: other_user, option: answer_b)
answer = question.find_or_initialize_user_answer(user, option_id: answer_b.id)
expect(answer).to eq existing_answer
expect(answer.author).to eq user
expect(answer.option).to eq answer_b
expect(answer.answer).to eq "Answer B"
end
it "initializes a new answer when only another user has answered" do
create(:poll_answer, question: question, author: other_user, option: answer_a)
answer = question.find_or_initialize_user_answer(user, option_id: answer_a.id)
expect(answer).to be_new_record
expect(answer.author).to eq user
expect(answer.option).to eq answer_a
expect(answer.answer).to eq "Answer A"
end
it "raises when option_id is invalid" do
expect do
question.find_or_initialize_user_answer(user, option_id: 999999)
end.to raise_error(ActiveRecord::RecordNotFound)
end
it "raises when option_id is nil" do
expect do
question.find_or_initialize_user_answer(user, answer_text: "ignored")
end.to raise_error(ActiveRecord::RecordNotFound)
end
end
context "multiple question" do
let(:question) { create(:poll_question_multiple, :abc, max_votes: 3) }
let(:answer_a) { question.question_options.find_by(title: "Answer A") }
let(:answer_b) { question.question_options.find_by(title: "Answer B") }
it "finds the existing answer for the same user and option" do
existing_answer = create(:poll_answer, question: question, author: user, option: answer_a)
create(:poll_answer, question: question, author: other_user, option: answer_a)
answer = question.find_or_initialize_user_answer(user, option_id: answer_a.id)
expect(answer).to eq existing_answer
expect(answer.author).to eq user
expect(answer.option).to eq answer_a
expect(answer.answer).to eq "Answer A"
end
it "initializes a new answer when selecting a different option" do
create(:poll_answer, question: question, author: user, option: answer_a)
create(:poll_answer, question: question, author: other_user, option: answer_b)
answer = question.find_or_initialize_user_answer(user, option_id: answer_b.id)
expect(answer).to be_new_record
expect(answer.author).to eq user
expect(answer.option).to eq answer_b
expect(answer.answer).to eq "Answer B"
end
end
context "Open-ended question" do
let(:question) { create(:poll_question_open) }
it "ignores invalid option_id and uses answer_text" do
answer = question.find_or_initialize_user_answer(user, option_id: 999999, answer_text: "Hi")
expect(answer.option).to be nil
expect(answer.answer).to eq "Hi"
end
it "ignores option_id when nil and assigns answer with option set to nil" do
answer = question.find_or_initialize_user_answer(user, answer_text: "Hi")
expect(answer.option).to be nil
expect(answer.answer).to eq "Hi"
end
it "reuses the existing poll answer for the user and updates answer" do
existing = create(:poll_answer, question: question, author: user, answer: "Before")
answer = question.find_or_initialize_user_answer(user, answer_text: "After")
expect(answer).to eq existing
expect(answer.author).to eq user
expect(answer.answer).to eq "After"
end
end
end
context "open-ended results" do
let(:poll) { create(:poll) }
let!(:question_open) { create(:poll_question_open, poll: poll) }
it "includes voters who didn't answer any questions in blank answers count" do
create(:poll_voter, poll: poll)
expect(question_open.open_ended_blank_answers_count).to eq 1
expect(question_open.open_ended_valid_answers_count).to eq 0
end
describe "#open_ended_valid_answers_count" do
it "returns 0 when there are no answers" do
expect(question_open.open_ended_valid_answers_count).to eq 0
end
it "counts answers" do
create(:poll_answer, question: question_open, answer: "Hello")
create(:poll_answer, question: question_open, answer: "Bye")
expect(question_open.open_ended_valid_answers_count).to eq 2
end
end
describe "#open_ended_blank_answers_count" do
let(:another_question) { create(:poll_question, :yes_no, poll: poll) }
let(:option_yes) { another_question.question_options.find_by(title: "Yes") }
let(:option_no) { another_question.question_options.find_by(title: "No") }
it "counts valid participants of the poll who did not answer the open-ended question" do
voters = create_list(:poll_voter, 3, poll: poll)
voters.each do |voter|
create(:poll_answer, question: another_question, author: voter.user, option: option_yes)
end
create(:poll_answer, question: question_open, author: voters.sample.user, answer: "Free text")
expect(question_open.open_ended_valid_answers_count).to eq 1
expect(question_open.open_ended_blank_answers_count).to eq 2
end
it "returns 0 when there are no valid participants in the poll" do
expect(question_open.open_ended_blank_answers_count).to eq 0
end
it "counts every user one time even if they answered many questions" do
multiple_question = create(:poll_question_multiple, :abc, poll: poll)
option_a = multiple_question.question_options.find_by(title: "Answer A")
option_b = multiple_question.question_options.find_by(title: "Answer B")
another_question_open = create(:poll_question_open, poll: poll)
voter = create(:poll_voter, poll: poll)
create(:poll_answer, question: multiple_question, author: voter.user, option: option_a)
create(:poll_answer, question: multiple_question, author: voter.user, option: option_b)
create(:poll_answer, question: another_question, author: voter.user, option: option_yes)
create(:poll_answer, question: another_question_open, author: voter.user, answer: "Free text")
expect(question_open.open_ended_blank_answers_count).to eq 1
end
end
describe "percentages" do
it "returns 0.0 when there aren't any answers" do
expect(question_open.open_ended_valid_percentage).to eq 0.0
expect(question_open.open_ended_blank_percentage).to eq 0.0
end
it "calculates valid and blank percentages based on counts" do
another_question = create(:poll_question, :yes_no, poll: poll)
option_yes = another_question.question_options.find_by(title: "Yes")
voters = create_list(:poll_voter, 4, poll: poll)
voters.each do |voter|
create(:poll_answer, question: another_question, author: voter.user, option: option_yes)
end
create(:poll_answer, question: question_open, author: voters.sample.user, answer: "A")
expect(question_open.open_ended_valid_percentage).to eq 25.0
expect(question_open.open_ended_blank_percentage).to eq 75.0
end
end
end
end