Add specs for polls (#4246)
* Extract validation logic into a service object. * Extract logic for updating polls custom fields into a service object. * Use `strip_heredoc` instead. * FIX: Polls do not update when configuration has been changed.
This commit is contained in:
parent
ecb2a0b9c7
commit
a849fae5ee
|
@ -0,0 +1,95 @@
|
||||||
|
module DiscoursePoll
|
||||||
|
class PollsUpdater
|
||||||
|
VALID_POLLS_CONFIGS = %w{type min max}.map(&:freeze)
|
||||||
|
|
||||||
|
def self.update(post, polls)
|
||||||
|
# load previous polls
|
||||||
|
previous_polls = post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] || {}
|
||||||
|
|
||||||
|
# extract options
|
||||||
|
current_options = extract_option_ids(polls)
|
||||||
|
previous_options = extract_option_ids(previous_polls)
|
||||||
|
|
||||||
|
# are the polls different?
|
||||||
|
if polls_updated?(polls, previous_polls) || (current_options != previous_options)
|
||||||
|
has_votes = total_votes(previous_polls) > 0
|
||||||
|
|
||||||
|
# outside of the 5-minute edit window?
|
||||||
|
if post.created_at < 5.minutes.ago && has_votes
|
||||||
|
# cannot add/remove/rename polls
|
||||||
|
if polls.keys.sort != previous_polls.keys.sort
|
||||||
|
post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
# deal with option changes
|
||||||
|
if User.staff.pluck(:id).include?(post.last_editor_id)
|
||||||
|
# staff can only edit options
|
||||||
|
polls.each_key do |poll_name|
|
||||||
|
if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size && previous_polls[poll_name]["voters"].to_i > 0
|
||||||
|
post.errors.add(:base, I18n.t("poll.staff_cannot_add_or_remove_options_after_5_minutes"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# OP cannot edit poll options
|
||||||
|
post.errors.add(:base, I18n.t("poll.op_cannot_edit_options_after_5_minutes"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# try to merge votes
|
||||||
|
polls.each_key do |poll_name|
|
||||||
|
next unless previous_polls.has_key?(poll_name)
|
||||||
|
|
||||||
|
# when the # of options has changed, reset all the votes
|
||||||
|
if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size
|
||||||
|
PostCustomField.where(post_id: post.id, name: DiscoursePoll::VOTES_CUSTOM_FIELD).destroy_all
|
||||||
|
post.clear_custom_fields
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
polls[poll_name]["voters"] = previous_polls[poll_name]["voters"]
|
||||||
|
polls[poll_name]["anonymous_voters"] = previous_polls[poll_name]["anonymous_voters"] if previous_polls[poll_name].has_key?("anonymous_voters")
|
||||||
|
|
||||||
|
for o in 0...polls[poll_name]["options"].size
|
||||||
|
current_option = polls[poll_name]["options"][o]
|
||||||
|
previous_option = previous_polls[poll_name]["options"][o]
|
||||||
|
|
||||||
|
current_option["votes"] = previous_option["votes"]
|
||||||
|
current_option["anonymous_votes"] = previous_option["anonymous_votes"] if previous_option.has_key?("anonymous_votes")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# immediately store the polls
|
||||||
|
post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] = polls
|
||||||
|
post.save_custom_fields(true)
|
||||||
|
|
||||||
|
# publish the changes
|
||||||
|
MessageBus.publish("/polls/#{post.topic_id}", { post_id: post.id, polls: polls })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.polls_updated?(current_polls, previous_polls)
|
||||||
|
return true if (current_polls.keys.sort != previous_polls.keys.sort)
|
||||||
|
|
||||||
|
current_polls.each_key do |poll_name|
|
||||||
|
if !previous_polls[poll_name] ||
|
||||||
|
(current_polls[poll_name].values_at(*VALID_POLLS_CONFIGS) != previous_polls[poll_name].values_at(*VALID_POLLS_CONFIGS))
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.extract_option_ids(polls)
|
||||||
|
polls.values.map { |p| p["options"].map { |o| o["id"] } }.flatten.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.total_votes(polls)
|
||||||
|
polls.map { |key, value| value["voters"].to_i }.sum
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,112 @@
|
||||||
|
module DiscoursePoll
|
||||||
|
class PollsValidator
|
||||||
|
def initialize(post)
|
||||||
|
@post = post
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_polls
|
||||||
|
polls = {}
|
||||||
|
|
||||||
|
extracted_polls = DiscoursePoll::Poll::extract(@post.raw, @post.topic_id)
|
||||||
|
|
||||||
|
extracted_polls.each do |poll|
|
||||||
|
# polls should have a unique name
|
||||||
|
return false unless unique_poll_name?(polls, poll)
|
||||||
|
|
||||||
|
# options must be unique
|
||||||
|
return false unless unique_options?(poll)
|
||||||
|
|
||||||
|
# at least 2 options
|
||||||
|
return false unless at_least_two_options?(poll)
|
||||||
|
|
||||||
|
# maximum # of options
|
||||||
|
return false unless valid_number_of_options?(poll)
|
||||||
|
|
||||||
|
# poll with multiple choices
|
||||||
|
return false unless valid_multiple_choice_settings?(poll)
|
||||||
|
|
||||||
|
# store the valid poll
|
||||||
|
polls[poll["name"]] = poll
|
||||||
|
end
|
||||||
|
|
||||||
|
polls
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def unique_poll_name?(polls, poll)
|
||||||
|
if polls.has_key?(poll["name"])
|
||||||
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
|
@post.errors.add(:base, I18n.t("poll.multiple_polls_without_name"))
|
||||||
|
else
|
||||||
|
@post.errors.add(:base, I18n.t("poll.multiple_polls_with_same_name", name: poll["name"]))
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def unique_options?(poll)
|
||||||
|
if poll["options"].map { |o| o["id"] }.uniq.size != poll["options"].size
|
||||||
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
|
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_different_options"))
|
||||||
|
else
|
||||||
|
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_different_options", name: poll["name"]))
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def at_least_two_options?(poll)
|
||||||
|
if poll["options"].size < 2
|
||||||
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
|
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_2_options"))
|
||||||
|
else
|
||||||
|
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_2_options", name: poll["name"]))
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_number_of_options?(poll)
|
||||||
|
if poll["options"].size > SiteSetting.poll_maximum_options
|
||||||
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
|
@post.errors.add(:base, I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options))
|
||||||
|
else
|
||||||
|
@post.errors.add(:base, I18n.t("poll.named_poll_must_have_less_options", name: poll["name"], count: SiteSetting.poll_maximum_options))
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_multiple_choice_settings?(poll)
|
||||||
|
if poll["type"] == "multiple"
|
||||||
|
min = (poll["min"].presence || 1).to_i
|
||||||
|
max = (poll["max"].presence || poll["options"].size).to_i
|
||||||
|
|
||||||
|
if min > max || max <= 0 || max > poll["options"].size || min >= poll["options"].size
|
||||||
|
if poll["name"] == ::DiscoursePoll::DEFAULT_POLL_NAME
|
||||||
|
@post.errors.add(:base, I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"))
|
||||||
|
else
|
||||||
|
@post.errors.add(:base, I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: poll["name"]))
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,15 +14,18 @@ register_asset "javascripts/poll_dialect.js", :server_side
|
||||||
|
|
||||||
PLUGIN_NAME ||= "discourse_poll".freeze
|
PLUGIN_NAME ||= "discourse_poll".freeze
|
||||||
|
|
||||||
POLLS_CUSTOM_FIELD ||= "polls".freeze
|
|
||||||
VOTES_CUSTOM_FIELD ||= "polls-votes".freeze
|
|
||||||
|
|
||||||
DATA_PREFIX ||= "data-poll-".freeze
|
DATA_PREFIX ||= "data-poll-".freeze
|
||||||
DEFAULT_POLL_NAME ||= "poll".freeze
|
|
||||||
|
|
||||||
after_initialize do
|
after_initialize do
|
||||||
|
|
||||||
module ::DiscoursePoll
|
module ::DiscoursePoll
|
||||||
|
DEFAULT_POLL_NAME ||= "poll".freeze
|
||||||
|
POLLS_CUSTOM_FIELD ||= "polls".freeze
|
||||||
|
VOTES_CUSTOM_FIELD ||= "polls-votes".freeze
|
||||||
|
|
||||||
|
autoload :PollsValidator, "#{Rails.root}/plugins/poll/lib/polls_validator"
|
||||||
|
autoload :PollsUpdater, "#{Rails.root}/plugins/poll/lib/polls_updater"
|
||||||
|
|
||||||
class Engine < ::Rails::Engine
|
class Engine < ::Rails::Engine
|
||||||
engine_name PLUGIN_NAME
|
engine_name PLUGIN_NAME
|
||||||
isolate_namespace DiscoursePoll
|
isolate_namespace DiscoursePoll
|
||||||
|
@ -46,7 +49,7 @@ after_initialize do
|
||||||
raise StandardError.new I18n.t("poll.topic_must_be_open_to_vote")
|
raise StandardError.new I18n.t("poll.topic_must_be_open_to_vote")
|
||||||
end
|
end
|
||||||
|
|
||||||
polls = post.custom_fields[POLLS_CUSTOM_FIELD]
|
polls = post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
|
||||||
|
|
||||||
raise StandardError.new I18n.t("poll.no_polls_associated_with_this_post") if polls.blank?
|
raise StandardError.new I18n.t("poll.no_polls_associated_with_this_post") if polls.blank?
|
||||||
|
|
||||||
|
@ -64,11 +67,11 @@ after_initialize do
|
||||||
poll["voters"] = poll["anonymous_voters"] || 0
|
poll["voters"] = poll["anonymous_voters"] || 0
|
||||||
all_options = Hash.new(0)
|
all_options = Hash.new(0)
|
||||||
|
|
||||||
post.custom_fields[VOTES_CUSTOM_FIELD] ||= {}
|
post.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD] ||= {}
|
||||||
post.custom_fields[VOTES_CUSTOM_FIELD]["#{user_id}"] ||= {}
|
post.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]["#{user_id}"] ||= {}
|
||||||
post.custom_fields[VOTES_CUSTOM_FIELD]["#{user_id}"][poll_name] = options
|
post.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]["#{user_id}"][poll_name] = options
|
||||||
|
|
||||||
post.custom_fields[VOTES_CUSTOM_FIELD].each do |_, user_votes|
|
post.custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD].each do |_, user_votes|
|
||||||
next unless votes = user_votes[poll_name]
|
next unless votes = user_votes[poll_name]
|
||||||
votes.each { |option| all_options[option] += 1 }
|
votes.each { |option| all_options[option] += 1 }
|
||||||
poll["voters"] += 1 if (available_options & votes.to_set).size > 0
|
poll["voters"] += 1 if (available_options & votes.to_set).size > 0
|
||||||
|
@ -79,7 +82,7 @@ after_initialize do
|
||||||
option["votes"] = all_options[option["id"]] + anonymous_votes
|
option["votes"] = all_options[option["id"]] + anonymous_votes
|
||||||
end
|
end
|
||||||
|
|
||||||
post.custom_fields[POLLS_CUSTOM_FIELD] = polls
|
post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] = polls
|
||||||
post.save_custom_fields(true)
|
post.save_custom_fields(true)
|
||||||
|
|
||||||
MessageBus.publish("/polls/#{post.topic_id}", { post_id: post_id, polls: polls })
|
MessageBus.publish("/polls/#{post.topic_id}", { post_id: post_id, polls: polls })
|
||||||
|
@ -109,7 +112,7 @@ after_initialize do
|
||||||
raise StandardError.new I18n.t("poll.only_staff_or_op_can_toggle_status")
|
raise StandardError.new I18n.t("poll.only_staff_or_op_can_toggle_status")
|
||||||
end
|
end
|
||||||
|
|
||||||
polls = post.custom_fields[POLLS_CUSTOM_FIELD]
|
polls = post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]
|
||||||
|
|
||||||
raise StandardError.new I18n.t("poll.no_polls_associated_with_this_post") if polls.blank?
|
raise StandardError.new I18n.t("poll.no_polls_associated_with_this_post") if polls.blank?
|
||||||
raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) if polls[poll_name].blank?
|
raise StandardError.new I18n.t("poll.no_poll_with_this_name", name: poll_name) if polls[poll_name].blank?
|
||||||
|
@ -214,7 +217,7 @@ after_initialize do
|
||||||
polls = self.polls
|
polls = self.polls
|
||||||
|
|
||||||
DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post.id}") do
|
DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post.id}") do
|
||||||
post.custom_fields[POLLS_CUSTOM_FIELD] = polls
|
post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] = polls
|
||||||
post.save_custom_fields(true)
|
post.save_custom_fields(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -224,130 +227,13 @@ after_initialize do
|
||||||
# only care when raw has changed!
|
# only care when raw has changed!
|
||||||
return unless self.raw_changed?
|
return unless self.raw_changed?
|
||||||
|
|
||||||
polls = {}
|
validator = DiscoursePoll::PollsValidator.new(self)
|
||||||
|
return unless (polls = validator.validate_polls)
|
||||||
extracted_polls = DiscoursePoll::Poll::extract(self.raw, self.topic_id)
|
|
||||||
|
|
||||||
extracted_polls.each do |poll|
|
|
||||||
# polls should have a unique name
|
|
||||||
if polls.has_key?(poll["name"])
|
|
||||||
poll["name"] == DEFAULT_POLL_NAME ?
|
|
||||||
self.errors.add(:base, I18n.t("poll.multiple_polls_without_name")) :
|
|
||||||
self.errors.add(:base, I18n.t("poll.multiple_polls_with_same_name", name: poll["name"]))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# options must be unique
|
|
||||||
if poll["options"].map { |o| o["id"] }.uniq.size != poll["options"].size
|
|
||||||
poll["name"] == DEFAULT_POLL_NAME ?
|
|
||||||
self.errors.add(:base, I18n.t("poll.default_poll_must_have_different_options")) :
|
|
||||||
self.errors.add(:base, I18n.t("poll.named_poll_must_have_different_options", name: poll["name"]))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# at least 2 options
|
|
||||||
if poll["options"].size < 2
|
|
||||||
poll["name"] == DEFAULT_POLL_NAME ?
|
|
||||||
self.errors.add(:base, I18n.t("poll.default_poll_must_have_at_least_2_options")) :
|
|
||||||
self.errors.add(:base, I18n.t("poll.named_poll_must_have_at_least_2_options", name: poll["name"]))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# maximum # of options
|
|
||||||
if poll["options"].size > SiteSetting.poll_maximum_options
|
|
||||||
poll["name"] == DEFAULT_POLL_NAME ?
|
|
||||||
self.errors.add(:base, I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options)) :
|
|
||||||
self.errors.add(:base, I18n.t("poll.named_poll_must_have_less_options", name: poll["name"], count: SiteSetting.poll_maximum_options))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# poll with multiple choices
|
|
||||||
if poll["type"] == "multiple"
|
|
||||||
min = (poll["min"].presence || 1).to_i
|
|
||||||
max = (poll["max"].presence || poll["options"].size).to_i
|
|
||||||
|
|
||||||
if min > max || max <= 0 || max > poll["options"].size || min >= poll["options"].size
|
|
||||||
poll["name"] == DEFAULT_POLL_NAME ?
|
|
||||||
self.errors.add(:base, I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")) :
|
|
||||||
self.errors.add(:base, I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: poll["name"]))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# store the valid poll
|
|
||||||
polls[poll["name"]] = poll
|
|
||||||
end
|
|
||||||
|
|
||||||
# are we updating a post?
|
# are we updating a post?
|
||||||
if self.id.present?
|
if self.id.present?
|
||||||
post = self
|
DistributedMutex.synchronize("#{PLUGIN_NAME}-#{self.id}") do
|
||||||
DistributedMutex.synchronize("#{PLUGIN_NAME}-#{post.id}") do
|
DiscoursePoll::PollsUpdater.update(self, polls)
|
||||||
# load previous polls
|
|
||||||
previous_polls = post.custom_fields[POLLS_CUSTOM_FIELD] || {}
|
|
||||||
|
|
||||||
# extract options
|
|
||||||
current_options = polls.values.map { |p| p["options"].map { |o| o["id"] } }.flatten.sort
|
|
||||||
previous_options = previous_polls.values.map { |p| p["options"].map { |o| o["id"] } }.flatten.sort
|
|
||||||
|
|
||||||
# are the polls different?
|
|
||||||
if polls.keys != previous_polls.keys || current_options != previous_options
|
|
||||||
|
|
||||||
has_votes = previous_polls.keys.map { |p| previous_polls[p]["voters"].to_i }.sum > 0
|
|
||||||
|
|
||||||
# outside of the 5-minute edit window?
|
|
||||||
if post.created_at < 5.minutes.ago && has_votes
|
|
||||||
# cannot add/remove/rename polls
|
|
||||||
if polls.keys.sort != previous_polls.keys.sort
|
|
||||||
post.errors.add(:base, I18n.t("poll.cannot_change_polls_after_5_minutes"))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
# deal with option changes
|
|
||||||
if User.staff.pluck(:id).include?(post.last_editor_id)
|
|
||||||
# staff can only edit options
|
|
||||||
polls.each_key do |poll_name|
|
|
||||||
if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size && previous_polls[poll_name]["voters"].to_i > 0
|
|
||||||
post.errors.add(:base, I18n.t("poll.staff_cannot_add_or_remove_options_after_5_minutes"))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# OP cannot edit poll options
|
|
||||||
post.errors.add(:base, I18n.t("poll.op_cannot_edit_options_after_5_minutes"))
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# try to merge votes
|
|
||||||
polls.each_key do |poll_name|
|
|
||||||
next unless previous_polls.has_key?(poll_name)
|
|
||||||
|
|
||||||
# when the # of options has changed, reset all the votes
|
|
||||||
if polls[poll_name]["options"].size != previous_polls[poll_name]["options"].size
|
|
||||||
PostCustomField.where(post_id: post.id, name: VOTES_CUSTOM_FIELD).destroy_all
|
|
||||||
post.clear_custom_fields
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
polls[poll_name]["voters"] = previous_polls[poll_name]["voters"]
|
|
||||||
polls[poll_name]["anonymous_voters"] = previous_polls[poll_name]["anonymous_voters"] if previous_polls[poll_name].has_key?("anonymous_voters")
|
|
||||||
|
|
||||||
for o in 0...polls[poll_name]["options"].size
|
|
||||||
current_option = polls[poll_name]["options"][o]
|
|
||||||
previous_option = previous_polls[poll_name]["options"][o]
|
|
||||||
|
|
||||||
current_option["votes"] = previous_option["votes"]
|
|
||||||
current_option["anonymous_votes"] = previous_option["anonymous_votes"] if previous_option.has_key?("anonymous_votes")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# immediately store the polls
|
|
||||||
post.custom_fields[POLLS_CUSTOM_FIELD] = polls
|
|
||||||
post.save_custom_fields(true)
|
|
||||||
|
|
||||||
# publish the changes
|
|
||||||
MessageBus.publish("/polls/#{post.topic_id}", { post_id: post.id, polls: polls })
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
self.polls = polls
|
self.polls = polls
|
||||||
|
@ -356,11 +242,11 @@ after_initialize do
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
Post.register_custom_field_type(POLLS_CUSTOM_FIELD, :json)
|
Post.register_custom_field_type(DiscoursePoll::POLLS_CUSTOM_FIELD, :json)
|
||||||
Post.register_custom_field_type(VOTES_CUSTOM_FIELD, :json)
|
Post.register_custom_field_type(DiscoursePoll::VOTES_CUSTOM_FIELD, :json)
|
||||||
|
|
||||||
TopicView.add_post_custom_fields_whitelister do |user|
|
TopicView.add_post_custom_fields_whitelister do |user|
|
||||||
user ? [POLLS_CUSTOM_FIELD, VOTES_CUSTOM_FIELD] : [POLLS_CUSTOM_FIELD]
|
user ? [DiscoursePoll::POLLS_CUSTOM_FIELD, DiscoursePoll::VOTES_CUSTOM_FIELD] : [DiscoursePoll::POLLS_CUSTOM_FIELD]
|
||||||
end
|
end
|
||||||
|
|
||||||
on(:reduce_cooked) do |fragment, post|
|
on(:reduce_cooked) do |fragment, post|
|
||||||
|
@ -376,20 +262,20 @@ after_initialize do
|
||||||
|
|
||||||
# tells the front-end we have a poll for that post
|
# tells the front-end we have a poll for that post
|
||||||
on(:post_created) do |post|
|
on(:post_created) do |post|
|
||||||
next if post.is_first_post? || post.custom_fields[POLLS_CUSTOM_FIELD].blank?
|
next if post.is_first_post? || post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].blank?
|
||||||
MessageBus.publish("/polls/#{post.topic_id}", {
|
MessageBus.publish("/polls/#{post.topic_id}", {
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
polls: post.custom_fields[POLLS_CUSTOM_FIELD]})
|
polls: post.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]})
|
||||||
end
|
end
|
||||||
|
|
||||||
add_to_serializer(:post, :polls, false) { post_custom_fields[POLLS_CUSTOM_FIELD] }
|
add_to_serializer(:post, :polls, false) { post_custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD] }
|
||||||
add_to_serializer(:post, :include_polls?) { post_custom_fields.present? && post_custom_fields[POLLS_CUSTOM_FIELD].present? }
|
add_to_serializer(:post, :include_polls?) { post_custom_fields.present? && post_custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD].present? }
|
||||||
|
|
||||||
add_to_serializer(:post, :polls_votes, false) { post_custom_fields[VOTES_CUSTOM_FIELD]["#{scope.user.id}"] }
|
add_to_serializer(:post, :polls_votes, false) { post_custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD]["#{scope.user.id}"] }
|
||||||
add_to_serializer(:post, :include_polls_votes?) do
|
add_to_serializer(:post, :include_polls_votes?) do
|
||||||
return unless scope.user
|
return unless scope.user
|
||||||
return unless post_custom_fields.present?
|
return unless post_custom_fields.present?
|
||||||
return unless post_custom_fields[VOTES_CUSTOM_FIELD].present?
|
return unless post_custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD].present?
|
||||||
post_custom_fields[VOTES_CUSTOM_FIELD].has_key?("#{scope.user.id}")
|
post_custom_fields[DiscoursePoll::VOTES_CUSTOM_FIELD].has_key?("#{scope.user.id}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe DiscoursePoll::PollsUpdater do
|
||||||
|
let(:post_with_two_polls) do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
|
||||||
|
[poll name=test]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
Fabricate(:post, raw: raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:post) do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
Fabricate(:post, raw: raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:other_post) do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 3
|
||||||
|
* 4
|
||||||
|
* 5
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
Fabricate(:post, raw: raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:polls) do
|
||||||
|
DiscoursePoll::PollsValidator.new(post).validate_polls
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:polls_with_3_options) do
|
||||||
|
DiscoursePoll::PollsValidator.new(other_post).validate_polls
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:two_polls) do
|
||||||
|
DiscoursePoll::PollsValidator.new(post_with_two_polls).validate_polls
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.update' do
|
||||||
|
describe 'when post does not contain any polls' do
|
||||||
|
it 'should update polls correctly' do
|
||||||
|
post = Fabricate(:post)
|
||||||
|
|
||||||
|
message = MessageBus.track_publish do
|
||||||
|
described_class.update(post, polls)
|
||||||
|
end.first
|
||||||
|
|
||||||
|
expect(post.reload.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]).to eq(polls)
|
||||||
|
expect(message.data[:post_id]).to eq(post.id)
|
||||||
|
expect(message.data[:polls]).to eq(polls)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when post contains existing polls' do
|
||||||
|
it "should be able to update polls correctly" do
|
||||||
|
message = MessageBus.track_publish do
|
||||||
|
described_class.update(post, polls_with_3_options)
|
||||||
|
end.first
|
||||||
|
|
||||||
|
expect(post.reload.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]).to eq(polls_with_3_options)
|
||||||
|
expect(message.data[:post_id]).to eq(post.id)
|
||||||
|
expect(message.data[:polls]).to eq(polls_with_3_options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'when there are no changes' do
|
||||||
|
it "should not do anything" do
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
described_class.update(post, polls)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(messages).to eq([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "polls of type 'multiple'" do
|
||||||
|
let(:min_2_post) do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple min=2 max=3]
|
||||||
|
- Option 1
|
||||||
|
- Option 2
|
||||||
|
- Option 3
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
Fabricate(:post, raw: raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:min_2_poll) do
|
||||||
|
DiscoursePoll::PollsValidator.new(min_2_post).validate_polls
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:min_1_post) do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple min=1 max=2]
|
||||||
|
- Option 1
|
||||||
|
- Option 2
|
||||||
|
- Option 3
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
Fabricate(:post, raw: raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:min_1_poll) do
|
||||||
|
DiscoursePoll::PollsValidator.new(min_1_post).validate_polls
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should be able to update options" do
|
||||||
|
min_2_poll
|
||||||
|
|
||||||
|
message = MessageBus.track_publish do
|
||||||
|
described_class.update(min_2_post, min_1_poll)
|
||||||
|
end.first
|
||||||
|
|
||||||
|
expect(min_2_post.reload.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]).to eq(min_1_poll)
|
||||||
|
expect(message.data[:post_id]).to eq(min_2_post.id)
|
||||||
|
expect(message.data[:polls]).to eq(min_1_poll)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when post has been created more than 5 minutes ago" do
|
||||||
|
let(:another_post) { Fabricate(:post, created_at: Time.zone.now - 5.minutes) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
polls.each { |key, value| value["voters"] = 2 }
|
||||||
|
described_class.update(another_post, polls)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not allow new polls to be added" do
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
described_class.update(another_post, two_polls)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(another_post.errors[:base]).to include(I18n.t(
|
||||||
|
"poll.cannot_change_polls_after_5_minutes")
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(messages).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not allow users to edit options of current poll" do
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
described_class.update(another_post, polls_with_3_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(another_post.errors[:base]).to include(I18n.t(
|
||||||
|
"poll.op_cannot_edit_options_after_5_minutes"
|
||||||
|
))
|
||||||
|
|
||||||
|
expect(messages).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
context "staff" do
|
||||||
|
it "should not allow staff to add options if votes have been casted" do
|
||||||
|
another_post.update_attributes!(last_editor_id: User.staff.first.id)
|
||||||
|
|
||||||
|
messages = MessageBus.track_publish do
|
||||||
|
described_class.update(another_post, polls_with_3_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(another_post.errors[:base]).to include(I18n.t(
|
||||||
|
"poll.staff_cannot_add_or_remove_options_after_5_minutes"
|
||||||
|
))
|
||||||
|
|
||||||
|
expect(messages).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow staff to add options if no votes have been casted" do
|
||||||
|
post.update_attributes!(
|
||||||
|
created_at: Time.zone.now - 5.minutes,
|
||||||
|
last_editor_id: User.staff.first.id
|
||||||
|
)
|
||||||
|
|
||||||
|
message = MessageBus.track_publish do
|
||||||
|
described_class.update(post, polls_with_3_options)
|
||||||
|
end.first
|
||||||
|
|
||||||
|
expect(post.reload.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]).to eq(polls_with_3_options)
|
||||||
|
expect(message.data[:post_id]).to eq(post.id)
|
||||||
|
expect(message.data[:polls]).to eq(polls_with_3_options)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow staff to edit options if votes have been casted" do
|
||||||
|
another_post.update_attributes!(last_editor_id: User.staff.first.id)
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 3
|
||||||
|
* 4
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
different_post = Fabricate(:post, raw: raw)
|
||||||
|
different_polls = DiscoursePoll::PollsValidator.new(different_post).validate_polls
|
||||||
|
|
||||||
|
message = MessageBus.track_publish do
|
||||||
|
described_class.update(another_post, different_polls)
|
||||||
|
end.first
|
||||||
|
|
||||||
|
different_polls.each { |key, value| value["voters"] = 2 }
|
||||||
|
|
||||||
|
expect(another_post.reload.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]).to eq(different_polls)
|
||||||
|
expect(message.data[:post_id]).to eq(another_post.id)
|
||||||
|
expect(message.data[:polls]).to eq(different_polls)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should allow staff to edit options if votes have not been casted" do
|
||||||
|
post.update_attributes!(last_editor_id: User.staff.first.id)
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 3
|
||||||
|
* 4
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
different_post = Fabricate(:post, raw: raw)
|
||||||
|
different_polls = DiscoursePoll::PollsValidator.new(different_post).validate_polls
|
||||||
|
|
||||||
|
message = MessageBus.track_publish do
|
||||||
|
described_class.update(post, different_polls)
|
||||||
|
end.first
|
||||||
|
|
||||||
|
expect(post.reload.custom_fields[DiscoursePoll::POLLS_CUSTOM_FIELD]).to eq(different_polls)
|
||||||
|
expect(message.data[:post_id]).to eq(post.id)
|
||||||
|
expect(message.data[:polls]).to eq(different_polls)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.extract_option_ids' do
|
||||||
|
it 'should return an array of the options id' do
|
||||||
|
expect(described_class.extract_option_ids(polls)).to eq(
|
||||||
|
["4d8a15e3cc35750f016ce15a43937620", "cd314db7dfbac2b10687b6f39abfdf41"]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '.total_votes' do
|
||||||
|
let!(:post) do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
|
||||||
|
[poll name=test]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
Fabricate(:post, raw: raw)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return the right number of votes" do
|
||||||
|
expect(described_class.total_votes(polls)).to eq(0)
|
||||||
|
|
||||||
|
polls.each { |key, value| value["voters"] = 2 }
|
||||||
|
|
||||||
|
expect(described_class.total_votes(polls)).to eq(4)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,225 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ::DiscoursePoll::PollsValidator do
|
||||||
|
let(:post) { Fabricate(:post) }
|
||||||
|
subject { described_class.new(post) }
|
||||||
|
|
||||||
|
describe "#validate_polls" do
|
||||||
|
it "should ensure that polls have unique names" do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.multiple_polls_without_name")
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll name=test]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
|
||||||
|
[poll name=test]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.multiple_polls_with_same_name", name: 'test')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should ensure that polls have unique options' do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
* 1
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.default_poll_must_have_different_options")
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll name=test]
|
||||||
|
* 1
|
||||||
|
* 1
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.named_poll_must_have_different_options", name: 'test')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
it 'should ensure that polls have at least 2 options' do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.default_poll_must_have_at_least_2_options")
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll name=test]
|
||||||
|
* 1
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.named_poll_must_have_at_least_2_options", name: 'test')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should ensure that polls' options do not exceed site settings" do
|
||||||
|
SiteSetting.poll_maximum_options = 2
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
* 3
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(I18n.t(
|
||||||
|
"poll.default_poll_must_have_less_options",
|
||||||
|
count: SiteSetting.poll_maximum_options
|
||||||
|
))
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll name=test]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
* 3
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(I18n.t(
|
||||||
|
"poll.named_poll_must_have_less_options",
|
||||||
|
name: 'test', count: SiteSetting.poll_maximum_options
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'multiple type polls' do
|
||||||
|
it "should ensure that min should not be greater than max" do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple min=2 max=1]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
* 3
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple min=2 max=1 name=test]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
* 3
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: 'test')
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should ensure max setting is greater than 0" do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple max=-2]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should ensure that max settings is smaller or equal to the number of options" do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple max=3]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should ensure that min settings is smaller than the number of options" do
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple min=2]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
|
||||||
|
)
|
||||||
|
|
||||||
|
raw = <<-RAW.strip_heredoc
|
||||||
|
[poll type=multiple min=3]
|
||||||
|
* 1
|
||||||
|
* 2
|
||||||
|
[/poll]
|
||||||
|
RAW
|
||||||
|
|
||||||
|
expect(post.update_attributes(raw: raw)).to eq(false)
|
||||||
|
|
||||||
|
expect(post.errors[:base]).to include(
|
||||||
|
I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue