Show errors when submitting too many answers
This could be the case when JavaScript is disabled. Note that, in `Poll/WebVote` we're calling `given_answers` inside a transaction. Putting this code before the transaction resulted in a test failing sometimes, probably because of a bug that might be possible to reproduce by doing simultaneous requests.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<%= form_for poll, form_attributes do |f| %>
|
<%= form_for web_vote, form_attributes do |f| %>
|
||||||
<% questions.each do |question| %>
|
<% questions.each do |question| %>
|
||||||
<%= render Polls::Questions::QuestionComponent.new(question, disabled: disabled?) %>
|
<%= render Polls::Questions::QuestionComponent.new(question, form: f, disabled: disabled?) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= f.submit(class: "button", value: t("polls.form.vote"), disabled: disabled?) %>
|
<%= f.submit(class: "button", value: t("polls.form.vote"), disabled: disabled?) %>
|
||||||
|
|||||||
@@ -19,4 +19,5 @@
|
|||||||
<p><%= options_read_more_links %></p>
|
<p><%= options_read_more_links %></p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= form.error_for(:"question_#{question.id}") %>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
class Polls::Questions::QuestionComponent < ApplicationComponent
|
class Polls::Questions::QuestionComponent < ApplicationComponent
|
||||||
attr_reader :question, :disabled
|
attr_reader :question, :form, :disabled
|
||||||
alias_method :disabled?, :disabled
|
alias_method :disabled?, :disabled
|
||||||
use_helpers :current_user
|
|
||||||
|
|
||||||
def initialize(question, disabled: false)
|
def initialize(question, form:, disabled: false)
|
||||||
@question = question
|
@question = question
|
||||||
|
@form = form
|
||||||
@disabled = disabled
|
@disabled = disabled
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -69,6 +69,6 @@ class Polls::Questions::QuestionComponent < ApplicationComponent
|
|||||||
end
|
end
|
||||||
|
|
||||||
def checked?(option)
|
def checked?(option)
|
||||||
question.answers.where(author: current_user, option: option).any?
|
form.object.answers[question.id].find { |answer| answer.option_id == option.id }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
class Poll::WebVote
|
class Poll::WebVote
|
||||||
|
include ActiveModel::Validations
|
||||||
attr_reader :poll, :user
|
attr_reader :poll, :user
|
||||||
|
delegate :t, to: "ApplicationController.helpers"
|
||||||
|
|
||||||
|
validate :max_answers
|
||||||
|
|
||||||
def initialize(poll, user)
|
def initialize(poll, user)
|
||||||
@poll = poll
|
@poll = poll
|
||||||
@@ -10,30 +14,68 @@ class Poll::WebVote
|
|||||||
poll.questions.for_render.sort_for_list
|
poll.questions.for_render.sort_for_list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def answers
|
||||||
|
@answers ||= questions.to_h do |question|
|
||||||
|
[question.id, question.answers.where(author: user)]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update(params)
|
def update(params)
|
||||||
all_valid = true
|
all_valid = true
|
||||||
|
|
||||||
user.with_lock do
|
user.with_lock do
|
||||||
|
self.answers = given_answers(params)
|
||||||
|
|
||||||
questions.each do |question|
|
questions.each do |question|
|
||||||
question.answers.where(author: user).destroy_all
|
question.answers.where(author: user).where.not(id: answers[question.id].map(&:id)).destroy_all
|
||||||
next unless params[question.id.to_s]
|
|
||||||
|
|
||||||
option_ids = params[question.id.to_s][:option_id]
|
if valid? && answers[question.id].all?(&:valid?)
|
||||||
|
|
||||||
answers = Array(option_ids).map do |option_id|
|
|
||||||
question.find_or_initialize_user_answer(user, option_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
if answers.map(&:valid?).all?(true)
|
|
||||||
Poll::Voter.find_or_create_by!(user: user, poll: poll, origin: "web")
|
Poll::Voter.find_or_create_by!(user: user, poll: poll, origin: "web")
|
||||||
answers.each(&:save!)
|
answers[question.id].each(&:save!)
|
||||||
else
|
else
|
||||||
all_valid = false
|
all_valid = false
|
||||||
raise ActiveRecord::Rollback
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
raise ActiveRecord::Rollback unless all_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
all_valid
|
all_valid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_key
|
||||||
|
end
|
||||||
|
|
||||||
|
def persisted?
|
||||||
|
Poll::Voter.where(user: user, poll: poll, origin: "web").exists?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
attr_writer :answers
|
||||||
|
|
||||||
|
def given_answers(params)
|
||||||
|
questions.to_h do |question|
|
||||||
|
[question.id, answers_for_question(question, params[question.id.to_s])]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def answers_for_question(question, question_params)
|
||||||
|
return [] unless question_params
|
||||||
|
|
||||||
|
Array(question_params[:option_id]).map do |option_id|
|
||||||
|
question.find_or_initialize_user_answer(user, option_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def max_answers
|
||||||
|
questions.each do |question|
|
||||||
|
if answers[question.id].count > question.max_votes
|
||||||
|
errors.add(
|
||||||
|
:"question_#{question.id}",
|
||||||
|
t("polls.form.maximum_exceeded", maximum: question.max_votes, given: answers[question.id].count)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ en:
|
|||||||
final_date: "Final recounts/Results"
|
final_date: "Final recounts/Results"
|
||||||
form:
|
form:
|
||||||
vote: "Vote"
|
vote: "Vote"
|
||||||
|
maximum_exceeded: "you've selected %{given} answers, but the maximum you can select is %{maximum}"
|
||||||
index:
|
index:
|
||||||
filters:
|
filters:
|
||||||
current: "Open"
|
current: "Open"
|
||||||
|
|||||||
@@ -579,6 +579,7 @@ es:
|
|||||||
final_date: "Recuento final/Resultados"
|
final_date: "Recuento final/Resultados"
|
||||||
form:
|
form:
|
||||||
vote: "Votar"
|
vote: "Votar"
|
||||||
|
maximum_exceeded: "has seleccionado %{given} respuestas, pero el máximo que puedes seleccionar es %{maximum}"
|
||||||
index:
|
index:
|
||||||
filters:
|
filters:
|
||||||
current: "Abiertas"
|
current: "Abiertas"
|
||||||
|
|||||||
@@ -53,11 +53,13 @@ describe Polls::FormComponent do
|
|||||||
context "geozone restricted poll" do
|
context "geozone restricted poll" do
|
||||||
let(:poll) { create(:poll, geozone_restricted: true) }
|
let(:poll) { create(:poll, geozone_restricted: true) }
|
||||||
let(:geozone) { create(:geozone) }
|
let(:geozone) { create(:geozone) }
|
||||||
|
before { poll.geozones << geozone }
|
||||||
|
|
||||||
it "renders disabled fields for users from another geozone" do
|
context "user from another geozone" do
|
||||||
poll.geozones << geozone
|
let(:user) { create(:user, :level_two) }
|
||||||
sign_in(user)
|
before { sign_in(user) }
|
||||||
|
|
||||||
|
it "renders disabled fields" do
|
||||||
render_inline Polls::FormComponent.new(web_vote)
|
render_inline Polls::FormComponent.new(web_vote)
|
||||||
|
|
||||||
page.find("fieldset[disabled]") do |fieldset|
|
page.find("fieldset[disabled]") do |fieldset|
|
||||||
@@ -67,11 +69,13 @@ describe Polls::FormComponent do
|
|||||||
|
|
||||||
expect(page).to have_button "Vote", disabled: true
|
expect(page).to have_button "Vote", disabled: true
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "renders enabled fields for same-geozone users" do
|
context "user from the same geozone" do
|
||||||
poll.geozones << geozone
|
let(:user) { create(:user, :level_two, geozone: geozone) }
|
||||||
sign_in(create(:user, :level_two, geozone: geozone))
|
before { sign_in(user) }
|
||||||
|
|
||||||
|
it "renders enabled answers" do
|
||||||
render_inline Polls::FormComponent.new(web_vote)
|
render_inline Polls::FormComponent.new(web_vote)
|
||||||
|
|
||||||
expect(page).not_to have_css "fieldset[disabled]"
|
expect(page).not_to have_css "fieldset[disabled]"
|
||||||
@@ -81,3 +85,4 @@ describe Polls::FormComponent do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ describe Polls::Questions::QuestionComponent do
|
|||||||
let(:question) { create(:poll_question, :yes_no, poll: poll) }
|
let(:question) { create(:poll_question, :yes_no, poll: poll) }
|
||||||
let(:option_yes) { question.question_options.find_by(title: "Yes") }
|
let(:option_yes) { question.question_options.find_by(title: "Yes") }
|
||||||
let(:option_no) { question.question_options.find_by(title: "No") }
|
let(:option_no) { question.question_options.find_by(title: "No") }
|
||||||
|
let(:user) { User.new }
|
||||||
|
let(:web_vote) { Poll::WebVote.new(poll, user) }
|
||||||
|
let(:form) { ConsulFormBuilder.new(:web_vote, web_vote, ApplicationController.new.view_context, {}) }
|
||||||
|
|
||||||
it "renders more information links when any question option has additional information" do
|
it "renders more information links when any question option has additional information" do
|
||||||
allow_any_instance_of(Poll::Question::Option).to receive(:with_read_more?).and_return(true)
|
allow_any_instance_of(Poll::Question::Option).to receive(:with_read_more?).and_return(true)
|
||||||
|
|
||||||
render_inline Polls::Questions::QuestionComponent.new(question)
|
render_inline Polls::Questions::QuestionComponent.new(question, form: form)
|
||||||
|
|
||||||
page.find("#poll_question_#{question.id}") do |poll_question|
|
page.find("#poll_question_#{question.id}") do |poll_question|
|
||||||
expect(poll_question).to have_content "Read more about"
|
expect(poll_question).to have_content "Read more about"
|
||||||
@@ -20,13 +23,13 @@ describe Polls::Questions::QuestionComponent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "renders answers in given order" do
|
it "renders answers in given order" do
|
||||||
render_inline Polls::Questions::QuestionComponent.new(question)
|
render_inline Polls::Questions::QuestionComponent.new(question, form: form)
|
||||||
|
|
||||||
expect("Yes").to appear_before("No")
|
expect("Yes").to appear_before("No")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "renders disabled answers when given the disabled parameter" do
|
it "renders disabled answers when given the disabled parameter" do
|
||||||
render_inline Polls::Questions::QuestionComponent.new(question, disabled: true)
|
render_inline Polls::Questions::QuestionComponent.new(question, form: form, disabled: true)
|
||||||
|
|
||||||
page.find("fieldset[disabled]") do |fieldset|
|
page.find("fieldset[disabled]") do |fieldset|
|
||||||
expect(fieldset).to have_field "Yes"
|
expect(fieldset).to have_field "Yes"
|
||||||
@@ -39,7 +42,7 @@ describe Polls::Questions::QuestionComponent do
|
|||||||
before { sign_in(user) }
|
before { sign_in(user) }
|
||||||
|
|
||||||
it "renders radio buttons for single-choice questions" do
|
it "renders radio buttons for single-choice questions" do
|
||||||
render_inline Polls::Questions::QuestionComponent.new(question)
|
render_inline Polls::Questions::QuestionComponent.new(question, form: form)
|
||||||
|
|
||||||
expect(page).to have_field "Yes", type: :radio
|
expect(page).to have_field "Yes", type: :radio
|
||||||
expect(page).to have_field "No", type: :radio
|
expect(page).to have_field "No", type: :radio
|
||||||
@@ -47,7 +50,9 @@ describe Polls::Questions::QuestionComponent do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "renders checkboxes for multiple-choice questions" do
|
it "renders checkboxes for multiple-choice questions" do
|
||||||
render_inline Polls::Questions::QuestionComponent.new(create(:poll_question_multiple, :abc))
|
question = create(:poll_question_multiple, :abc, poll: poll)
|
||||||
|
|
||||||
|
render_inline Polls::Questions::QuestionComponent.new(question, form: form)
|
||||||
|
|
||||||
expect(page).to have_field "Answer A", type: :checkbox
|
expect(page).to have_field "Answer A", type: :checkbox
|
||||||
expect(page).to have_field "Answer B", type: :checkbox
|
expect(page).to have_field "Answer B", type: :checkbox
|
||||||
@@ -59,7 +64,7 @@ describe Polls::Questions::QuestionComponent do
|
|||||||
it "selects the option when users have already voted" do
|
it "selects the option when users have already voted" do
|
||||||
create(:poll_answer, author: user, question: question, option: option_yes)
|
create(:poll_answer, author: user, question: question, option: option_yes)
|
||||||
|
|
||||||
render_inline Polls::Questions::QuestionComponent.new(question)
|
render_inline Polls::Questions::QuestionComponent.new(question, form: form)
|
||||||
|
|
||||||
expect(page).to have_field "Yes", type: :radio, checked: true
|
expect(page).to have_field "Yes", type: :radio, checked: true
|
||||||
expect(page).to have_field "No", type: :radio, checked: false
|
expect(page).to have_field "No", type: :radio, checked: false
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ describe Poll::WebVote do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "updates a poll_voter with user and poll data" do
|
it "updates a poll_voter with user and poll data" do
|
||||||
create(:poll_answer, question: question, author: user, option: option_yes)
|
answer = create(:poll_answer, question: question, author: user, option: option_yes)
|
||||||
|
|
||||||
web_vote.update(question.id.to_s => { option_id: option_no.id.to_s })
|
web_vote.update(question.id.to_s => { option_id: option_no.id.to_s })
|
||||||
|
|
||||||
expect(poll.reload.voters.size).to eq 1
|
expect(poll.reload.voters.size).to eq 1
|
||||||
expect(question.reload.answers.size).to eq 1
|
expect(question.reload.answers.size).to eq 1
|
||||||
|
expect(question.answers.first).to eq answer.reload
|
||||||
|
|
||||||
voter = poll.voters.first
|
voter = poll.voters.first
|
||||||
answer = question.answers.first
|
|
||||||
|
|
||||||
expect(answer.author).to eq user
|
expect(answer.author).to eq user
|
||||||
expect(answer.option).to eq option_no
|
expect(answer.option).to eq option_no
|
||||||
|
|||||||
@@ -71,4 +71,33 @@ describe "Poll Votation Type" do
|
|||||||
expect(page).to have_field "Answer B", type: :checkbox, checked: false
|
expect(page).to have_field "Answer B", type: :checkbox, checked: false
|
||||||
expect(page).to have_field "Answer C", type: :checkbox, checked: true
|
expect(page).to have_field "Answer C", type: :checkbox, checked: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scenario "Too many answers", :no_js do
|
||||||
|
create(:poll_question_multiple, :abcde, poll: poll, max_votes: 2, title: "Which ones are correct?")
|
||||||
|
|
||||||
|
visit poll_path(poll)
|
||||||
|
check "Answer A"
|
||||||
|
check "Answer B"
|
||||||
|
check "Answer D"
|
||||||
|
click_button "Vote"
|
||||||
|
|
||||||
|
within_fieldset("Which ones are correct?") do
|
||||||
|
expect(page).to have_content "you've selected 3 answers, but the maximum you can select is 2"
|
||||||
|
expect(page).to have_field "Answer A", type: :checkbox, checked: true
|
||||||
|
expect(page).to have_field "Answer B", type: :checkbox, checked: true
|
||||||
|
expect(page).to have_field "Answer C", type: :checkbox, checked: false
|
||||||
|
expect(page).to have_field "Answer D", type: :checkbox, checked: true
|
||||||
|
expect(page).to have_field "Answer E", type: :checkbox, checked: false
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).not_to have_content "Thank you for voting!"
|
||||||
|
|
||||||
|
visit poll_path(poll)
|
||||||
|
|
||||||
|
expect(page).not_to have_content "but the maximum you can select"
|
||||||
|
|
||||||
|
within_fieldset("Which ones are correct?") do
|
||||||
|
expect(page).to have_field type: :checkbox, checked: false, count: 5
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user