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.
This commit is contained in:
@@ -1,25 +1,46 @@
|
||||
<h3 id="<%= question.title.parameterize %>"><%= question.title %></h3>
|
||||
<table id="question_<%= question.id %>_results_table">
|
||||
<thead>
|
||||
<tr>
|
||||
<%- question.question_options.each do |option| %>
|
||||
<th scope="col" class="<%= option_styles(option) %>">
|
||||
<% if most_voted_option?(option) %>
|
||||
<span class="show-for-sr"><%= t("polls.show.results.most_voted_answer") %></span>
|
||||
<% end %>
|
||||
<%= option.title %>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<%- question.question_options.each do |option| %>
|
||||
<td id="option_<%= option.id %>_result" class="<%= option_styles(option) %>">
|
||||
<%= option.total_votes %>
|
||||
(<%= option.total_votes_percentage.round(2) %>%)
|
||||
<% if question.accepts_options? %>
|
||||
<thead>
|
||||
<tr>
|
||||
<%- question.question_options.each do |option| %>
|
||||
<th scope="col" class="<%= option_styles(option) %>">
|
||||
<% if most_voted_option?(option) %>
|
||||
<span class="show-for-sr"><%= t("polls.show.results.most_voted_answer") %></span>
|
||||
<% end %>
|
||||
<%= option.title %>
|
||||
</th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<%- question.question_options.each do |option| %>
|
||||
<td class="<%= option_styles(option) %>">
|
||||
<%= option.total_votes %>
|
||||
(<%= option.total_votes_percentage.round(2) %>%)
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
</tbody>
|
||||
<% else %>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><%= t("polls.show.results.open_ended.valid") %></th>
|
||||
<th scope="col"><%= t("polls.show.results.open_ended.blank") %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<%= question.open_ended_valid_answers_count %>
|
||||
(<%= question.open_ended_valid_percentage.round(2) %>%)
|
||||
</td>
|
||||
<% end %>
|
||||
</tr>
|
||||
</tbody>
|
||||
<td>
|
||||
<%= question.open_ended_blank_answers_count %>
|
||||
(<%= question.open_ended_blank_percentage.round(2) %>%)
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
@@ -87,6 +87,26 @@ class Poll::Question < ApplicationRecord
|
||||
answer
|
||||
end
|
||||
|
||||
def open_ended_valid_answers_count
|
||||
answers.count
|
||||
end
|
||||
|
||||
def open_ended_blank_answers_count
|
||||
poll.voters.count - open_ended_valid_answers_count
|
||||
end
|
||||
|
||||
def open_ended_valid_percentage
|
||||
return 0.0 if open_ended_total_answers.zero?
|
||||
|
||||
(open_ended_valid_answers_count * 100.0) / open_ended_total_answers
|
||||
end
|
||||
|
||||
def open_ended_blank_percentage
|
||||
return 0.0 if open_ended_total_answers.zero?
|
||||
|
||||
(open_ended_blank_answers_count * 100.0) / open_ended_total_answers
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_by_attributes(user, option_id)
|
||||
@@ -96,4 +116,8 @@ class Poll::Question < ApplicationRecord
|
||||
{ author: user }
|
||||
end
|
||||
end
|
||||
|
||||
def open_ended_total_answers
|
||||
open_ended_valid_answers_count + open_ended_blank_answers_count
|
||||
end
|
||||
end
|
||||
|
||||
@@ -639,6 +639,9 @@ en:
|
||||
results:
|
||||
title: "Questions"
|
||||
most_voted_answer: "Most voted answer: "
|
||||
open_ended:
|
||||
valid: Valid
|
||||
blank: Blank
|
||||
poll_header:
|
||||
back_to_proposal: Back to proposal
|
||||
poll_questions:
|
||||
|
||||
@@ -639,6 +639,9 @@ es:
|
||||
results:
|
||||
title: "Preguntas"
|
||||
most_voted_answer: "Respuesta más votada: "
|
||||
open_ended:
|
||||
valid: Válidos
|
||||
blank: En blanco
|
||||
poll_header:
|
||||
back_to_proposal: Volver a la propuesta
|
||||
poll_questions:
|
||||
|
||||
47
spec/components/polls/results/question_component_spec.rb
Normal file
47
spec/components/polls/results/question_component_spec.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
require "rails_helper"
|
||||
|
||||
describe Polls::Results::QuestionComponent do
|
||||
context "question that accepts options" do
|
||||
let(:question) { create(:poll_question, :yes_no) }
|
||||
let(:option_yes) { question.question_options.find_by(title: "Yes") }
|
||||
let(:option_no) { question.question_options.find_by(title: "No") }
|
||||
|
||||
it "renders results table content" do
|
||||
create(:poll_answer, question: question, option: option_yes)
|
||||
create(:poll_answer, question: question, option: option_no)
|
||||
|
||||
render_inline Polls::Results::QuestionComponent.new(question)
|
||||
|
||||
expect(page).to have_table with_rows: [{ "Most voted answer: Yes" => "1 (50.0%)",
|
||||
"No" => "1 (50.0%)" }]
|
||||
|
||||
page.find("table") do |table|
|
||||
expect(table).to have_css "th.win", count: 1
|
||||
expect(table).to have_css "td.win", count: 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "question that does not accept options" do
|
||||
let(:open_ended_question) { create(:poll_question_open) }
|
||||
|
||||
it "renders open_ended headers and empty counts when there are no participants" do
|
||||
render_inline Polls::Results::QuestionComponent.new(open_ended_question)
|
||||
|
||||
expect(page).to have_table with_rows: [{ "Valid" => "0 (0.0%)",
|
||||
"Blank" => "0 (0.0%)" }]
|
||||
end
|
||||
|
||||
it "renders counts and percentages provided by the model metrics" do
|
||||
allow(open_ended_question).to receive_messages(
|
||||
open_ended_valid_answers_count: 3,
|
||||
open_ended_blank_answers_count: 1
|
||||
)
|
||||
|
||||
render_inline Polls::Results::QuestionComponent.new(open_ended_question)
|
||||
|
||||
expect(page).to have_table with_rows: [{ "Valid" => "3 (75.0%)",
|
||||
"Blank" => "1 (25.0%)" }]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,47 +3,34 @@ require "rails_helper"
|
||||
describe Polls::ResultsComponent do
|
||||
let(:poll) { create(:poll) }
|
||||
|
||||
let(:question_1) { create(:poll_question, poll: poll, title: "Do you like Consul Democracy?") }
|
||||
let(:option_yes) { create(:poll_question_option, question: question_1, title: "Yes") }
|
||||
let(:option_no) { create(:poll_question_option, question: question_1, title: "No") }
|
||||
let(:question_1) { create(:poll_question, :yes_no, poll: poll, title: "Do you like Consul Democracy?") }
|
||||
let(:option_yes) { question_1.question_options.find_by(title: "Yes") }
|
||||
let(:option_no) { question_1.question_options.find_by(title: "No") }
|
||||
|
||||
let(:question_2) { create(:poll_question, poll: poll, title: "What's your favorite color?") }
|
||||
let(:option_blue) { create(:poll_question_option, question: question_2, title: "Blue") }
|
||||
let(:option_green) { create(:poll_question_option, question: question_2, title: "Green") }
|
||||
let(:option_yellow) { create(:poll_question_option, question: question_2, title: "Yellow") }
|
||||
let(:question_2) { create(:poll_question, :abc, poll: poll, title: "Which option do you prefer?") }
|
||||
let(:option_a) { question_2.question_options.find_by(title: "Answer A") }
|
||||
let(:option_b) { question_2.question_options.find_by(title: "Answer B") }
|
||||
let(:option_c) { question_2.question_options.find_by(title: "Answer C") }
|
||||
|
||||
it "renders results content" do
|
||||
create_list(:poll_answer, 2, question: question_1, option: option_yes)
|
||||
create(:poll_answer, question: question_1, option: option_no)
|
||||
|
||||
create(:poll_answer, question: question_2, option: option_blue)
|
||||
create(:poll_answer, question: question_2, option: option_green)
|
||||
create(:poll_answer, question: question_2, option: option_yellow)
|
||||
create(:poll_answer, question: question_2, option: option_a)
|
||||
create(:poll_answer, question: question_2, option: option_b)
|
||||
create(:poll_answer, question: question_2, option: option_c)
|
||||
|
||||
render_inline Polls::ResultsComponent.new(poll)
|
||||
|
||||
expect(page).to have_content "Do you like Consul Democracy?"
|
||||
expect(page).to have_table "question_#{question_1.id}_results_table",
|
||||
with_rows: [{ "Most voted answer: Yes" => "2 (66.67%)",
|
||||
"No" => "1 (33.33%)" }]
|
||||
|
||||
page.find("#question_#{question_1.id}_results_table") do |table|
|
||||
expect(table).to have_css "#option_#{option_yes.id}_result", text: "2 (66.67%)", normalize_ws: true
|
||||
expect(table).to have_css "#option_#{option_no.id}_result", text: "1 (33.33%)", normalize_ws: true
|
||||
end
|
||||
|
||||
expect(page).to have_content "What's your favorite color?"
|
||||
|
||||
page.find("#question_#{question_2.id}_results_table") do |table|
|
||||
expect(table).to have_css "#option_#{option_blue.id}_result", text: "1 (33.33%)", normalize_ws: true
|
||||
expect(table).to have_css "#option_#{option_green.id}_result", text: "1 (33.33%)", normalize_ws: true
|
||||
expect(table).to have_css "#option_#{option_yellow.id}_result", text: "1 (33.33%)", normalize_ws: true
|
||||
end
|
||||
end
|
||||
|
||||
it "renders results for polls with questions but without answers" do
|
||||
poll = create(:poll, :expired, results_enabled: true)
|
||||
question = create(:poll_question, poll: poll)
|
||||
|
||||
render_inline Polls::ResultsComponent.new(poll)
|
||||
|
||||
expect(page).to have_content question.title
|
||||
expect(page).to have_content "Which option do you prefer?"
|
||||
expect(page).to have_table "question_#{question_2.id}_results_table",
|
||||
with_rows: [{ "Most voted answer: Answer A" => "1 (33.33%)",
|
||||
"Answer B" => "1 (33.33%)",
|
||||
"Answer C" => "1 (33.33%)" }]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -188,4 +188,87 @@ RSpec.describe Poll::Question do
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user