diff --git a/.rubocop.yml b/.rubocop.yml
index ca3696b9b..6f0d769a4 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -571,6 +571,9 @@ RSpec/BeNil:
Enabled: true
EnforcedStyle: be
+RSpec/ChangeByZero:
+ Enabled: true
+
RSpec/ContextMethod:
Enabled: true
diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss
index 8700b6ef0..09b22bd4f 100644
--- a/app/assets/stylesheets/participation.scss
+++ b/app/assets/stylesheets/participation.scss
@@ -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
// ---------------------------
diff --git a/app/assets/stylesheets/polls/form.scss b/app/assets/stylesheets/polls/form.scss
new file mode 100644
index 000000000..e85879c24
--- /dev/null
+++ b/app/assets/stylesheets/polls/form.scss
@@ -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);
+ }
+}
diff --git a/app/components/polls/form_component.html.erb b/app/components/polls/form_component.html.erb
index 6516b9152..f3d83701b 100644
--- a/app/components/polls/form_component.html.erb
+++ b/app/components/polls/form_component.html.erb
@@ -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 %>
diff --git a/app/components/polls/form_component.rb b/app/components/polls/form_component.rb
index 0a2bc5668..48dafc51f 100644
--- a/app/components/polls/form_component.rb
+++ b/app/components/polls/form_component.rb
@@ -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
diff --git a/app/components/polls/questions/options_component.html.erb b/app/components/polls/questions/options_component.html.erb
deleted file mode 100644
index a183b2aac..000000000
--- a/app/components/polls/questions/options_component.html.erb
+++ /dev/null
@@ -1,35 +0,0 @@
-
- <% 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| %>
- <%= question_option.title %>
- <% end %>
- <% end %>
-
diff --git a/app/components/polls/questions/options_component.rb b/app/components/polls/questions/options_component.rb
deleted file mode 100644
index 25875cd6e..000000000
--- a/app/components/polls/questions/options_component.rb
+++ /dev/null
@@ -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
diff --git a/app/components/polls/questions/question_component.html.erb b/app/components/polls/questions/question_component.html.erb
index 46563a87b..89b90f9bd 100644
--- a/app/components/polls/questions/question_component.html.erb
+++ b/app/components/polls/questions/question_component.html.erb
@@ -1,22 +1,22 @@
-
-
- <%= question.title %>
-
+
+
diff --git a/app/components/polls/questions/question_component.rb b/app/components/polls/questions/question_component.rb
index 627abc6e9..6961ca412 100644
--- a/app/components/polls/questions/question_component.rb
+++ b/app/components/polls/questions/question_component.rb
@@ -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
diff --git a/app/controllers/polls/answers_controller.rb b/app/controllers/polls/answers_controller.rb
deleted file mode 100644
index a969c5edf..000000000
--- a/app/controllers/polls/answers_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb
index 80b72309b..f32332f3c 100644
--- a/app/controllers/polls_controller.rb
+++ b/app/controllers/polls_controller.rb
@@ -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
diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb
index 369630981..429ff7ef7 100644
--- a/app/models/abilities/common.rb
+++ b/app/models/abilities/common.rb
@@ -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 }
diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb
index b9c52fb38..d4b0150e2 100644
--- a/app/models/poll/answer.rb
+++ b/app/models/poll/answer.rb
@@ -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
diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb
index c11d8c654..93e9c4453 100644
--- a/app/models/poll/question.rb
+++ b/app/models/poll/question.rb
@@ -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
diff --git a/app/models/poll/web_vote.rb b/app/models/poll/web_vote.rb
new file mode 100644
index 000000000..2dba50fe5
--- /dev/null
+++ b/app/models/poll/web_vote.rb
@@ -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
diff --git a/app/views/polls/answers/show.js.erb b/app/views/polls/answers/show.js.erb
deleted file mode 100644
index 1cfab79f7..000000000
--- a/app/views/polls/answers/show.js.erb
+++ /dev/null
@@ -1 +0,0 @@
-$("#<%= dom_id(@question) %>_options").html("<%= j render Polls::Questions::OptionsComponent.new(@question) %>");
diff --git a/app/views/polls/show.html.erb b/app/views/polls/show.html.erb
index 808ae2816..79d010a63 100644
--- a/app/views/polls/show.html.erb
+++ b/app/views/polls/show.html.erb
@@ -31,7 +31,7 @@
<% end %>
<% end %>
- <%= render Polls::FormComponent.new(@questions) %>
+ <%= render Polls::FormComponent.new(@web_vote) %>
@@ -46,7 +46,7 @@
- <%= render Polls::Questions::ReadMoreComponent.with_collection(@questions) %>
+ <%= render Polls::Questions::ReadMoreComponent.with_collection(@web_vote.questions) %>
diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml
index 0c0745e00..d48184f08 100644
--- a/config/locales/en/general.yml
+++ b/config/locales/en/general.yml
@@ -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:
diff --git a/config/locales/en/responders.yml b/config/locales/en/responders.yml
index 550a5b9f1..dc5527d48 100644
--- a/config/locales/en/responders.yml
+++ b/config/locales/en/responders.yml
@@ -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."
diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml
index db8622d81..0ee5caf11 100644
--- a/config/locales/es/general.yml
+++ b/config/locales/es/general.yml
@@ -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:
diff --git a/config/locales/es/responders.yml b/config/locales/es/responders.yml
index 49a78fad1..50fddc758 100644
--- a/config/locales/es/responders.yml
+++ b/config/locales/es/responders.yml
@@ -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."
diff --git a/config/routes/poll.rb b/config/routes/poll.rb
index 754c7c4d2..9c44c0f21 100644
--- a/config/routes/poll.rb
+++ b/config/routes/poll.rb
@@ -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
diff --git a/spec/components/polls/form_component_spec.rb b/spec/components/polls/form_component_spec.rb
new file mode 100644
index 000000000..754b7f72e
--- /dev/null
+++ b/spec/components/polls/form_component_spec.rb
@@ -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
diff --git a/spec/components/polls/questions/options_component_spec.rb b/spec/components/polls/questions/options_component_spec.rb
deleted file mode 100644
index c50416cd5..000000000
--- a/spec/components/polls/questions/options_component_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/components/polls/questions/question_component_spec.rb b/spec/components/polls/questions/question_component_spec.rb
index aba98dd94..e03851222 100644
--- a/spec/components/polls/questions/question_component_spec.rb
+++ b/spec/components/polls/questions/question_component_spec.rb
@@ -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
diff --git a/spec/controllers/polls/answers_controller_spec.rb b/spec/controllers/polls/answers_controller_spec.rb
deleted file mode 100644
index a5286da3c..000000000
--- a/spec/controllers/polls/answers_controller_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/controllers/polls_controller_spec.rb b/spec/controllers/polls_controller_spec.rb
index 296c1a0d6..6a0a03c2e 100644
--- a/spec/controllers/polls_controller_spec.rb
+++ b/spec/controllers/polls_controller_spec.rb
@@ -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
diff --git a/spec/models/abilities/common_spec.rb b/spec/models/abilities/common_spec.rb
index f79295d13..3aa184bf7 100644
--- a/spec/models/abilities/common_spec.rb
+++ b/spec/models/abilities/common_spec.rb
@@ -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
diff --git a/spec/models/poll/answer_spec.rb b/spec/models/poll/answer_spec.rb
index 4f3ca154e..aec882fc5 100644
--- a/spec/models/poll/answer_spec.rb
+++ b/spec/models/poll/answer_spec.rb
@@ -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
diff --git a/spec/models/poll/web_vote_spec.rb b/spec/models/poll/web_vote_spec.rb
new file mode 100644
index 000000000..3b4b86eb1
--- /dev/null
+++ b/spec/models/poll/web_vote_spec.rb
@@ -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
diff --git a/spec/support/common_actions/polls.rb b/spec/support/common_actions/polls.rb
index 0340567f6..f6d064f51 100644
--- a/spec/support/common_actions/polls.rb
+++ b/spec/support/common_actions/polls.rb
@@ -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
diff --git a/spec/system/polls/polls_spec.rb b/spec/system/polls/polls_spec.rb
index 0030460c8..49043ccf2 100644
--- a/spec/system/polls/polls_spec.rb
+++ b/spec/system/polls/polls_spec.rb
@@ -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
diff --git a/spec/system/polls/results_spec.rb b/spec/system/polls/results_spec.rb
index 78b45d997..9e269d3ff 100644
--- a/spec/system/polls/results_spec.rb
+++ b/spec/system/polls/results_spec.rb
@@ -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)
diff --git a/spec/system/polls/votation_types_spec.rb b/spec/system/polls/votation_types_spec.rb
index 46d444376..e77ad2d7b 100644
--- a/spec/system/polls/votation_types_spec.rb
+++ b/spec/system/polls/votation_types_spec.rb
@@ -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
diff --git a/spec/system/polls/voter_spec.rb b/spec/system/polls/voter_spec.rb
index 8ff4cfce5..2dd53d315 100644
--- a/spec/system/polls/voter_spec.rb
+++ b/spec/system/polls/voter_spec.rb
@@ -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. " \