Merge pull request #1258 from consul/polling-questions
Polling questions
This commit is contained in:
17
app/controllers/polls/questions_controller.rb
Normal file
17
app/controllers/polls/questions_controller.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
class Polls::QuestionsController < ApplicationController
|
||||
|
||||
load_and_authorize_resource :poll
|
||||
load_and_authorize_resource :question, class: 'Poll::Question', through: :poll
|
||||
|
||||
def answer
|
||||
partial_result = @question.partial_results.find_or_initialize_by(author: current_user,
|
||||
amount: 1,
|
||||
origin: 'web')
|
||||
|
||||
partial_result.answer = params[:answer]
|
||||
partial_result.save!
|
||||
|
||||
@answers_by_question_id = {@question.id => params[:answer]}
|
||||
end
|
||||
|
||||
end
|
||||
22
app/controllers/polls_controller.rb
Normal file
22
app/controllers/polls_controller.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
class PollsController < ApplicationController
|
||||
|
||||
load_and_authorize_resource
|
||||
|
||||
has_filters %w{current expired incoming}
|
||||
|
||||
def index
|
||||
@polls = @polls.send(@current_filter).sort_for_list.page(params[:page])
|
||||
end
|
||||
|
||||
def show
|
||||
@answerable_questions = @poll.questions.answerable_by(current_user).for_render.sort_for_list
|
||||
@non_answerable_questions = @poll.questions.where.not(id: @answerable_questions.map(&:id)).for_render.sort_for_list
|
||||
|
||||
@answers_by_question_id = {}
|
||||
poll_partial_results = Poll::PartialResult.by_question(@poll.question_ids).by_author(current_user.try(:id))
|
||||
poll_partial_results.each do |result|
|
||||
@answers_by_question_id[result.question_id] = result.answer
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -48,6 +48,10 @@ module Abilities
|
||||
can :create, SpendingProposal
|
||||
can :create, DirectMessage
|
||||
can :show, DirectMessage, sender_id: user.id
|
||||
can(:answer, Poll, Poll.answerable_by(user)){ |poll| poll.answerable_by?(user) }
|
||||
can(:answer, Poll::Question, Poll::Question.answerable_by(user)) do |question|
|
||||
question.answerable_by?(user)
|
||||
end
|
||||
end
|
||||
|
||||
can [:create, :show], ProposalNotification, proposal: { author_id: user.id }
|
||||
|
||||
@@ -6,6 +6,7 @@ module Abilities
|
||||
can [:read, :map], Debate
|
||||
can [:read, :map, :summary], Proposal
|
||||
can :read, Comment
|
||||
can :read, Poll
|
||||
can :read, SpendingProposal
|
||||
can :read, Legislation
|
||||
can :read, User
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
class Poll < ActiveRecord::Base
|
||||
has_many :booths
|
||||
has_many :voters, through: :booths, class_name: "Poll::Voter"
|
||||
has_many :questions
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
scope :current, -> { where('starts_at <= ? and ? <= ends_at', Time.now, Time.now) }
|
||||
scope :incoming, -> { where('? < starts_at', Time.now) }
|
||||
scope :expired, -> { where('ends_at < ?', Time.now) }
|
||||
|
||||
scope :sort_for_list, -> { order(:starts_at) }
|
||||
|
||||
def current?(timestamp = DateTime.now)
|
||||
starts_at <= timestamp && timestamp <= ends_at
|
||||
end
|
||||
|
||||
def incoming?(timestamp = DateTime.now)
|
||||
timestamp < starts_at
|
||||
end
|
||||
|
||||
def expired?(timestamp = DateTime.now)
|
||||
ends_at < timestamp
|
||||
end
|
||||
|
||||
def answerable_by?(user)
|
||||
user.present? && user.level_two_or_three_verified? && current?
|
||||
end
|
||||
|
||||
def self.answerable_by(user)
|
||||
return none if user.nil? || user.unverified?
|
||||
current
|
||||
end
|
||||
end
|
||||
18
app/models/poll/partial_result.rb
Normal file
18
app/models/poll/partial_result.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
class Poll::PartialResult < ActiveRecord::Base
|
||||
|
||||
VALID_ORIGINS = %w{ web }
|
||||
|
||||
belongs_to :question, -> { with_hidden }
|
||||
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
|
||||
|
||||
validates :question, presence: true
|
||||
validates :author, presence: true
|
||||
validates :answer, presence: true
|
||||
validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }}
|
||||
validates :origin, inclusion: {in: VALID_ORIGINS}
|
||||
|
||||
scope :by_author, -> (author_id) { where(author_id: author_id) }
|
||||
scope :by_question, -> (question_id) { where(question_id: question_id) }
|
||||
|
||||
|
||||
end
|
||||
66
app/models/poll/question.rb
Normal file
66
app/models/poll/question.rb
Normal file
@@ -0,0 +1,66 @@
|
||||
class Poll::Question < ActiveRecord::Base
|
||||
include Measurable
|
||||
|
||||
acts_as_paranoid column: :hidden_at
|
||||
include ActsAsParanoidAliases
|
||||
|
||||
belongs_to :poll
|
||||
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
|
||||
|
||||
has_many :comments, as: :commentable
|
||||
has_many :answers
|
||||
has_many :partial_results
|
||||
has_and_belongs_to_many :geozones
|
||||
belongs_to :proposal
|
||||
|
||||
validates :title, presence: true
|
||||
validates :question, presence: true
|
||||
validates :summary, presence: true
|
||||
validates :author, presence: true
|
||||
|
||||
validates :title, length: { in: 4..Poll::Question.title_max_length }
|
||||
validates :description, length: { maximum: Poll::Question.description_max_length }
|
||||
validates :question, length: { in: 10..Poll::Question.question_max_length }
|
||||
|
||||
scope :sort_for_list, -> { order('poll_questions.proposal_id IS NULL', :created_at)}
|
||||
scope :for_render, -> { includes(:author, :proposal) }
|
||||
scope :by_geozone, -> (geozone_id) { joins(:geozones).where(geozones: {id: geozone_id}) }
|
||||
|
||||
def description
|
||||
super.try :html_safe
|
||||
end
|
||||
|
||||
def valid_answers
|
||||
(super.try(:split, ',').compact || []).map(&:strip)
|
||||
end
|
||||
|
||||
def copy_attributes_from_proposal(proposal)
|
||||
if proposal.present?
|
||||
self.author = proposal.author
|
||||
self.author_visible_name = proposal.author.name
|
||||
self.proposal_id = proposal.id
|
||||
self.title = proposal.title
|
||||
self.description = proposal.description
|
||||
self.summary = proposal.summary
|
||||
self.question = proposal.question
|
||||
self.all_geozones = true
|
||||
self.valid_answers = I18n.t('poll_questions.default_valid_answers')
|
||||
end
|
||||
end
|
||||
|
||||
def answerable_by?(user)
|
||||
poll.answerable_by?(user) && (self.all_geozones || self.geozone_ids.include?(user.geozone_id))
|
||||
end
|
||||
|
||||
def self.answerable_by(user)
|
||||
return none if user.nil? || user.unverified?
|
||||
|
||||
joins('LEFT JOIN "geozones_poll_questions" ON "geozones_poll_questions"."question_id" = "poll_questions"."id"')
|
||||
.where('poll_questions.poll_id IN (?) AND (poll_questions.all_geozones = ? OR geozones_poll_questions.geozone_id = ?)',
|
||||
Poll.answerable_by(user).pluck(:id),
|
||||
true,
|
||||
user.geozone_id || -1) # user.geozone_id can be nil, which would throw errors on sql
|
||||
.group('poll_questions.id')
|
||||
end
|
||||
|
||||
end
|
||||
7
app/views/polls/index.html.erb
Normal file
7
app/views/polls/index.html.erb
Normal file
@@ -0,0 +1,7 @@
|
||||
<%= render 'shared/filter_subnav', i18n_namespace: "polls.index" %>
|
||||
|
||||
<% @polls.each do |poll| %>
|
||||
<%= link_to poll.name, poll %>
|
||||
<% end %>
|
||||
|
||||
<%= paginate @polls %>
|
||||
23
app/views/polls/questions/_answers.html.erb
Normal file
23
app/views/polls/questions/_answers.html.erb
Normal file
@@ -0,0 +1,23 @@
|
||||
<div class="enquiries-answers">
|
||||
<% if can? :answer, question %>
|
||||
<div class="small-12 small-centered text-center column">
|
||||
<% question.valid_answers.each do |answer| %>
|
||||
<% if @answers_by_question_id[question.id] == answer %>
|
||||
<span class="button answered-fixme-decabeza">
|
||||
<%= answer %>
|
||||
</span>
|
||||
<% else %>
|
||||
<%= link_to answer,
|
||||
answer_poll_question_path(poll_id: question.poll_id, id: question.id, answer: answer),
|
||||
method: :post,
|
||||
remote: true,
|
||||
class: "button secondary hollow" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<% question.valid_answers.each do |answer| %>
|
||||
<span class="button deactivated-fixme-decabeza"><%= answer %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
1
app/views/polls/questions/answer.js.erb
Normal file
1
app/views/polls/questions/answer.js.erb
Normal file
@@ -0,0 +1 @@
|
||||
$("#<%= dom_id(@question) %>_answers").html('<%= j render("polls/questions/answers", question: @question) %>');
|
||||
53
app/views/polls/show.html.erb
Normal file
53
app/views/polls/show.html.erb
Normal file
@@ -0,0 +1,53 @@
|
||||
<%= @poll.name %>
|
||||
|
||||
<% unless can?(:answer, @poll) %>
|
||||
<div class="small-12 column">
|
||||
<% if current_user.nil? %>
|
||||
<div class="callout primary">
|
||||
<%= t("polls.show.cant_answer_not_logged_in",
|
||||
signin: link_to(t("polls.show.signin"), new_user_session_path, class: "probe-message"),
|
||||
signup: link_to(t("polls.show.signup"), new_user_registration_path, class: "probe-message")).html_safe %>
|
||||
</div>
|
||||
<% elsif current_user.unverified? %>
|
||||
<div class="callout warning">
|
||||
<%= t('polls.show.cant_answer_verify_html',
|
||||
verify_link: link_to(t('polls.show.verify_link'), verification_path)) %>
|
||||
</div>
|
||||
<% elsif @poll.incoming? %>
|
||||
<div class="callout primary">
|
||||
<%= t('polls.show.cant_answer_incoming') %>
|
||||
</div>
|
||||
<% elsif @poll.expired? %>
|
||||
<div class="callout alert">
|
||||
<%= t('polls.show.cant_answer_expired') %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% @answerable_questions.each do |question| %>
|
||||
<div id="<%= dom_id(question) %>">
|
||||
<%= question.title %>
|
||||
|
||||
<div class="row margin-top text-center" id="<%= dom_id(question) %>_answers">
|
||||
<%= render 'polls/questions/answers', question: question %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if can?(:answer, @poll) &&
|
||||
@non_answerable_questions.present? %>
|
||||
<div class="callout warning">
|
||||
<%= t('polls.show.cant_answer_wrong_geozone') %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% @non_answerable_questions.each do |question| %>
|
||||
<div id="<%= dom_id(question) %>">
|
||||
<%= question.title %>
|
||||
|
||||
<div class="row margin-top text-center" id="<%= dom_id(question) %>_answers">
|
||||
<%= render 'polls/questions/answers', question: question %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -375,6 +375,18 @@ en:
|
||||
update:
|
||||
form:
|
||||
submit_button: Save changes
|
||||
polls:
|
||||
show:
|
||||
cant_answer_not_logged_in: "You must %{signin} or %{signup} to participate."
|
||||
signin: Sign in
|
||||
signup: Sign up
|
||||
cant_answer_verify_html: "You must %{verify_link} in order to answer."
|
||||
verify_link: "verify your account"
|
||||
cant_answer_incoming: "This poll has not yet started."
|
||||
cant_answer_expired: "This poll has finished."
|
||||
cant_answer_wrong_geozone: "The following questions are not available in your geozone."
|
||||
poll_questions:
|
||||
default_valid_answers: "Yes, No"
|
||||
proposal_ballots:
|
||||
title: "Votings"
|
||||
description_html: "The following citizen proposals that have reached the <strong>required supports</strong> and will be voted."
|
||||
|
||||
@@ -85,6 +85,12 @@ Rails.application.routes.draw do
|
||||
get :search, on: :collection
|
||||
end
|
||||
|
||||
resources :polls, only: [:show, :index] do
|
||||
resources :questions, only: [], controller: 'polls/questions' do
|
||||
post :answer, on: :member
|
||||
end
|
||||
end
|
||||
|
||||
resources :users, only: [:show] do
|
||||
resources :direct_messages, only: [:new, :create, :show]
|
||||
end
|
||||
|
||||
21
db/migrate/20161028104156_create_poll_questions.rb
Normal file
21
db/migrate/20161028104156_create_poll_questions.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class CreatePollQuestions < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :poll_questions do |t|
|
||||
t.references :proposal, index: true, foreign_key: true
|
||||
t.references :poll, index: true, foreign_key: true
|
||||
t.references :author, index: true # foreign key added later due to rails 4
|
||||
t.string :author_visible_name
|
||||
t.string :title
|
||||
t.string :question
|
||||
t.string :summary
|
||||
t.string :valid_answers
|
||||
t.text :description
|
||||
t.integer :comments_count
|
||||
t.datetime :hidden_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_foreign_key :poll_questions, :users, column: :author_id
|
||||
end
|
||||
end
|
||||
10
db/migrate/20161028143204_create_geozones_poll_questions.rb
Normal file
10
db/migrate/20161028143204_create_geozones_poll_questions.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class CreateGeozonesPollQuestions < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :geozones_poll_questions do |t|
|
||||
t.references :geozone, index: true, foreign_key: true
|
||||
t.integer :question_id, index: true
|
||||
end
|
||||
|
||||
add_foreign_key :geozones_poll_questions, :poll_questions, column: :question_id
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
class AddAllGeozonesToPollQuestions < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :poll_questions, :all_geozones, :boolean, default: false
|
||||
end
|
||||
end
|
||||
14
db/migrate/20161107174423_create_poll_partial_result.rb
Normal file
14
db/migrate/20161107174423_create_poll_partial_result.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class CreatePollPartialResult < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :poll_partial_results do |t|
|
||||
t.integer :question_id, index: true
|
||||
t.integer :author_id, index: true
|
||||
t.string :answer, index: true
|
||||
t.integer :amount
|
||||
t.string :origin, index: true
|
||||
end
|
||||
|
||||
add_foreign_key(:poll_partial_results, :users, column: :author_id)
|
||||
add_foreign_key(:poll_partial_results, :poll_questions, column: :question_id)
|
||||
end
|
||||
end
|
||||
51
db/schema.rb
51
db/schema.rb
@@ -11,7 +11,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 20161102133838) do
|
||||
ActiveRecord::Schema.define(version: 20161107174423) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
@@ -211,6 +211,14 @@ ActiveRecord::Schema.define(version: 20161102133838) do
|
||||
t.string "census_code"
|
||||
end
|
||||
|
||||
create_table "geozones_poll_questions", force: :cascade do |t|
|
||||
t.integer "geozone_id"
|
||||
t.integer "question_id"
|
||||
end
|
||||
|
||||
add_index "geozones_poll_questions", ["geozone_id"], name: "index_geozones_poll_questions_on_geozone_id", using: :btree
|
||||
add_index "geozones_poll_questions", ["question_id"], name: "index_geozones_poll_questions_on_question_id", using: :btree
|
||||
|
||||
create_table "identities", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.string "provider"
|
||||
@@ -287,6 +295,40 @@ ActiveRecord::Schema.define(version: 20161102133838) do
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "poll_partial_results", force: :cascade do |t|
|
||||
t.integer "question_id"
|
||||
t.integer "author_id"
|
||||
t.string "answer"
|
||||
t.integer "amount"
|
||||
t.string "origin"
|
||||
end
|
||||
|
||||
add_index "poll_partial_results", ["answer"], name: "index_poll_partial_results_on_answer", using: :btree
|
||||
add_index "poll_partial_results", ["author_id"], name: "index_poll_partial_results_on_author_id", using: :btree
|
||||
add_index "poll_partial_results", ["origin"], name: "index_poll_partial_results_on_origin", using: :btree
|
||||
add_index "poll_partial_results", ["question_id"], name: "index_poll_partial_results_on_question_id", using: :btree
|
||||
|
||||
create_table "poll_questions", force: :cascade do |t|
|
||||
t.integer "proposal_id"
|
||||
t.integer "poll_id"
|
||||
t.integer "author_id"
|
||||
t.string "author_visible_name"
|
||||
t.string "title"
|
||||
t.string "question"
|
||||
t.string "summary"
|
||||
t.string "valid_answers"
|
||||
t.text "description"
|
||||
t.integer "comments_count"
|
||||
t.datetime "hidden_at"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "all_geozones", default: false
|
||||
end
|
||||
|
||||
add_index "poll_questions", ["author_id"], name: "index_poll_questions_on_author_id", using: :btree
|
||||
add_index "poll_questions", ["poll_id"], name: "index_poll_questions_on_poll_id", using: :btree
|
||||
add_index "poll_questions", ["proposal_id"], name: "index_poll_questions_on_proposal_id", using: :btree
|
||||
|
||||
create_table "poll_voters", force: :cascade do |t|
|
||||
t.integer "booth_id"
|
||||
t.string "document_number"
|
||||
@@ -583,12 +625,19 @@ ActiveRecord::Schema.define(version: 20161102133838) do
|
||||
add_foreign_key "annotations", "users"
|
||||
add_foreign_key "failed_census_calls", "users"
|
||||
add_foreign_key "flags", "users"
|
||||
add_foreign_key "geozones_poll_questions", "geozones"
|
||||
add_foreign_key "geozones_poll_questions", "poll_questions", column: "question_id"
|
||||
add_foreign_key "identities", "users"
|
||||
add_foreign_key "locks", "users"
|
||||
add_foreign_key "managers", "users"
|
||||
add_foreign_key "moderators", "users"
|
||||
add_foreign_key "notifications", "users"
|
||||
add_foreign_key "organizations", "users"
|
||||
add_foreign_key "poll_partial_results", "poll_questions", column: "question_id"
|
||||
add_foreign_key "poll_partial_results", "users", column: "author_id"
|
||||
add_foreign_key "poll_questions", "polls"
|
||||
add_foreign_key "poll_questions", "proposals"
|
||||
add_foreign_key "poll_questions", "users", column: "author_id"
|
||||
add_foreign_key "users", "geozones"
|
||||
add_foreign_key "valuators", "users"
|
||||
end
|
||||
|
||||
@@ -265,10 +265,21 @@ FactoryGirl.define do
|
||||
|
||||
factory :poll do
|
||||
sequence(:name) { |n| "Poll #{n}" }
|
||||
|
||||
starts_at { 1.month.ago }
|
||||
ends_at { 1.month.from_now }
|
||||
|
||||
trait :incoming do
|
||||
starts_at { 2.days.from_now }
|
||||
ends_at { 1.month.from_now }
|
||||
end
|
||||
|
||||
trait :expired do
|
||||
starts_at { 1.month.ago }
|
||||
ends_at { 15.days.ago }
|
||||
end
|
||||
end
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
factory :poll_officer, class: 'Poll::Officer' do
|
||||
user
|
||||
end
|
||||
@@ -278,7 +289,6 @@ FactoryGirl.define do
|
||||
association :booth, factory: :poll_booth
|
||||
end
|
||||
|
||||
>>>>>>> assigns officers to booths
|
||||
factory :poll_booth, class: 'Poll::Booth' do
|
||||
sequence(:name) { |n| "Booth #{n}" }
|
||||
sequence(:location) { |n| "Street #{n}" }
|
||||
@@ -299,7 +309,23 @@ FactoryGirl.define do
|
||||
end
|
||||
end
|
||||
|
||||
>>>>>>> validates voter in census
|
||||
factory :poll_question, class: 'Poll::Question' do
|
||||
poll
|
||||
association :author, factory: :user
|
||||
sequence(:title) { |n| "Question title #{n}" }
|
||||
sequence(:summary) { |n| "Question summary #{n}" }
|
||||
sequence(:description) { |n| "Question description #{n}" }
|
||||
sequence(:question) { |n| "Question question #{n}" }
|
||||
valid_answers { Faker::Lorem.words(3).join(', ') }
|
||||
end
|
||||
|
||||
factory :poll_partial_result, class: 'Poll::PartialResult' do
|
||||
association :question, factory: :poll_question
|
||||
association :author, factory: :user
|
||||
origin { 'web' }
|
||||
answer { question.verified_answers.sample }
|
||||
end
|
||||
|
||||
factory :organization do
|
||||
user
|
||||
responsible_name "Johnny Utah"
|
||||
|
||||
187
spec/features/polls_spec.rb
Normal file
187
spec/features/polls_spec.rb
Normal file
@@ -0,0 +1,187 @@
|
||||
# coding: utf-8
|
||||
require 'rails_helper'
|
||||
|
||||
feature 'Polls' do
|
||||
|
||||
context '#index' do
|
||||
|
||||
scenario 'Polls can be listed' do
|
||||
polls = create_list(:poll, 3)
|
||||
|
||||
visit polls_path
|
||||
|
||||
polls.each do |poll|
|
||||
expect(page).to have_link(poll.name)
|
||||
end
|
||||
end
|
||||
|
||||
scenario 'Filtering polls' do
|
||||
create(:poll, name: "Current poll")
|
||||
create(:poll, :incoming, name: "Incoming poll")
|
||||
create(:poll, :expired, name: "Expired poll")
|
||||
|
||||
visit polls_path
|
||||
expect(page).to have_content('Current poll')
|
||||
expect(page).to_not have_content('Incoming poll')
|
||||
expect(page).to_not have_content('Expired poll')
|
||||
|
||||
visit polls_path(filter: 'incoming')
|
||||
expect(page).to_not have_content('Current poll')
|
||||
expect(page).to have_content('Incoming poll')
|
||||
expect(page).to_not have_content('Expired poll')
|
||||
|
||||
visit polls_path(filter: 'expired')
|
||||
expect(page).to_not have_content('Current poll')
|
||||
expect(page).to_not have_content('Incoming poll')
|
||||
expect(page).to have_content('Expired poll')
|
||||
end
|
||||
|
||||
scenario "Current filter is properly highlighted" do
|
||||
visit polls_path
|
||||
expect(page).to_not have_link('Current')
|
||||
expect(page).to have_link('Incoming')
|
||||
expect(page).to have_link('Expired')
|
||||
|
||||
visit polls_path(filter: 'incoming')
|
||||
expect(page).to have_link('Current')
|
||||
expect(page).to_not have_link('Incoming')
|
||||
expect(page).to have_link('Expired')
|
||||
|
||||
visit polls_path(filter: 'expired')
|
||||
expect(page).to have_link('Current')
|
||||
expect(page).to have_link('Incoming')
|
||||
expect(page).to_not have_link('Expired')
|
||||
end
|
||||
end
|
||||
|
||||
context 'Show' do
|
||||
let(:geozone) { create(:geozone) }
|
||||
let(:poll) { create(:poll) }
|
||||
|
||||
scenario 'Lists questions from proposals as well as regular ones' do
|
||||
normal_question = create(:poll_question, poll: poll)
|
||||
proposal_question = create(:poll_question, poll: poll, proposal: create(:proposal))
|
||||
|
||||
visit poll_path(poll)
|
||||
expect(page).to have_content(poll.name)
|
||||
|
||||
expect(page).to have_content(normal_question.title)
|
||||
expect(page).to have_content(proposal_question.title)
|
||||
end
|
||||
|
||||
scenario 'Non-logged in users' do
|
||||
create(:poll_question, poll: poll, valid_answers: 'Han Solo, Chewbacca')
|
||||
visit poll_path(poll)
|
||||
|
||||
expect(page).to have_content('Han Solo')
|
||||
expect(page).to have_content('Chewbacca')
|
||||
expect(page).to have_content('You must Sign in or Sign up to participate')
|
||||
|
||||
expect(page).to_not have_link('Han Solo')
|
||||
expect(page).to_not have_link('Chewbacca')
|
||||
end
|
||||
|
||||
scenario 'Level 1 users' do
|
||||
create(:poll_question, poll: poll, geozone_ids: [geozone.id], valid_answers: 'Han Solo, Chewbacca')
|
||||
login_as(create(:user, geozone: geozone))
|
||||
visit poll_path(poll)
|
||||
|
||||
expect(page).to have_content('You must verify your account in order to answer')
|
||||
|
||||
expect(page).to have_content('Han Solo')
|
||||
expect(page).to have_content('Chewbacca')
|
||||
|
||||
expect(page).to_not have_link('Han Solo')
|
||||
expect(page).to_not have_link('Chewbacca')
|
||||
end
|
||||
|
||||
scenario 'Level 2 users in an incoming question' do
|
||||
incoming_poll = create(:poll, :incoming)
|
||||
create(:poll_question, poll: incoming_poll, geozone_ids: [geozone.id], valid_answers: 'Rey, Finn')
|
||||
login_as(create(:user, :level_two, geozone: geozone))
|
||||
|
||||
visit poll_path(incoming_poll)
|
||||
|
||||
expect(page).to have_content('Rey')
|
||||
expect(page).to have_content('Finn')
|
||||
expect(page).to_not have_link('Rey')
|
||||
expect(page).to_not have_link('Finn')
|
||||
|
||||
expect(page).to have_content('This poll has not yet started')
|
||||
end
|
||||
|
||||
scenario 'Level 2 users in an expired question' do
|
||||
expired_poll = create(:poll, :expired)
|
||||
create(:poll_question, poll: expired_poll, geozone_ids: [geozone.id], valid_answers: 'Luke, Leia')
|
||||
login_as(create(:user, :level_two, geozone: geozone))
|
||||
|
||||
visit poll_path(expired_poll)
|
||||
|
||||
expect(page).to have_content('Luke')
|
||||
expect(page).to have_content('Leia')
|
||||
expect(page).to_not have_link('Luke')
|
||||
expect(page).to_not have_link('Leia')
|
||||
|
||||
expect(page).to have_content('This poll has finished')
|
||||
end
|
||||
|
||||
scenario 'Level 2 users in a poll with questions for a geozone which is not theirs' do
|
||||
create(:poll_question, poll: poll, geozone_ids: [], valid_answers: 'Vader, Palpatine')
|
||||
login_as(create(:user, :level_two))
|
||||
|
||||
visit poll_path(poll)
|
||||
|
||||
expect(page).to have_content('The following questions are not available in your geozone')
|
||||
|
||||
expect(page).to have_content('Vader')
|
||||
expect(page).to have_content('Palpatine')
|
||||
expect(page).to_not have_link('Vader')
|
||||
expect(page).to_not have_link('Palpatine')
|
||||
end
|
||||
|
||||
scenario 'Level 2 users reading a same-geozone question' do
|
||||
create(:poll_question, poll: poll, geozone_ids: [geozone.id], valid_answers: 'Han Solo, Chewbacca')
|
||||
login_as(create(:user, :level_two, geozone: geozone))
|
||||
visit poll_path(poll)
|
||||
|
||||
expect(page).to have_link('Han Solo')
|
||||
expect(page).to have_link('Chewbacca')
|
||||
end
|
||||
|
||||
scenario 'Level 2 users reading a all-geozones question' do
|
||||
create(:poll_question, poll: poll, all_geozones: true, valid_answers: 'Han Solo, Chewbacca')
|
||||
login_as(create(:user, :level_two, geozone: geozone))
|
||||
visit poll_path(poll)
|
||||
|
||||
expect(page).to have_link('Han Solo')
|
||||
expect(page).to have_link('Chewbacca')
|
||||
end
|
||||
|
||||
scenario 'Level 2 users who have already answered' do
|
||||
question = create(:poll_question, poll: poll, geozone_ids:[geozone.id], valid_answers: 'Han Solo, Chewbacca')
|
||||
user = create(:user, :level_two, geozone: geozone)
|
||||
create(:poll_partial_result, question: question, author: user, answer: 'Chewbacca')
|
||||
login_as user
|
||||
visit poll_path(poll)
|
||||
|
||||
expect(page).to have_link('Han Solo')
|
||||
expect(page).to_not have_link('Chewbacca')
|
||||
expect(page).to have_content('Chewbacca')
|
||||
end
|
||||
|
||||
scenario 'Level 2 users answering', :js do
|
||||
create(:poll_question, poll: poll, geozone_ids: [geozone.id], valid_answers: 'Han Solo, Chewbacca')
|
||||
user = create(:user, :level_two, geozone: geozone)
|
||||
login_as user
|
||||
visit poll_path(poll)
|
||||
|
||||
click_link 'Han Solo'
|
||||
|
||||
expect(page).to_not have_link('Han Solo')
|
||||
expect(page).to have_link('Chewbacca')
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -3,8 +3,9 @@ require 'cancan/matchers'
|
||||
|
||||
describe "Abilities::Common" do
|
||||
subject(:ability) { Ability.new(user) }
|
||||
let(:geozone) { create(:geozone) }
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:user) { create(:user, geozone: geozone) }
|
||||
|
||||
let(:debate) { create(:debate) }
|
||||
let(:comment) { create(:comment) }
|
||||
@@ -13,6 +14,20 @@ describe "Abilities::Common" do
|
||||
let(:own_comment) { create(:comment, author: user) }
|
||||
let(:own_proposal) { create(:proposal, author: user) }
|
||||
|
||||
let(:current_poll) { create(:poll) }
|
||||
let(:incoming_poll) { create(:poll, :incoming) }
|
||||
let(:expired_poll) { create(:poll, :expired) }
|
||||
|
||||
let(:poll_question_from_own_geozone) { create(:poll_question, geozones: [geozone]) }
|
||||
let(:poll_question_from_other_geozone) { create(:poll_question, geozones: [create(:geozone)]) }
|
||||
let(:poll_question_from_all_geozones) { create(:poll_question, all_geozones: true) }
|
||||
let(:expired_poll_question_from_own_geozone) { create(:poll_question, poll: expired_poll, geozones: [geozone]) }
|
||||
let(:expired_poll_question_from_other_geozone) { create(:poll_question, poll: expired_poll, geozones: [create(:geozone)]) }
|
||||
let(:expired_poll_question_from_all_geozones) { create(:poll_question, poll: expired_poll, all_geozones: true) }
|
||||
let(:incoming_poll_question_from_own_geozone) { create(:poll_question, poll: incoming_poll, geozones: [geozone]) }
|
||||
let(:incoming_poll_question_from_other_geozone) { create(:poll_question, poll: incoming_poll, geozones: [create(:geozone)]) }
|
||||
let(:incoming_poll_question_from_all_geozones) { create(:poll_question, poll: incoming_poll, all_geozones: true) }
|
||||
|
||||
it { should be_able_to(:index, Debate) }
|
||||
it { should be_able_to(:show, debate) }
|
||||
it { should be_able_to(:vote, debate) }
|
||||
@@ -103,6 +118,33 @@ describe "Abilities::Common" do
|
||||
it { should be_able_to(:create, DirectMessage) }
|
||||
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_not be_able_to(:answer, incoming_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_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, incoming_poll_question_from_own_geozone ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone ) }
|
||||
|
||||
context "without geozone" do
|
||||
before(:each) { 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, 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, incoming_poll_question_from_own_geozone ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone ) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "when level 3 verified" do
|
||||
@@ -121,5 +163,32 @@ describe "Abilities::Common" do
|
||||
it { should be_able_to(:create, DirectMessage) }
|
||||
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_not be_able_to(:answer, incoming_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_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, incoming_poll_question_from_own_geozone ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone ) }
|
||||
|
||||
context "without geozone" do
|
||||
before(:each) { 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, 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, incoming_poll_question_from_own_geozone ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_all_geozones ) }
|
||||
it { should_not be_able_to(:answer, incoming_poll_question_from_other_geozone ) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
16
spec/models/poll/partial_result_spec.rb
Normal file
16
spec/models/poll/partial_result_spec.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Poll::PartialResult, type: :model do
|
||||
|
||||
describe "validations" do
|
||||
it "validates that the answers are included in the Enquiry's list" do
|
||||
q = create(:poll_question, valid_answers: 'One, Two, Three')
|
||||
expect(build(:poll_partial_result, question: q, answer: 'One')).to be_valid
|
||||
expect(build(:poll_partial_result, question: q, answer: 'Two')).to be_valid
|
||||
expect(build(:poll_partial_result, question: q, answer: 'Three')).to be_valid
|
||||
|
||||
expect(build(:poll_partial_result, question: q, answer: 'Four')).to_not be_valid
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
28
spec/models/poll/question_spec.rb
Normal file
28
spec/models/poll/question_spec.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Poll::Question, type: :model do
|
||||
|
||||
describe "#valid_answers" do
|
||||
it "gets a comma-separated string, but returns an array" do
|
||||
q = create(:poll_question, valid_answers: "Yes, No")
|
||||
expect(q.valid_answers).to eq(["Yes", "No"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "#copy_attributes_from_proposal" do
|
||||
it "copies the attributes from the proposal" do
|
||||
create_list(:geozone, 3)
|
||||
p = create(:proposal)
|
||||
q = create(:poll_question)
|
||||
q.copy_attributes_from_proposal(p)
|
||||
expect(q.valid_answers).to eq(['Yes', 'No'])
|
||||
expect(q.author).to eq(p.author)
|
||||
expect(q.author_visible_name).to eq(p.author.name)
|
||||
expect(q.proposal_id).to eq(p.id)
|
||||
expect(q.title).to eq(p.title)
|
||||
expect(q.question).to eq(p.question)
|
||||
expect(q.all_geozones).to be_true
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user