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`.
This commit is contained in:
Javi Martín
2024-05-16 02:43:55 +02:00
parent fd14c55615
commit a7e1b42b6c
35 changed files with 612 additions and 687 deletions

View File

@@ -571,6 +571,9 @@ RSpec/BeNil:
Enabled: true
EnforcedStyle: be
RSpec/ChangeByZero:
Enabled: true
RSpec/ContextMethod:
Enabled: true

View File

@@ -1396,8 +1396,7 @@
position: relative;
}
.public .poll,
.poll-question {
.public .poll {
border: 1px solid $border;
margin-bottom: calc($line-height / 2);
padding: calc($line-height / 2);
@@ -1413,47 +1412,6 @@
}
}
.poll-question {
padding: 0 $line-height;
h3 {
padding-top: $line-height;
}
}
.poll-question-options {
@include flex-with-gap($line-height * 0.25);
flex-wrap: wrap;
.button {
min-width: rem-calc(168);
@include breakpoint(medium down) {
width: 100%;
}
&.answered {
background: #f4f8ec;
border: 2px solid #92ba48;
color: color-pick-contrast(#f4f8ec);
position: relative;
&::after {
background: #92ba48;
border-radius: rem-calc(20);
color: #fff;
content: "\6c";
font-family: "icons" !important;
font-size: rem-calc(12);
padding: calc($line-height / 4);
position: absolute;
right: -6px;
top: -6px;
}
}
}
}
// 09. Polls results and stats
// ---------------------------

View File

@@ -0,0 +1,29 @@
.poll-form {
label {
@include radio-or-checkbox-and-label-alignment;
font-weight: normal;
&:first-of-type {
margin-top: calc($line-height / 2);
}
}
fieldset + fieldset {
margin-top: calc($line-height / 2);
}
legend {
@include header-font-size(h3);
margin-bottom: 0;
}
.help-text {
font-size: 1em;
font-style: normal;
font-weight: bold;
}
[type=submit] {
margin-top: calc($line-height * 3 / 4);
}
}

View File

@@ -1,3 +1,7 @@
<% questions.each do |question| %>
<%= render Polls::Questions::QuestionComponent.new(question) %>
<%= form_for poll, form_attributes do |f| %>
<% questions.each do |question| %>
<%= render Polls::Questions::QuestionComponent.new(question, disabled: disabled?) %>
<% end %>
<%= f.submit(class: "button", value: t("polls.form.vote"), disabled: disabled?) %>
<% end %>

View File

@@ -1,7 +1,19 @@
class Polls::FormComponent < ApplicationComponent
attr_reader :questions
attr_reader :web_vote
use_helpers :cannot?, :current_user
delegate :poll, :questions, to: :web_vote
def initialize(questions)
@questions = questions
def initialize(web_vote)
@web_vote = web_vote
end
private
def form_attributes
{ url: answer_poll_path(poll), method: :post, html: { class: "poll-form" }}
end
def disabled?
cannot?(:answer, poll) || poll.voted_in_booth?(current_user)
end
end

View File

@@ -1,35 +0,0 @@
<div class="poll-question-options">
<% if can?(:answer, question) && !question.poll.voted_in_booth?(current_user) %>
<% question_options.each do |question_option| %>
<% if already_answered?(question_option) %>
<%= button_to question_option.title,
question_answer_path(question, user_answer(question_option)),
method: :delete,
remote: true,
title: t("poll_questions.show.voted", answer: question_option.title),
class: "button answered",
"aria-pressed": true %>
<% else %>
<%= button_to question_option.title,
question_answers_path(question, option_id: question_option.id),
remote: true,
title: t("poll_questions.show.vote_answer", answer: question_option.title),
class: "button secondary hollow",
"aria-pressed": false,
disabled: disable_option?(question_option) %>
<% end %>
<% end %>
<% elsif !user_signed_in? %>
<% question_options.each do |question_option| %>
<%= link_to question_option.title, new_user_session_path, class: "button secondary hollow" %>
<% end %>
<% elsif !current_user.level_two_or_three_verified? %>
<% question_options.each do |question_option| %>
<%= link_to question_option.title, verification_path, class: "button secondary hollow" %>
<% end %>
<% else %>
<% question_options.each do |question_option| %>
<span class="button secondary hollow disabled"><%= question_option.title %></span>
<% end %>
<% end %>
</div>

View File

@@ -1,30 +0,0 @@
class Polls::Questions::OptionsComponent < ApplicationComponent
attr_reader :question
use_helpers :can?, :current_user, :user_signed_in?
def initialize(question)
@question = question
end
def already_answered?(question_option)
user_answer(question_option).present?
end
def question_options
question.question_options
end
def user_answer(question_option)
user_answers.find_by(answer: question_option.title)
end
def disable_option?(question_option)
question.multiple? && user_answers.count == question.max_votes
end
private
def user_answers
@user_answers ||= question.answers.by_author(current_user)
end
end

View File

@@ -1,22 +1,22 @@
<div id="<%= dom_id(question) %>" class="poll-question">
<h3>
<%= question.title %>
</h3>
<fieldset <%= fieldset_attributes %>>
<legend><%= question.title %></legend>
<% if question.votation_type.present? %>
<strong>
<%= t("poll_questions.description.#{question.vote_type}", maximum: question.max_votes) %>
</strong>
<% if multiple_choice? %>
<%= multiple_choice_help_text %>
<% question.question_options.each do |option| %>
<%= multiple_choice_field(option) %>
<% end %>
<% else %>
<% question.question_options.each do |option| %>
<%= single_choice_field(option) %>
<% end %>
<% end %>
<div id="<%= dom_id(question) %>_options" class="padding">
<%= render Polls::Questions::OptionsComponent.new(question) %>
</div>
<% if question.options_with_read_more? %>
<div>
<p><%= t("poll_questions.read_more_about") %></p>
<p><%= options_read_more_links %></p>
</div>
<% end %>
</div>
</fieldset>

View File

@@ -1,13 +1,65 @@
class Polls::Questions::QuestionComponent < ApplicationComponent
attr_reader :question
attr_reader :question, :disabled
alias_method :disabled?, :disabled
use_helpers :current_user
def initialize(question)
def initialize(question, disabled: false)
@question = question
@disabled = disabled
end
def options_read_more_links
safe_join(question.options_with_read_more.map do |option|
link_to option.title, "#option_#{option.id}"
end, ", ")
end
private
def fieldset_attributes
tag.attributes(
id: dom_id(question),
disabled: ("disabled" if disabled?),
data: { max_votes: question.max_votes }
)
end
def options_read_more_links
safe_join(question.options_with_read_more.map do |option|
link_to option.title, "#option_#{option.id}"
end, ", ")
end
def multiple_choice?
question.multiple?
end
def multiple_choice_help_text
tag.span(
t("poll_questions.description.multiple", maximum: question.max_votes),
class: "help-text"
)
end
def multiple_choice_field(option)
choice_field(option) do
check_box_tag "web_vote[#{question.id}][option_id][]",
option.id,
checked?(option),
id: "web_vote_option_#{option.id}"
end
end
def single_choice_field(option)
choice_field(option) do
radio_button_tag "web_vote[#{question.id}][option_id]",
option.id,
checked?(option),
id: "web_vote_option_#{option.id}"
end
end
def choice_field(option, &block)
label_tag("web_vote_option_#{option.id}") do
block.call + option.title
end
end
def checked?(option)
question.answers.where(author: current_user, option: option).any?
end
end

View File

@@ -1,36 +0,0 @@
class Polls::AnswersController < ApplicationController
load_and_authorize_resource :question, class: "::Poll::Question"
load_and_authorize_resource :answer, class: "::Poll::Answer",
through: :question,
through_association: :answers,
only: :destroy
def create
authorize! :answer, @question
answer = @question.find_or_initialize_user_answer(current_user, params[:option_id])
answer.save_and_record_voter_participation
respond_to do |format|
format.html do
redirect_to request.referer
end
format.js do
render :show
end
end
end
def destroy
@answer.destroy_and_remove_voter_participation
respond_to do |format|
format.html do
redirect_to request.referer
end
format.js do
render :show
end
end
end
end

View File

@@ -9,7 +9,7 @@ class PollsController < ApplicationController
load_and_authorize_resource
has_filters %w[current expired]
has_orders %w[most_voted newest oldest], only: :show
has_orders %w[most_voted newest oldest], only: [:show, :answer]
def index
@polls = Kaminari.paginate_array(
@@ -18,10 +18,21 @@ class PollsController < ApplicationController
end
def show
@questions = @poll.questions.for_render.sort_for_list
@web_vote = Poll::WebVote.new(@poll, current_user)
@comment_tree = CommentTree.new(@poll, params[:page], @current_order)
end
def answer
@web_vote = Poll::WebVote.new(@poll, current_user)
if @web_vote.update(answer_params)
redirect_to @poll, notice: t("flash.actions.create.poll_voter")
else
@comment_tree = CommentTree.new(@poll, params[:page], @current_order)
render :show
end
end
def stats
@stats = Poll::Stats.new(@poll).tap(&:generate)
end
@@ -38,4 +49,8 @@ class PollsController < ApplicationController
def load_active_poll
@active_poll = ActivePoll.first
end
def answer_params
params[:web_vote] || {}
end
end

View File

@@ -110,12 +110,6 @@ module Abilities
can :answer, Poll do |poll|
poll.answerable_by?(user)
end
can :answer, Poll::Question do |question|
question.answerable_by?(user)
end
can :destroy, Poll::Answer do |answer|
answer.author == user && answer.question.answerable_by?(user)
end
end
can [:create, :show], ProposalNotification, proposal: { author_id: user.id }

View File

@@ -17,32 +17,13 @@ class Poll::Answer < ApplicationRecord
scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :by_question, ->(question_id) { where(question_id: question_id) }
def save_and_record_voter_participation
author.with_lock do
save!
Poll::Voter.find_or_create_by!(user: author, poll: poll, origin: "web")
end
end
def destroy_and_remove_voter_participation
transaction do
destroy!
if author.poll_answers.where(question_id: poll.question_ids).none?
Poll::Voter.find_by(user: author, poll: poll, origin: "web").destroy!
end
end
end
private
def max_votes
return if !question || !author || persisted?
author.with_lock do
if question.answers.by_author(author).count >= question.max_votes
errors.add(:answer, "Maximum number of votes per user exceeded")
end
if question.answers.by_author(author).count >= question.max_votes
errors.add(:answer, "Maximum number of votes per user exceeded")
end
end
end

View File

@@ -41,8 +41,6 @@ class Poll::Question < ApplicationRecord
end
end
delegate :answerable_by?, to: :poll
def options_total_votes
question_options.reduce(0) { |total, question_option| total + question_option.total_votes }
end

View File

@@ -0,0 +1,43 @@
class Poll::WebVote
attr_reader :poll, :user
def initialize(poll, user)
@poll = poll
@user = user
end
def questions
poll.questions.for_render.sort_for_list
end
def update(params)
all_valid = true
user.with_lock do
unless questions.any? { |question| params.dig(question.id.to_s, :option_id).present? }
Poll::Voter.find_by(user: user, poll: poll, origin: "web")&.destroy!
end
questions.each do |question|
question.answers.where(author: user).destroy_all
next unless params[question.id.to_s]
option_ids = params[question.id.to_s][:option_id]
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")
answers.each(&:save!)
else
all_valid = false
raise ActiveRecord::Rollback
end
end
end
all_valid
end
end

View File

@@ -1 +0,0 @@
$("#<%= dom_id(@question) %>_options").html("<%= j render Polls::Questions::OptionsComponent.new(@question) %>");

View File

@@ -31,7 +31,7 @@
<% end %>
<% end %>
<%= render Polls::FormComponent.new(@questions) %>
<%= render Polls::FormComponent.new(@web_vote) %>
</div>
</div>
@@ -46,7 +46,7 @@
<div id="poll_more_info_options" class="expanded poll-more-info-options">
<div class="row padding">
<%= render Polls::Questions::ReadMoreComponent.with_collection(@questions) %>
<%= render Polls::Questions::ReadMoreComponent.with_collection(@web_vote.questions) %>
</div>
</div>

View File

@@ -577,6 +577,8 @@ en:
polls:
dates: "From %{open_at} to %{closed_at}"
final_date: "Final recounts/Results"
form:
vote: "Vote"
index:
filters:
current: "Open"
@@ -634,11 +636,7 @@ en:
poll_header:
back_to_proposal: Back to proposal
poll_questions:
show:
vote_answer: "Vote %{answer}"
voted: "You have voted %{answer}"
description:
unique: "You can select a maximum of 1 answer."
multiple: "You can select a maximum of %{maximum} answers."
read_more_about: "Read more about:"
proposal_notifications:

View File

@@ -10,6 +10,7 @@ en:
poll_question_option: "Answer created successfully"
poll_question_option_video: "Video created successfully"
poll_question_option_image: "Image uploaded successfully"
poll_voter: "Thank you for voting!"
proposal: "Proposal created successfully."
proposal_notification: "Your message has been sent correctly."
budget_investment: "Budget Investment created successfully."

View File

@@ -577,6 +577,8 @@ es:
polls:
dates: "Desde el %{open_at} hasta el %{closed_at}"
final_date: "Recuento final/Resultados"
form:
vote: "Votar"
index:
filters:
current: "Abiertas"
@@ -634,11 +636,7 @@ es:
poll_header:
back_to_proposal: Volver a la propuesta
poll_questions:
show:
vote_answer: "Votar %{answer}"
voted: "Has votado %{answer}"
description:
unique: "Puedes seleccionar un máximo de 1 respuesta."
multiple: "Puedes seleccionar un máximo de %{maximum} respuestas."
read_more_about: "Leer más:"
proposal_notifications:

View File

@@ -10,6 +10,7 @@ es:
poll_question_option: "Respuesta creada correctamente"
poll_question_option_video: "Vídeo creado correctamente"
poll_question_option_image: "Imagen cargada correctamente"
poll_voter: "¡Gracias por votar!"
proposal: "Propuesta creada correctamente."
proposal_notification: "Tu mensaje ha sido enviado correctamente."
budget_investment: "Proyecto de gasto creado correctamente."

View File

@@ -2,9 +2,6 @@ resources :polls, only: [:show, :index] do
member do
get :stats
get :results
end
resources :questions, controller: "polls/questions", shallow: true, only: [] do
resources :answers, controller: "polls/answers", only: [:create, :destroy], shallow: false
post :answer
end
end

View File

@@ -0,0 +1,70 @@
require "rails_helper"
describe Polls::FormComponent do
let(:user) { create(:user, :level_two) }
let(:poll) { create(:poll) }
let(:web_vote) { Poll::WebVote.new(poll, user) }
before { create(:poll_question, :yes_no, poll: poll) }
it "renders disabled fields when the user has already voted in a booth" do
create(:poll_voter, :from_booth, poll: poll, user: user)
sign_in(user)
render_inline Polls::FormComponent.new(web_vote)
page.find("fieldset[disabled]") do |fieldset|
expect(fieldset).to have_field "Yes"
expect(fieldset).to have_field "No"
end
expect(page).to have_button "Vote", disabled: true
end
context "expired poll" do
let(:poll) { create(:poll, :expired) }
it "renders disabled fields when the poll has expired" do
sign_in(user)
render_inline Polls::FormComponent.new(web_vote)
page.find("fieldset[disabled]") do |fieldset|
expect(fieldset).to have_field "Yes"
expect(fieldset).to have_field "No"
end
expect(page).to have_button "Vote", disabled: true
end
end
context "geozone restricted poll" do
let(:poll) { create(:poll, geozone_restricted: true) }
let(:geozone) { create(:geozone) }
it "renders disabled fields for users from another geozone" do
poll.geozones << geozone
sign_in(user)
render_inline Polls::FormComponent.new(web_vote)
page.find("fieldset[disabled]") do |fieldset|
expect(fieldset).to have_field "Yes"
expect(fieldset).to have_field "No"
end
expect(page).to have_button "Vote", disabled: true
end
it "renders enabled fields for same-geozone users" do
poll.geozones << geozone
sign_in(create(:user, :level_two, geozone: geozone))
render_inline Polls::FormComponent.new(web_vote)
expect(page).not_to have_css "fieldset[disabled]"
expect(page).to have_field "Yes"
expect(page).to have_field "No"
expect(page).to have_button "Vote"
end
end
end

View File

@@ -1,112 +0,0 @@
require "rails_helper"
describe Polls::Questions::OptionsComponent do
include Rails.application.routes.url_helpers
let(:poll) { create(:poll) }
let(:question) { create(:poll_question, :yes_no, poll: poll) }
it "renders answers in given order" do
render_inline Polls::Questions::OptionsComponent.new(question)
expect("Yes").to appear_before("No")
end
it "renders buttons to vote question answers" do
sign_in(create(:user, :verified))
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_button "Yes"
expect(page).to have_button "No"
expect(page).to have_css "button[aria-pressed='false']", count: 2
end
it "renders button to destroy current user answers" do
user = create(:user, :verified)
create(:poll_answer, author: user, question: question, answer: "Yes")
sign_in(user)
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_button "You have voted Yes"
expect(page).to have_button "Vote No"
expect(page).to have_css "button[aria-pressed='true']", text: "Yes"
end
it "renders disabled buttons when max votes is reached" do
user = create(:user, :verified)
question = create(:poll_question_multiple, :abc, max_votes: 2, author: user)
create(:poll_answer, author: user, question: question, answer: "Answer A")
create(:poll_answer, author: user, question: question, answer: "Answer C")
sign_in(user)
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_button "You have voted Answer A"
expect(page).to have_button "Vote Answer B", disabled: true
expect(page).to have_button "You have voted Answer C"
end
it "when user is not signed in, renders answers links pointing to user sign in path" do
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_link "Yes", href: new_user_session_path
expect(page).to have_link "No", href: new_user_session_path
end
it "when user is not verified, renders answers links pointing to user verification in path" do
sign_in(create(:user))
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_link "Yes", href: verification_path
expect(page).to have_link "No", href: verification_path
end
it "when user already voted in booth it renders disabled answers" do
user = create(:user, :level_two)
create(:poll_voter, :from_booth, poll: poll, user: user)
sign_in(user)
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_css "span.disabled", text: "Yes"
expect(page).to have_css "span.disabled", text: "No"
end
it "user cannot vote when poll expired it renders disabled answers" do
question = create(:poll_question, :yes_no, poll: create(:poll, :expired))
sign_in(create(:user, :level_two))
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_css "span.disabled", text: "Yes"
expect(page).to have_css "span.disabled", text: "No"
end
describe "geozone" do
let(:poll) { create(:poll, geozone_restricted: true) }
let(:geozone) { create(:geozone) }
let(:question) { create(:poll_question, :yes_no, poll: poll) }
it "when geozone which is not theirs it renders disabled answers" do
poll.geozones << geozone
sign_in(create(:user, :level_two))
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_css "span.disabled", text: "Yes"
expect(page).to have_css "span.disabled", text: "No"
end
it "reading a same-geozone poll it renders buttons to vote question answers" do
poll.geozones << geozone
sign_in(create(:user, :level_two, geozone: geozone))
render_inline Polls::Questions::OptionsComponent.new(question)
expect(page).to have_button "Yes"
expect(page).to have_button "No"
end
end
end

View File

@@ -1,18 +1,83 @@
require "rails_helper"
describe Polls::Questions::QuestionComponent do
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") }
it "renders more information links when any question option has additional information" do
question = create(:poll_question)
option_a = create(:poll_question_option, question: question, title: "Answer A")
option_b = create(:poll_question_option, question: question, title: "Answer B")
allow_any_instance_of(Poll::Question::Option).to receive(:with_read_more?).and_return(true)
render_inline Polls::Questions::QuestionComponent.new(question)
poll_question = page.find("#poll_question_#{question.id}")
expect(poll_question).to have_content("Read more about")
expect(poll_question).to have_link("Answer A", href: "#option_#{option_a.id}")
expect(poll_question).to have_link("Answer B", href: "#option_#{option_b.id}")
expect(poll_question).to have_content("Answer A, Answer B")
page.find("#poll_question_#{question.id}") do |poll_question|
expect(poll_question).to have_content "Read more about"
expect(poll_question).to have_link "Yes", href: "#option_#{option_yes.id}"
expect(poll_question).to have_link "No", href: "#option_#{option_no.id}"
expect(poll_question).to have_content "Yes, No"
end
end
it "renders answers in given order" do
render_inline Polls::Questions::QuestionComponent.new(question)
expect("Yes").to appear_before("No")
end
it "renders disabled answers when given the disabled parameter" do
render_inline Polls::Questions::QuestionComponent.new(question, disabled: true)
page.find("fieldset[disabled]") do |fieldset|
expect(fieldset).to have_field "Yes"
expect(fieldset).to have_field "No"
end
end
skip "disables fields when maximum votes has been reached" do # TODO: requires JavaScript
user = create(:user, :verified)
question = create(:poll_question_multiple, :abc, max_votes: 2, author: user)
create(:poll_answer, author: user, question: question, answer: "Answer A")
create(:poll_answer, author: user, question: question, answer: "Answer C")
sign_in(user)
render_inline Polls::Questions::QuestionComponent.new(question)
expect(page).to have_field "Answer A", type: :checkbox, checked: true
expect(page).to have_field "Answer B", type: :checkbox, checked: false
expect(page).to have_field "Answer C", type: :checkbox, checked: true
end
context "Verified user" do
let(:user) { create(:user, :level_two) }
before { sign_in(user) }
it "renders radio buttons for single-choice questions" do
render_inline Polls::Questions::QuestionComponent.new(question)
expect(page).to have_field "Yes", type: :radio
expect(page).to have_field "No", type: :radio
expect(page).to have_field type: :radio, checked: false, count: 2
end
it "renders checkboxes for multiple-choice questions" do
render_inline Polls::Questions::QuestionComponent.new(create(:poll_question_multiple, :abc))
expect(page).to have_field "Answer A", type: :checkbox
expect(page).to have_field "Answer B", type: :checkbox
expect(page).to have_field "Answer C", type: :checkbox
expect(page).to have_field type: :checkbox, checked: false, count: 3
expect(page).not_to have_field type: :checkbox, checked: true
end
it "selects the option when users have already voted" do
create(:poll_answer, author: user, question: question, option: option_yes)
render_inline Polls::Questions::QuestionComponent.new(question)
expect(page).to have_field "Yes", type: :radio, checked: true
expect(page).to have_field "No", type: :radio, checked: false
end
end
end

View File

@@ -1,23 +0,0 @@
require "rails_helper"
describe Polls::AnswersController do
describe "POST create" do
it "doesn't create duplicate records on simultaneous requests", :race_condition do
question = create(:poll_question_multiple, :abc)
sign_in(create(:user, :level_two))
2.times.map do
Thread.new do
post :create, params: {
question_id: question.id,
option_id: question.question_options.find_by(title: "Answer A").id,
format: :js
}
rescue ActiveRecord::RecordInvalid
end
end.each(&:join)
expect(Poll::Answer.count).to eq 1
end
end
end

View File

@@ -8,4 +8,25 @@ describe PollsController do
expect { get :index }.to raise_exception(FeatureFlags::FeatureDisabled)
end
end
describe "POST answer" do
it "doesn't create duplicate records on simultaneous requests", :race_condition do
question = create(:poll_question_multiple, :abc)
sign_in(create(:user, :level_two))
2.times.map do
Thread.new do
post :answer, params: {
id: question.poll.id,
web_vote: {
question.id.to_s => { option_id: question.question_options.find_by(title: "Answer A").id }
}
}
rescue AbstractController::DoubleRenderError
end
end.each(&:join)
expect(Poll::Answer.count).to eq 1
end
end
end

View File

@@ -51,18 +51,6 @@ describe Abilities::Common do
let(:poll_from_own_geozone) { create(:poll, geozone_restricted_to: [geozone]) }
let(:poll_from_other_geozone) { create(:poll, geozone_restricted_to: [create(:geozone)]) }
let(:poll_question_from_own_geozone) { create(:poll_question, poll: poll_from_own_geozone) }
let(:poll_question_from_other_geozone) { create(:poll_question, poll: poll_from_other_geozone) }
let(:poll_question_from_all_geozones) { create(:poll_question, poll: poll) }
let(:expired_poll_question_from_own_geozone) do
create(:poll_question, poll: expired_poll_from_own_geozone)
end
let(:expired_poll_question_from_other_geozone) do
create(:poll_question, poll: expired_poll_from_other_geozone)
end
let(:expired_poll_question_from_all_geozones) { create(:poll_question, poll: expired_poll) }
let(:own_proposal_document) { build(:document, documentable: own_proposal) }
let(:proposal_document) { build(:document, documentable: proposal) }
let(:own_budget_investment_document) { build(:document, documentable: own_investment_in_accepting_budget) }
@@ -252,39 +240,25 @@ describe Abilities::Common do
end
describe "Poll" do
it { should be_able_to(:answer, current_poll) }
it { should_not be_able_to(:answer, expired_poll) }
it { should be_able_to(:answer, current_poll) }
it { should_not be_able_to(:answer, expired_poll) }
it { should be_able_to(:answer, poll_question_from_own_geozone) }
it { should be_able_to(:answer, poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, poll_question_from_other_geozone) }
it { should be_able_to(:answer, poll_from_own_geozone) }
it { should be_able_to(:answer, poll) }
it { should_not be_able_to(:answer, poll_from_other_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) }
context "Poll::Answer" do
let(:own_answer) { create(:poll_answer, author: user) }
let(:other_user_answer) { create(:poll_answer) }
let(:expired_poll) { create(:poll, :expired) }
let(:question) { create(:poll_question, :yes_no, poll: expired_poll) }
let(:expired_poll_answer) { create(:poll_answer, author: user, question: question, answer: "Yes") }
it { should be_able_to(:destroy, own_answer) }
it { should_not be_able_to(:destroy, other_user_answer) }
it { should_not be_able_to(:destroy, expired_poll_answer) }
end
it { should_not be_able_to(:answer, expired_poll_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll_from_other_geozone) }
context "without geozone" do
before { user.geozone = nil }
it { should_not be_able_to(:answer, poll_question_from_own_geozone) }
it { should be_able_to(:answer, poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, poll_question_from_other_geozone) }
it { should_not be_able_to(:answer, poll_from_own_geozone) }
it { should be_able_to(:answer, poll) }
it { should_not be_able_to(:answer, poll_from_other_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) }
it { should_not be_able_to(:answer, expired_poll_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll_from_other_geozone) }
end
end
@@ -339,26 +313,25 @@ describe Abilities::Common do
it { should be_able_to(:show, own_direct_message) }
it { should_not be_able_to(:show, create(:direct_message)) }
it { should be_able_to(:answer, current_poll) }
it { should_not be_able_to(:answer, expired_poll) }
it { should be_able_to(:answer, current_poll) }
it { should_not be_able_to(:answer, expired_poll) }
it { should be_able_to(:answer, poll_question_from_own_geozone) }
it { should be_able_to(:answer, poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, poll_question_from_other_geozone) }
it { should be_able_to(:answer, poll_from_own_geozone) }
it { should be_able_to(:answer, poll) }
it { should_not be_able_to(:answer, poll_from_other_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) }
it { should_not be_able_to(:answer, expired_poll_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll_from_other_geozone) }
context "without geozone" do
before { user.geozone = nil }
it { should_not be_able_to(:answer, poll_question_from_own_geozone) }
it { should be_able_to(:answer, poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, poll_question_from_other_geozone) }
it { should_not be_able_to(:answer, poll_from_own_geozone) }
it { should be_able_to(:answer, poll) }
it { should_not be_able_to(:answer, poll_from_other_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll_question_from_all_geozones) }
it { should_not be_able_to(:answer, expired_poll_question_from_other_geozone) }
it { should_not be_able_to(:answer, expired_poll_from_own_geozone) }
it { should_not be_able_to(:answer, expired_poll) }
it { should_not be_able_to(:answer, expired_poll_from_other_geozone) }
end
end

View File

@@ -101,136 +101,5 @@ describe Poll::Answer do
expect(build(:poll_answer, question: question, answer: "Four")).not_to be_valid
end
context "creating answers at the same time", :race_condition do
it "validates max votes on single-answer questions" do
author = create(:user)
question = create(:poll_question, :yes_no)
answer = build(:poll_answer, author: author, question: question, answer: "Yes")
other_answer = build(:poll_answer, author: author, question: question, answer: "No")
[answer, other_answer].map do |poll_answer|
Thread.new { poll_answer.save }
end.each(&:join)
expect(Poll::Answer.count).to be 1
end
it "validates max votes on multiple-answer questions" do
author = create(:user, :level_two)
question = create(:poll_question_multiple, :abc, max_votes: 2)
create(:poll_answer, question: question, answer: "Answer A", author: author)
answer = build(:poll_answer, question: question, answer: "Answer B", author: author)
other_answer = build(:poll_answer, question: question, answer: "Answer C", author: author)
[answer, other_answer].map do |poll_answer|
Thread.new { poll_answer.save }
end.each(&:join)
expect(Poll::Answer.count).to be 2
end
end
end
describe "#save_and_record_voter_participation" do
let(:author) { create(:user, :level_two) }
let(:poll) { create(:poll) }
let(:question) { create(:poll_question, :yes_no, poll: poll) }
it "creates a poll_voter with user and poll data" do
answer = create(:poll_answer, question: question, author: author, answer: "Yes")
expect(answer.poll.voters).to be_blank
answer.save_and_record_voter_participation
expect(poll.reload.voters.size).to eq(1)
voter = poll.voters.first
expect(voter.document_number).to eq(answer.author.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
answer = create(:poll_answer, question: question, author: author, answer: "Yes")
answer.save_and_record_voter_participation
expect(poll.reload.voters.size).to eq(1)
updated_answer = answer.question.find_or_initialize_user_answer(
answer.author,
answer.question.question_options.excluding(answer.option).sample.id
)
updated_answer.save_and_record_voter_participation
expect(poll.reload.voters.size).to eq(1)
voter = poll.voters.first
expect(voter.document_number).to eq(updated_answer.author.document_number)
expect(voter.poll_id).to eq(updated_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)
answer = build(:poll_answer)
expect do
answer.save_and_record_voter_participation
end.to raise_error(ActiveRecord::RecordInvalid)
expect(answer).not_to be_persisted
end
it "does not create two voters when creating two answers at the same time", :race_condition do
answer = build(:poll_answer, question: question, author: author, answer: "Yes")
other_answer = build(:poll_answer, question: question, author: author, answer: "No")
[answer, other_answer].map do |poll_answer|
Thread.new do
poll_answer.save_and_record_voter_participation
rescue ActiveRecord::RecordInvalid
end
end.each(&:join)
expect(Poll::Voter.count).to be 1
end
it "does not create two voters when calling the method twice at the same time", :race_condition do
answer = create(:poll_answer, question: question, author: author, answer: "Yes")
2.times.map do
Thread.new { answer.save_and_record_voter_participation }
end.each(&:join)
expect(Poll::Voter.count).to be 1
end
end
describe "#destroy_and_remove_voter_participation" do
let(:poll) { create(:poll) }
let(:question) { create(:poll_question, :yes_no, poll: poll) }
it "destroys voter record and answer when it was the only user's answer" do
answer = build(:poll_answer, question: question)
answer.save_and_record_voter_participation
expect { answer.destroy_and_remove_voter_participation }
.to change { Poll::Answer.count }.by(-1)
.and change { Poll::Voter.count }.by(-1)
end
it "destroys the answer but does not destroy the voter record when the user
has answered other poll questions" do
answer = build(:poll_answer, question: question)
answer.save_and_record_voter_participation
other_question = create(:poll_question, :yes_no, poll: poll)
other_answer = build(:poll_answer, question: other_question, author: answer.author)
other_answer.save_and_record_voter_participation
expect(other_answer).to be_persisted
expect { answer.destroy_and_remove_voter_participation }
.to change { Poll::Answer.count }.by(-1)
.and change { Poll::Voter.count }.by(0)
end
end
end

View File

@@ -0,0 +1,110 @@
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

View File

@@ -1,13 +1,14 @@
module Polls
def vote_for_poll_via_web(poll, question, option)
def vote_for_poll_via_web(poll, questions_with_options)
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
click_button option
expect(page).to have_button("You have voted #{option}")
expect(page).not_to have_button("Vote #{option}")
questions_with_options.each do |question, option|
within_fieldset(question.title) { choose option }
end
click_button "Vote"
expect(page).to have_content "Thank you for voting!"
end
def vote_for_poll_via_booth

View File

@@ -96,7 +96,7 @@ describe "Polls" do
expect(page).not_to have_css ".already-answer"
vote_for_poll_via_web(poll_with_question, question, "Yes")
vote_for_poll_via_web(poll_with_question, question => "Yes")
visit polls_path
@@ -199,41 +199,82 @@ describe "Polls" do
scenario "Level 2 users answering" do
poll.update!(geozone_restricted_to: [geozone])
create(:poll_question, :yes_no, poll: poll, title: "Do you agree?")
question = create(:poll_question, :yes_no, poll: poll)
user = create(:user, :level_two, geozone: geozone)
login_as user
login_as(create(:user, :level_two, geozone: geozone))
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
click_button "Vote Yes"
within_fieldset("Do you agree?") { choose "Yes" }
click_button "Vote"
expect(page).to have_button "You have voted Yes"
expect(page).to have_button "Vote No"
expect(page).to have_content "Thank you for voting!"
expect(page).to have_content "You have already participated in this poll. " \
"If you vote again it will be overwritten."
within_fieldset("Do you agree?") do
expect(page).to have_field "Yes", type: :radio, checked: true
end
expect(page).to have_button "Vote"
end
scenario "Level 2 users changing answer" do
poll.update!(geozone_restricted_to: [geozone])
question = create(:poll_question, :yes_no, poll: poll)
user = create(:user, :level_two, geozone: geozone)
question = create(:poll_question, :yes_no, poll: poll, title: "Do you agree?")
poll.update!(geozone_restricted_to: [geozone])
create(:poll_answer, author: user, question: question, answer: "Yes")
create(:poll_voter, poll: poll, user: user)
login_as user
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
click_button "Yes"
expect(page).to have_content "You have already participated in this poll. " \
"If you vote again it will be overwritten."
expect(page).to have_button "You have voted Yes"
expect(page).to have_button "Vote No"
within_fieldset("Do you agree?") do
expect(page).to have_field "Yes", type: :radio, checked: true
click_button "No"
expect(page).to have_button "Vote Yes"
expect(page).to have_button "You have voted No"
choose "No"
end
click_button "Vote"
expect(page).to have_content "Thank you for voting!"
within_fieldset("Do you agree?") do
expect(page).to have_field "No", type: :radio, checked: true
expect(page).to have_field "Yes", type: :radio, checked: false
end
expect(page).to have_button "Vote"
end
scenario "Level 2 users deleting their answer" do
user = create(:user, :level_two, geozone: geozone)
question = create(:poll_question_multiple, :abc, poll: poll, title: "Which ones are better?")
create(:poll_answer, author: user, question: question, answer: "Answer A")
create(:poll_voter, poll: poll, user: user)
login_as user
visit poll_path(poll)
expect(page).to have_content "You have already participated in this poll. " \
"If you vote again it will be overwritten."
within_fieldset("Which ones are better?") { uncheck "Answer A" }
click_button "Vote"
expect(page).to have_content "Thank you for voting!"
expect(page).not_to have_content "You have already participated"
within_fieldset("Which ones are better?") do
expect(page).to have_field type: :checkbox, checked: false, count: 3
expect(page).not_to have_field type: :checkbox, checked: true
end
expect(page).to have_button "Vote"
end
scenario "Shows SDG tags when feature is enabled" do
@@ -259,20 +300,6 @@ describe "Polls" do
expect("Not restricted").to appear_before("Geozone Poll")
expect("Geozone Poll").to appear_before("A Poll")
end
scenario "Level 2 users answering in a browser without javascript", :no_js do
question = create(:poll_question, :yes_no, poll: poll)
user = create(:user, :level_two)
login_as user
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
click_button "Yes"
expect(page).to have_button "You have voted Yes"
expect(page).to have_button "No"
end
end
end
context "Booth & Website", :with_frozen_time do
@@ -283,7 +310,7 @@ describe "Polls" do
scenario "Already voted on booth cannot vote on website" do
create(:poll_shift, officer: officer, booth: booth, date: Date.current, task: :vote_collection)
create(:poll_officer_assignment, officer: officer, poll: poll, booth: booth, date: Date.current)
question = create(:poll_question, :yes_no, poll: poll)
create(:poll_question, :yes_no, poll: poll, title: "Have you voted using a booth?")
user = create(:user, :level_two, :in_census)
login_as(officer.user)
@@ -304,12 +331,9 @@ describe "Polls" do
expect(page).to have_content "You have already participated in a physical booth. " \
"You can not participate again."
within("#poll_question_#{question.id}_options") do
expect(page).to have_content("Yes")
expect(page).to have_content("No")
expect(page).not_to have_button "Yes"
expect(page).not_to have_button "No"
within_fieldset("Have you voted using a booth?") do
expect(page).to have_field "Yes", type: :radio, disabled: true
expect(page).to have_field "No", type: :radio, disabled: true
end
end
end

View File

@@ -17,18 +17,15 @@ describe "Poll Results" do
option5 = create(:poll_question_option, question: question2, title: "Yellow")
login_as user1
vote_for_poll_via_web(poll, question1, "Yes")
vote_for_poll_via_web(poll, question2, "Blue")
vote_for_poll_via_web(poll, question1 => "Yes", question2 => "Blue")
logout
login_as user2
vote_for_poll_via_web(poll, question1, "Yes")
vote_for_poll_via_web(poll, question2, "Green")
vote_for_poll_via_web(poll, question1 => "Yes", question2 => "Green")
logout
login_as user3
vote_for_poll_via_web(poll, question1, "No")
vote_for_poll_via_web(poll, question2, "Yellow")
vote_for_poll_via_web(poll, question1 => "No", question2 => "Yellow")
logout
travel_to(poll.ends_at + 1.day)

View File

@@ -7,63 +7,38 @@ describe "Poll Votation Type" do
login_as(author)
end
scenario "Unique answer" do
question = create(:poll_question_unique, :yes_no)
scenario "Unique and multiple answers" do
poll = create(:poll)
create(:poll_question_unique, :yes_no, poll: poll, title: "Is it that bad?")
create(:poll_question_multiple, :abcde, poll: poll, max_votes: 3, title: "Which ones do you prefer?")
visit poll_path(question.poll)
visit poll_path(poll)
expect(page).to have_content "You can select a maximum of 1 answer."
expect(page).to have_content(question.title)
expect(page).to have_button("Vote Yes")
expect(page).to have_button("Vote No")
within_fieldset("Is it that bad?") { choose "Yes" }
within "#poll_question_#{question.id}_options" do
click_button "Yes"
expect(page).to have_button("You have voted Yes")
expect(page).to have_button("Vote No")
click_button "No"
expect(page).to have_button("Vote Yes")
expect(page).to have_button("You have voted No")
within_fieldset("Which ones do you prefer?") do
check "Answer A"
check "Answer C"
end
end
scenario "Multiple answers" do
question = create(:poll_question_multiple, :abc, max_votes: 2)
visit poll_path(question.poll)
click_button "Vote"
expect(page).to have_content "You can select a maximum of 2 answers."
expect(page).to have_content(question.title)
expect(page).to have_button("Vote Answer A")
expect(page).to have_button("Vote Answer B")
expect(page).to have_button("Vote Answer C")
expect(page).to have_content "Thank you for voting!"
expect(page).to have_content "You have already participated in this poll. " \
"If you vote again it will be overwritten."
within "#poll_question_#{question.id}_options" do
click_button "Vote Answer A"
expect(page).to have_button("You have voted Answer A")
click_button "Vote Answer C"
expect(page).to have_button("You have voted Answer C")
expect(page).to have_button("Vote Answer B", disabled: true)
click_button "You have voted Answer A"
expect(page).to have_button("Vote Answer A")
expect(page).to have_button("Vote Answer B")
click_button "You have voted Answer C"
expect(page).to have_button("Vote Answer C")
click_button "Vote Answer B"
expect(page).to have_button("You have voted Answer B")
expect(page).to have_button("Vote Answer A")
expect(page).to have_button("Vote Answer C")
within_fieldset("Is it that bad?") do
expect(page).to have_field "Yes", type: :radio, checked: true
end
within_fieldset("Which ones do you prefer?") do
expect(page).to have_field "Answer A", type: :checkbox, checked: true
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 D", type: :checkbox, checked: false
expect(page).to have_field "Answer E", type: :checkbox, checked: false
end
expect(page).to have_button "Vote"
end
end

View File

@@ -3,7 +3,7 @@ require "rails_helper"
describe "Voter" do
context "Origin", :with_frozen_time do
let(:poll) { create(:poll) }
let!(:question) { create(:poll_question, :yes_no, poll: poll) }
let!(:question) { create(:poll_question, :yes_no, poll: poll, title: "Is this question stupid?") }
let(:booth) { create(:poll_booth) }
let(:officer) { create(:poll_officer) }
let(:admin) { create(:administrator) }
@@ -20,41 +20,12 @@ describe "Voter" do
login_as user
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
click_button "Vote Yes"
within_fieldset("Is this question stupid?") { choose "Yes" }
click_button "Vote"
expect(page).to have_button("You have voted Yes")
expect(page).not_to have_button("Vote Yes")
end
refresh
expect(page).to have_content("You have already participated in this poll.")
expect(page).to have_content("If you vote again it will be overwritten")
end
scenario "Remove vote via web - Standard" do
user = create(:user, :level_two)
create(:poll_answer, question: question, author: user, answer: "Yes")
create(:poll_voter, poll: poll, user: user)
login_as user
visit poll_path(poll)
expect(page).to have_content("You have already participated in this poll.")
expect(page).to have_content("If you vote again it will be overwritten")
within("#poll_question_#{question.id}_options") do
click_button "You have voted Yes"
expect(page).to have_button("Vote Yes")
expect(page).to have_button("Vote No")
end
refresh
expect(page).not_to have_content("You have already participated in this poll.")
expect(page).not_to have_content("If you vote again it will be overwritten")
expect(page).to have_content "Thank you for voting!"
expect(page).to have_content "You have already participated in this poll. " \
"If you vote again it will be overwritten."
end
scenario "Voting via web as unverified user" do
@@ -63,9 +34,9 @@ describe "Voter" do
login_as user
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
expect(page).to have_link("Yes", href: verification_path)
expect(page).to have_link("No", href: verification_path)
within_fieldset "Is this question stupid?" do
expect(page).to have_field "Yes", type: :radio, disabled: true
expect(page).to have_field "No", type: :radio, disabled: true
end
expect(page).to have_content "You must verify your account in order to answer"
@@ -142,7 +113,7 @@ describe "Voter" do
scenario "Trying to vote in web and then in booth" do
login_as user
vote_for_poll_via_web(poll, question, "Yes")
vote_for_poll_via_web(poll, question => "Yes")
logout
login_through_form_as_officer(officer)
@@ -165,8 +136,9 @@ describe "Voter" do
login_as user
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
expect(page).not_to have_button("Yes")
within_fieldset "Is this question stupid?" do
expect(page).to have_field "Yes", type: :radio, disabled: true
expect(page).to have_field "No", type: :radio, disabled: true
end
expect(page).to have_content "You have already participated in a physical booth. " \
"You can not participate again."
@@ -203,8 +175,9 @@ describe "Voter" do
visit poll_path(poll)
within("#poll_question_#{question.id}_options") do
expect(page).not_to have_button("Yes")
within_fieldset "Is this question stupid?" do
expect(page).to have_field "Yes", type: :radio, disabled: true
expect(page).to have_field "No", type: :radio, disabled: true
end
expect(page).to have_content "You have already participated in a physical booth. " \