Also make the :yes_no factory trait create a votation_type_unique by default, since yes/no questions should always be unique.
290 lines
11 KiB
Ruby
290 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: "Sí") }
|
|
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: "Sí")
|
|
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: "Sí")
|
|
|
|
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
|
|
|
|
describe "scopes" do
|
|
describe ".for_physical_votes" do
|
|
it "returns unique and multiple, but not open_ended" do
|
|
question_unique = create(:poll_question_unique)
|
|
question_multiple = create(:poll_question_multiple)
|
|
question_open_ended = create(:poll_question_open)
|
|
|
|
result = Poll::Question.for_physical_votes
|
|
|
|
expect(result).to match_array [question_unique, question_multiple]
|
|
expect(result).not_to include question_open_ended
|
|
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
|