Merge pull request #1258 from consul/polling-questions

Polling questions
This commit is contained in:
Raimond Garcia
2016-11-14 17:44:41 +01:00
committed by GitHub
23 changed files with 690 additions and 7 deletions

View 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

View 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

View File

@@ -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 }

View File

@@ -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

View File

@@ -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
end
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

View 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

View 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

View 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 %>

View 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>

View File

@@ -0,0 +1 @@
$("#<%= dom_id(@question) %>_answers").html('<%= j render("polls/questions/answers", question: @question) %>');

View 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 %>

View File

@@ -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."

View File

@@ -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

View 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

View 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

View File

@@ -0,0 +1,5 @@
class AddAllGeozonesToPollQuestions < ActiveRecord::Migration
def change
add_column :poll_questions, :all_geozones, :boolean, default: false
end
end

View 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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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

View 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

View 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