diff --git a/app/components/admin/allowed_table_actions_component.html.erb b/app/components/admin/allowed_table_actions_component.html.erb new file mode 100644 index 000000000..df89fe8f7 --- /dev/null +++ b/app/components/admin/allowed_table_actions_component.html.erb @@ -0,0 +1,3 @@ +<%= render table_actions_component do %> + <%= content %> +<% end %> diff --git a/app/components/admin/allowed_table_actions_component.rb b/app/components/admin/allowed_table_actions_component.rb new file mode 100644 index 000000000..dc5f2e1d5 --- /dev/null +++ b/app/components/admin/allowed_table_actions_component.rb @@ -0,0 +1,20 @@ +class Admin::AllowedTableActionsComponent < ApplicationComponent + attr_reader :record, :options + delegate :can?, to: :helpers + delegate :action, to: :table_actions_component + + def initialize(record, **options) + @record = record + @options = options + end + + private + + def actions + (options[:actions] || [:edit, :destroy]).select { |action| can?(action, record) } + end + + def table_actions_component + @table_actions_component ||= Admin::TableActionsComponent.new(record, **options.merge(actions: actions)) + end +end diff --git a/app/components/admin/poll/questions/answers/documents/index_component.html.erb b/app/components/admin/poll/questions/answers/documents/index_component.html.erb index 9c6b95c20..6db626293 100644 --- a/app/components/admin/poll/questions/answers/documents/index_component.html.erb +++ b/app/components/admin/poll/questions/answers/documents/index_component.html.erb @@ -8,42 +8,38 @@
- <%= form_for(Poll::Question::Answer.new, url: admin_answer_documents_path(answer)) do |f| %> - <%= render "shared/errors", resource: answer %> + <% if can?(:update, @answer) %> + <%= form_for(Poll::Question::Answer.new, url: admin_answer_documents_path(answer)) do |f| %> + <%= render "shared/errors", resource: answer %> -
- <%= render "documents/nested_documents", f: f %> -
+
+ <%= render "documents/nested_documents", f: f %> +
-
- <%= f.submit(class: "button expanded", value: t("shared.save")) %> +
+ <%= f.submit(class: "button expanded", value: t("shared.save")) %> +
+ <% end %> + <% else %> +
+ <%= t("admin.questions.no_edit") %>
<% end %> - <% if answer.documents.present? %> + <% if documents.present? %> - <% answer.documents.each do |document| %> + <% documents.each do |document| %> <% end %> diff --git a/app/components/admin/poll/questions/answers/documents/index_component.rb b/app/components/admin/poll/questions/answers/documents/index_component.rb index ca72d4f98..b45c9856e 100644 --- a/app/components/admin/poll/questions/answers/documents/index_component.rb +++ b/app/components/admin/poll/questions/answers/documents/index_component.rb @@ -1,7 +1,14 @@ class Admin::Poll::Questions::Answers::Documents::IndexComponent < ApplicationComponent attr_reader :answer + delegate :can?, to: :helpers def initialize(answer) @answer = answer end + + private + + def documents + @documents ||= @answer.class.find(@answer.id).documents + end end diff --git a/app/components/admin/poll/questions/answers/documents/table_actions_component.html.erb b/app/components/admin/poll/questions/answers/documents/table_actions_component.html.erb new file mode 100644 index 000000000..19e8e3ac2 --- /dev/null +++ b/app/components/admin/poll/questions/answers/documents/table_actions_component.html.erb @@ -0,0 +1,8 @@ +<%= render Admin::AllowedTableActionsComponent.new(document, + destroy_path: document_path(document)) do |actions| %> + <%= actions.action(:download, + text: t("documents.buttons.download_document"), + path: document.attachment, + target: "_blank", + rel: "nofollow") %> +<% end %> diff --git a/app/components/admin/poll/questions/answers/documents/table_actions_component.rb b/app/components/admin/poll/questions/answers/documents/table_actions_component.rb new file mode 100644 index 000000000..da895ef1a --- /dev/null +++ b/app/components/admin/poll/questions/answers/documents/table_actions_component.rb @@ -0,0 +1,7 @@ +class Admin::Poll::Questions::Answers::Documents::TableActionsComponent < ApplicationComponent + attr_reader :document + + def initialize(document) + @document = document + end +end diff --git a/app/components/admin/poll/questions/answers/table_actions_component.html.erb b/app/components/admin/poll/questions/answers/table_actions_component.html.erb new file mode 100644 index 000000000..d401f1a36 --- /dev/null +++ b/app/components/admin/poll/questions/answers/table_actions_component.html.erb @@ -0,0 +1 @@ +<%= render Admin::AllowedTableActionsComponent.new(answer) %> diff --git a/app/components/admin/poll/questions/answers/table_actions_component.rb b/app/components/admin/poll/questions/answers/table_actions_component.rb new file mode 100644 index 000000000..94b60b92e --- /dev/null +++ b/app/components/admin/poll/questions/answers/table_actions_component.rb @@ -0,0 +1,7 @@ +class Admin::Poll::Questions::Answers::TableActionsComponent < ApplicationComponent + attr_reader :answer + + def initialize(answer) + @answer = answer + end +end diff --git a/app/components/admin/poll/questions/answers/videos/table_actions_component.html.erb b/app/components/admin/poll/questions/answers/videos/table_actions_component.html.erb new file mode 100644 index 000000000..e4ebef5f4 --- /dev/null +++ b/app/components/admin/poll/questions/answers/videos/table_actions_component.html.erb @@ -0,0 +1 @@ +<%= render Admin::AllowedTableActionsComponent.new(video) %> diff --git a/app/components/admin/poll/questions/answers/videos/table_actions_component.rb b/app/components/admin/poll/questions/answers/videos/table_actions_component.rb new file mode 100644 index 000000000..06e772b6b --- /dev/null +++ b/app/components/admin/poll/questions/answers/videos/table_actions_component.rb @@ -0,0 +1,7 @@ +class Admin::Poll::Questions::Answers::Videos::TableActionsComponent < ApplicationComponent + attr_reader :video + + def initialize(video) + @video = video + end +end diff --git a/app/components/admin/poll/questions/form_component.html.erb b/app/components/admin/poll/questions/form_component.html.erb new file mode 100644 index 000000000..fc183f186 --- /dev/null +++ b/app/components/admin/poll/questions/form_component.html.erb @@ -0,0 +1,38 @@ +<%= render "shared/globalize_locales", resource: question %> + +<%= translatable_form_for(question, url: url) do |f| %> + + <%= render "shared/errors", resource: question %> + + <%= f.hidden_field :proposal_id %> + +
+
+ <% if question.poll.present? %> + <%= f.hidden_field :poll_id, value: question.poll.id %> + <% else %> +
+ <%= f.select :poll_id, + options_for_select(select_options), + prompt: t("admin.questions.index.select_poll"), + hint: t("admin.questions.form.poll_help") %> +
+ <% end %> +
+
+ +
+ <%= f.translatable_fields do |translations_form| %> +
+ <%= translations_form.text_field :title %> +
+ <% end %> +
+ +
+
+ <%= f.submit(class: "button success expanded", value: t("shared.save")) %> +
+
+ +<% end %> diff --git a/app/components/admin/poll/questions/form_component.rb b/app/components/admin/poll/questions/form_component.rb new file mode 100644 index 000000000..5f54d84e2 --- /dev/null +++ b/app/components/admin/poll/questions/form_component.rb @@ -0,0 +1,19 @@ +class Admin::Poll::Questions::FormComponent < ApplicationComponent + include TranslatableFormHelper + include GlobalizeHelper + attr_reader :question, :url + delegate :can?, to: :helpers + + def initialize(question, url:) + @question = question + @url = url + end + + private + + def select_options + Poll.all.select { |poll| can?(:create, Poll::Question.new(poll: poll)) }.map do |poll| + [poll.name, poll.id] + end + end +end diff --git a/app/components/admin/poll/questions/table_actions_component.html.erb b/app/components/admin/poll/questions/table_actions_component.html.erb new file mode 100644 index 000000000..736eda7af --- /dev/null +++ b/app/components/admin/poll/questions/table_actions_component.html.erb @@ -0,0 +1,3 @@ +<%= render Admin::AllowedTableActionsComponent.new(question) do |actions| %> + <%= actions.action(:answers, text: t("admin.polls.show.edit_answers")) %> +<% end %> diff --git a/app/components/admin/poll/questions/table_actions_component.rb b/app/components/admin/poll/questions/table_actions_component.rb new file mode 100644 index 000000000..ba4eb66f4 --- /dev/null +++ b/app/components/admin/poll/questions/table_actions_component.rb @@ -0,0 +1,7 @@ +class Admin::Poll::Questions::TableActionsComponent < ApplicationComponent + attr_reader :question + + def initialize(question) + @question = question + end +end diff --git a/app/controllers/admin/poll/questions/answers/documents_controller.rb b/app/controllers/admin/poll/questions/answers/documents_controller.rb index 3210896d6..4fce04d05 100644 --- a/app/controllers/admin/poll/questions/answers/documents_controller.rb +++ b/app/controllers/admin/poll/questions/answers/documents_controller.rb @@ -8,12 +8,13 @@ class Admin::Poll::Questions::Answers::DocumentsController < Admin::Poll::BaseCo def create @answer.attributes = documents_params + authorize! :update, @answer if @answer.save redirect_to admin_answer_documents_path(@answer), notice: t("admin.documents.create.success_notice") else - render :new + render :index end end diff --git a/app/controllers/admin/poll/questions/answers/images_controller.rb b/app/controllers/admin/poll/questions/answers/images_controller.rb index a209b058f..9b60f3f4c 100644 --- a/app/controllers/admin/poll/questions/answers/images_controller.rb +++ b/app/controllers/admin/poll/questions/answers/images_controller.rb @@ -2,6 +2,7 @@ class Admin::Poll::Questions::Answers::ImagesController < Admin::Poll::BaseContr include ImageAttributes load_and_authorize_resource :answer, class: "::Poll::Question::Answer" + load_and_authorize_resource only: [:destroy] def index end @@ -11,6 +12,7 @@ class Admin::Poll::Questions::Answers::ImagesController < Admin::Poll::BaseContr def create @answer.attributes = images_params + authorize! :update, @answer if @answer.save redirect_to admin_answer_images_path(@answer), @@ -21,7 +23,6 @@ class Admin::Poll::Questions::Answers::ImagesController < Admin::Poll::BaseContr end def destroy - @image = ::Image.find(params[:id]) @image.destroy! respond_to do |format| diff --git a/app/controllers/admin/poll/questions/answers_controller.rb b/app/controllers/admin/poll/questions/answers_controller.rb index c015f1ebe..844f3d494 100644 --- a/app/controllers/admin/poll/questions/answers_controller.rb +++ b/app/controllers/admin/poll/questions/answers_controller.rb @@ -30,6 +30,11 @@ class Admin::Poll::Questions::AnswersController < Admin::Poll::BaseController end end + def destroy + @answer.destroy! + redirect_to admin_question_path(@question), notice: t("admin.answers.destroy.success_notice") + end + def order_answers ::Poll::Question::Answer.order_answers(params[:ordered_list]) head :ok diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb index 598e664e7..c0c8540f8 100644 --- a/app/controllers/admin/poll/questions_controller.rb +++ b/app/controllers/admin/poll/questions_controller.rb @@ -3,7 +3,8 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController include Translatable load_and_authorize_resource :poll - load_and_authorize_resource :question, class: "Poll::Question" + load_resource class: "Poll::Question" + authorize_resource except: [:new, :index] def index @polls = Poll.not_budget @@ -16,6 +17,9 @@ class Admin::Poll::QuestionsController < Admin::Poll::BaseController @polls = Poll.all proposal = Proposal.find(params[:proposal_id]) if params[:proposal_id].present? @question.copy_attributes_from_proposal(proposal) + @question.poll = @poll + + authorize! :create, @question end def create diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index e9a455c3c..f6e2bb038 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -88,12 +88,23 @@ module Abilities can [:search, :create, :index, :destroy], ::Poll::Officer can [:create, :destroy, :manage], ::Poll::BoothAssignment can [:create, :destroy], ::Poll::OfficerAssignment - can [:read, :create, :update], Poll::Question - can :destroy, Poll::Question - can :manage, Poll::Question::Answer - can :manage, Poll::Question::Answer::Video - can [:create, :destroy], Image do |image| - image.imageable_type == "Poll::Question::Answer" + can :read, Poll::Question + can [:create], Poll::Question do |question| + question.poll.blank? || !question.poll.started? + end + can [:update, :destroy], Poll::Question do |question| + !question.poll.started? + end + can [:read, :order_answers], Poll::Question::Answer + can [:create, :update, :destroy], Poll::Question::Answer do |answer| + can?(:update, answer.question) + end + can :read, Poll::Question::Answer::Video + can [:create, :update, :destroy], Poll::Question::Answer::Video do |video| + can?(:update, video.answer) + end + can [:destroy], Image do |image| + image.imageable_type == "Poll::Question::Answer" && can?(:update, image.imageable) end can :manage, SiteCustomization::Page @@ -113,7 +124,9 @@ module Abilities cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation, ::Legislation::Proposal] can [:create], Document - can [:destroy], Document, documentable_type: "Poll::Question::Answer" + can [:destroy], Document do |document| + document.documentable_type == "Poll::Question::Answer" && can?(:update, document.documentable) + end can [:create, :destroy], DirectUpload can [:deliver], Newsletter, hidden_at: nil diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 049a2c8b5..2da9a057d 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -71,10 +71,12 @@ module Abilities can [:create, :destroy], Follow, user_id: user.id can [:destroy], Document do |document| - document.documentable&.author_id == user.id + document.documentable_type != "Poll::Question::Answer" && document.documentable&.author_id == user.id end - can [:destroy], Image, imageable: { author_id: user.id } + can [:destroy], Image do |image| + image.imageable_type != "Poll::Question::Answer" && image.imageable&.author_id == user.id + end can [:create, :destroy], DirectUpload diff --git a/app/models/poll.rb b/app/models/poll.rb index 2acd9cfb0..18cba6d9b 100644 --- a/app/models/poll.rb +++ b/app/models/poll.rb @@ -37,6 +37,10 @@ class Poll < ApplicationRecord validates_translation :name, presence: true validate :date_range + validate :start_date_is_not_past_date, on: :create + validate :start_date_change, on: :update + validate :end_date_is_not_past_date, on: :update + validate :end_date_change, on: :update validate :only_one_active, unless: :public? accepts_nested_attributes_for :questions, reject_if: :all_blank, allow_destroy: true @@ -67,6 +71,10 @@ class Poll < ApplicationRecord name end + def started?(timestamp = Time.current) + starts_at.present? && starts_at < timestamp + end + def current?(timestamp = Time.current) starts_at <= timestamp && timestamp <= ends_at end @@ -143,6 +151,34 @@ class Poll < ApplicationRecord end end + def start_date_is_not_past_date + if starts_at.present? && starts_at < Time.current + errors.add(:starts_at, I18n.t("errors.messages.past_date")) + end + end + + def start_date_change + if will_save_change_to_starts_at? + if starts_at_in_database < Time.current + errors.add(:starts_at, I18n.t("errors.messages.cannot_change_date.poll_started")) + elsif starts_at < Time.current + errors.add(:starts_at, I18n.t("errors.messages.past_date")) + end + end + end + + def end_date_is_not_past_date + if will_save_change_to_ends_at? && ends_at < Time.current + errors.add(:ends_at, I18n.t("errors.messages.past_date")) + end + end + + def end_date_change + if will_save_change_to_ends_at? && ends_at_in_database < Time.current + errors.add(:ends_at, I18n.t("errors.messages.cannot_change_date.poll_ended")) + end + end + def generate_slug? slug.nil? end diff --git a/app/views/admin/poll/polls/_poll_header.html.erb b/app/views/admin/poll/polls/_poll_header.html.erb index c6dcd0e7c..fafabcbfc 100644 --- a/app/views/admin/poll/polls/_poll_header.html.erb +++ b/app/views/admin/poll/polls/_poll_header.html.erb @@ -23,3 +23,7 @@ <% end %> + +
+ <%= t("admin.questions.no_edit") %> +
diff --git a/app/views/admin/poll/polls/_questions.html.erb b/app/views/admin/poll/polls/_questions.html.erb index e7f1fc7a0..acae1cf4f 100644 --- a/app/views/admin/poll/polls/_questions.html.erb +++ b/app/views/admin/poll/polls/_questions.html.erb @@ -1,7 +1,9 @@

<%= t("admin.polls.show.questions_title") %>

-<%= link_to t("admin.questions.index.create"), new_admin_question_path(poll_id: @poll.id), - class: "button float-right" %> +<% if can?(:create, Poll::Question.new(poll: @poll)) %> + <%= link_to t("admin.questions.index.create"), new_admin_question_path(poll_id: @poll.id), + class: "button float-right" %> +<% end %> <% if @poll.questions.empty? %>
@@ -28,9 +30,7 @@ <% end %>
<% end %> diff --git a/app/views/admin/poll/questions/_form.html.erb b/app/views/admin/poll/questions/_form.html.erb index 5679830e4..c1254b5ca 100644 --- a/app/views/admin/poll/questions/_form.html.erb +++ b/app/views/admin/poll/questions/_form.html.erb @@ -1,40 +1 @@ -<%= render "shared/globalize_locales", resource: @question %> - -<%= translatable_form_for(@question, url: form_url) do |f| %> - - <%= render "shared/errors", resource: @question %> - - <%= f.hidden_field :proposal_id %> - -
-
- <% if @poll.present? %> - <%= f.hidden_field :poll_id, value: @poll.id %> - <% elsif @question.poll.present? %> - <%= f.hidden_field :poll_id, value: @question.poll.id %> - <% else %> -
- <% select_options = Poll.all.map { |p| [p.name, p.id] } %> - <%= f.select :poll_id, - options_for_select(select_options), - prompt: t("admin.questions.index.select_poll") %> -
- <% end %> -
-
- -
- <%= f.translatable_fields do |translations_form| %> -
- <%= translations_form.text_field :title %> -
- <% end %> -
- -
-
- <%= f.submit(class: "button success expanded", value: t("shared.save")) %> -
-
- -<% end %> +<%= render Admin::Poll::Questions::FormComponent.new(@question, url: form_url) %> diff --git a/app/views/admin/poll/questions/answers/images/index.html.erb b/app/views/admin/poll/questions/answers/images/index.html.erb index f031f2b1f..e3f2b3e92 100644 --- a/app/views/admin/poll/questions/answers/images/index.html.erb +++ b/app/views/admin/poll/questions/answers/images/index.html.erb @@ -1,8 +1,20 @@ <%= back_link_to admin_question_path(@answer.question) %> -<%= link_to t("admin.questions.answers.images.add_image"), - new_admin_answer_image_path(@answer), - class: "button hollow float-right" %> +
+ +

+ <%= t("admin.answers.images.index.title") %> +

+ +<% if can?(:update, @answer) %> + <%= link_to t("admin.questions.answers.images.add_image"), + new_admin_answer_image_path(@answer), + class: "button hollow float-right" %> +<% else %> +
+ <%= t("admin.questions.no_edit") %> +
+<% end %> <% end %> diff --git a/app/views/admin/poll/questions/show.html.erb b/app/views/admin/poll/questions/show.html.erb index 954767747..ec4e9cecc 100644 --- a/app/views/admin/poll/questions/show.html.erb +++ b/app/views/admin/poll/questions/show.html.erb @@ -1,7 +1,9 @@ <%= back_link_to admin_poll_path(@question.poll) %> -<%= link_to t("admin.questions.show.edit_question"), edit_admin_question_path(@question), - class: "button hollow float-right" %> +<% if can?(:update, @question) %> + <%= link_to t("admin.questions.show.edit_question"), edit_admin_question_path(@question), + class: "button hollow float-right" %> +<% end %>
@@ -30,8 +32,14 @@
- <%= link_to t("admin.questions.show.add_answer"), new_admin_question_answer_path(@question), - class: "button float-right" %> + <% if can?(:create, Poll::Question::Answer.new(question: @question)) %> + <%= link_to t("admin.questions.show.add_answer"), new_admin_question_answer_path(@question), + class: "button float-right" %> + <% else %> +
+ <%= t("admin.questions.no_edit") %> +
+ <% end %>
<%= t("admin.questions.show.answers.document_title") %> <%= t("admin.questions.show.answers.document_actions") %>
<%= link_to document.title, document.attachment %> - <%= render Admin::TableActionsComponent.new(document, - actions: [:destroy], - destroy_path: document_path(document) - ) do |actions| %> - <%= actions.action(:download, - text: t("documents.buttons.download_document"), - path: document.attachment, - target: "_blank", - rel: "nofollow") %> - - <% end %> + <%= render Admin::Poll::Questions::Answers::Documents::TableActionsComponent.new(document) %>
- <%= render Admin::TableActionsComponent.new(question) do |actions| %> - <%= actions.action(:answers, text: t("admin.polls.show.edit_answers")) %> - <% end %> + <%= render Admin::Poll::Questions::TableActionsComponent.new(question) %>
<%= video.title %> <%= link_to video.url, video.url %> - <%= render Admin::TableActionsComponent.new(video) %> + <%= render Admin::Poll::Questions::Answers::Videos::TableActionsComponent.new(video) %>
@@ -71,7 +79,7 @@ admin_answer_videos_path(answer) %> <% end %> diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index cfc5bf653..20635ee3a 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -528,10 +528,10 @@ en: size: "Size" url: "URL" create: - success_notice: "Document uploaded succesfully" + success_notice: "Document uploaded successfully" unable_notice: "Invalid document" destroy: - success_notice: "Document deleted succesfully" + success_notice: "Document deleted successfully" hidden_users: index: filter: Filter @@ -894,7 +894,7 @@ en: pending_to_be_sent: This is the content pending to be sent moderate_pending: Moderate notification send send_pending: Send pending - send_pending_notification: Pending notifications sent succesfully + send_pending_notification: Pending notifications sent successfully proposal_notification_digest: title: "Proposal notification digest" description: "Gathers all proposal notifications for an user in a single message, to avoid too much emails." @@ -1123,11 +1123,13 @@ en: poll_not_assigned: "Poll not assigned" edit: title: "Edit Question" + form: + poll_help: "You can only select polls that have not started yet" new: title: "Create question to poll %{poll}" title_proposal: "Create question" destroy: - notice: "Question deleted succesfully" + notice: "Question deleted successfully" answers: images: add_image: "Add image" @@ -1151,11 +1153,17 @@ en: documents_list: Documents list document_title: Title document_actions: Actions + no_edit: "Once the poll has started it will not be possible to create, edit or delete questions, answers or any content associated with the poll." answers: new: title: New answer edit: title: Edit answer + destroy: + success_notice: "Answer deleted successfully" + images: + index: + title: Images videos: index: title: Videos @@ -1308,7 +1316,7 @@ en: title: Map configuration help: Here you can customize the way the map is displayed to users. Drag map marker or click anywhere over the map, set desired zoom and click button "Update". flash: - update: Map configuration updated succesfully. + update: Map configuration updated successfully. form: submit: Update how_to_enable: 'To show the map to users you must enable "Proposals and budget investments geolocation" on "Features" tab.' diff --git a/config/locales/en/general.yml b/config/locales/en/general.yml index 96b0655c9..4a70c1a3e 100644 --- a/config/locales/en/general.yml +++ b/config/locales/en/general.yml @@ -137,6 +137,10 @@ en: allowed_file_content_types: "content type must be one of %{types}" user_not_found: User not found invalid_date_range: "Invalid date range" + past_date: "Must not be a past date" + cannot_change_date: + poll_started: "Cannot be changed if voting has already started" + poll_ended: "Cannot be changed if voting has already ended" form: accept_terms: I agree to the %{policy} and the %{conditions} accept_terms_title: I agree to the Privacy Policy and the Terms and conditions of use diff --git a/config/locales/en/responders.yml b/config/locales/en/responders.yml index 371cbaf71..be118de2a 100644 --- a/config/locales/en/responders.yml +++ b/config/locales/en/responders.yml @@ -26,12 +26,12 @@ en: poll_booth: "Booth updated successfully." active_poll: "Polls description updated successfully." proposal: "Proposal updated successfully." - budget_investment: "Investment project updated succesfully." + budget_investment: "Investment project updated successfully." topic: "Topic updated successfully." valuator_group: "Valuator group updated successfully" translation: "Translation updated successfully" destroy: - budget_investment: "Investment project deleted succesfully." + budget_investment: "Investment project deleted successfully." topic: "Topic deleted successfully." poll_question_answer_video: "Answer video deleted successfully." valuator_group: "Valuator group deleted successfully" diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 8e997a466..d40af634b 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -1122,6 +1122,8 @@ es: poll_not_assigned: "Votación no asignada" edit: title: "Editar pregunta ciudadana" + form: + poll_help: "Solo se pueden seleccionar votaciones que no hayan empezado" new: title: "Crear pregunta ciudadana para la votación %{poll}" title_proposal: "Crear pregunta ciudadana" @@ -1150,11 +1152,17 @@ es: documents_list: Lista de documentos document_title: Título document_actions: Acciones + no_edit: "Una vez comenzada la votación no será posible crear, editar o eliminar preguntas, respuestas o cualquier contenido asociado a la votación." answers: new: title: Nueva respuesta edit: title: Editar respuesta + destroy: + success_notice: "Respuesta eliminada correctamente" + images: + index: + title: Imágenes videos: index: title: Vídeos diff --git a/config/locales/es/general.yml b/config/locales/es/general.yml index ebfbd6749..d731f4bd4 100644 --- a/config/locales/es/general.yml +++ b/config/locales/es/general.yml @@ -137,6 +137,10 @@ es: allowed_file_content_types: "el tipo de contenido debe ser uno de los siguientes: %{types}" user_not_found: Usuario no encontrado invalid_date_range: "El rango de fechas no es válido" + past_date: "No puede ser una fecha pasada" + cannot_change_date: + poll_started: "No puede ser cambiada si la votación ha comenzado" + poll_ended: "No puede ser cambiada si la votación ha acabado" form: accept_terms: Acepto la %{policy} y las %{conditions} accept_terms_title: Acepto la Política de privacidad y las Condiciones de uso diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 84d3e6b68..56a2cf77e 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -169,7 +169,7 @@ namespace :admin do end resources :questions, shallow: true do - resources :answers, except: [:index, :show, :destroy], controller: "questions/answers", shallow: false + resources :answers, except: [:index, :show], controller: "questions/answers", shallow: false resources :answers, only: [], controller: "questions/answers" do resources :images, controller: "questions/answers/images" resources :videos, controller: "questions/answers/videos", shallow: false diff --git a/db/dev_seeds/polls.rb b/db/dev_seeds/polls.rb index 6ed23e601..a4bd79fba 100644 --- a/db/dev_seeds/polls.rb +++ b/db/dev_seeds/polls.rb @@ -2,30 +2,38 @@ require_dependency "poll/answer" require_dependency "poll/question/answer" section "Creating polls" do - Poll.create!(name: I18n.t("seeds.polls.current_poll"), + def create_poll!(attributes) + poll = Poll.create!(attributes.merge(starts_at: 1.day.from_now, ends_at: 2.days.from_now)) + poll.update_columns( + starts_at: attributes[:starts_at].beginning_of_minute, + ends_at: attributes[:ends_at].beginning_of_minute + ) + end + + create_poll!(name: I18n.t("seeds.polls.current_poll"), slug: I18n.t("seeds.polls.current_poll").parameterize, starts_at: 7.days.ago, ends_at: 7.days.from_now, geozone_restricted: false) - Poll.create!(name: I18n.t("seeds.polls.current_poll_geozone_restricted"), + create_poll!(name: I18n.t("seeds.polls.current_poll_geozone_restricted"), slug: I18n.t("seeds.polls.current_poll_geozone_restricted").parameterize, starts_at: 5.days.ago, ends_at: 5.days.from_now, geozone_restricted: true, geozones: Geozone.sample(3)) - Poll.create!(name: I18n.t("seeds.polls.recounting_poll"), + create_poll!(name: I18n.t("seeds.polls.recounting_poll"), slug: I18n.t("seeds.polls.recounting_poll").parameterize, starts_at: 15.days.ago, ends_at: 2.days.ago) - Poll.create!(name: I18n.t("seeds.polls.expired_poll_without_stats"), + create_poll!(name: I18n.t("seeds.polls.expired_poll_without_stats"), slug: I18n.t("seeds.polls.expired_poll_without_stats").parameterize, starts_at: 2.months.ago, ends_at: 1.month.ago) - Poll.create!(name: I18n.t("seeds.polls.expired_poll_with_stats"), + create_poll!(name: I18n.t("seeds.polls.expired_poll_with_stats"), slug: I18n.t("seeds.polls.expired_poll_with_stats").parameterize, starts_at: 2.months.ago, ends_at: 1.month.ago, diff --git a/spec/components/admin/allowed_table_actions_component_spec.rb b/spec/components/admin/allowed_table_actions_component_spec.rb new file mode 100644 index 000000000..6396be6b8 --- /dev/null +++ b/spec/components/admin/allowed_table_actions_component_spec.rb @@ -0,0 +1,57 @@ +require "rails_helper" + +describe Admin::AllowedTableActionsComponent, controller: Admin::BaseController do + before do + sign_in(create(:administrator).user) + allow_any_instance_of(Admin::AllowedTableActionsComponent).to receive(:can?).and_return true + end + let(:record) { create(:banner, title: "Important!") } + + it "renders edit and destroy actions by default if they're allowed" do + component = Admin::AllowedTableActionsComponent.new(record) + + render_inline component + + expect(page).to have_link count: 1 + expect(page).to have_link "Edit" + expect(page).to have_button count: 1 + expect(page).to have_button "Delete" + end + + it "accepts an actions parameter" do + render_inline Admin::AllowedTableActionsComponent.new(record, actions: [:edit]) + + expect(page).to have_link "Edit" + expect(page).not_to have_button "Delete" + end + + it "accepts custom options" do + render_inline Admin::AllowedTableActionsComponent.new(record, edit_text: "change", edit_path: "/myedit") + + expect(page).to have_link "change", href: "/myedit" + end + + it "accepts custom content" do + render_inline Admin::AllowedTableActionsComponent.new(record) do + "Main".html_safe + end + + expect(page).to have_link count: 2 + expect(page).to have_link "Main", href: "/" + expect(page).to have_link "Edit" + + expect(page).to have_button count: 1 + expect(page).to have_button "Delete" + end + + it "only renders the allowed actions" do + component = Admin::AllowedTableActionsComponent.new(record) + allow(component).to receive(:can?).with(:edit, record).and_return true + allow(component).to receive(:can?).with(:destroy, record).and_return false + + render_inline component + + expect(page).to have_link "Edit" + expect(page).not_to have_button "Delete" + end +end diff --git a/spec/components/admin/poll/questions/answers/documents/index_component_spec.rb b/spec/components/admin/poll/questions/answers/documents/index_component_spec.rb new file mode 100644 index 000000000..053c37ce5 --- /dev/null +++ b/spec/components/admin/poll/questions/answers/documents/index_component_spec.rb @@ -0,0 +1,19 @@ +require "rails_helper" + +describe Admin::Poll::Questions::Answers::Documents::IndexComponent do + before { sign_in(create(:administrator).user) } + let(:future_answer) { create(:poll_question_answer, poll: create(:poll, :future)) } + let(:current_answer) { create(:poll_question_answer, poll: create(:poll)) } + + it "displays the 'Add new document' link when the poll has not started" do + render_inline Admin::Poll::Questions::Answers::Documents::IndexComponent.new(future_answer) + + expect(page).to have_link "Add new document" + end + + it "does not display the 'Add new document' link when the poll has started" do + render_inline Admin::Poll::Questions::Answers::Documents::IndexComponent.new(current_answer) + + expect(page).not_to have_link "Add new document" + end +end diff --git a/spec/components/admin/poll/questions/answers/documents/table_actions_component_spec.rb b/spec/components/admin/poll/questions/answers/documents/table_actions_component_spec.rb new file mode 100644 index 000000000..2d2261aea --- /dev/null +++ b/spec/components/admin/poll/questions/answers/documents/table_actions_component_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +describe Admin::Poll::Questions::Answers::Documents::TableActionsComponent, controller: Admin::BaseController do + before { sign_in(create(:administrator).user) } + let(:future_answer) { create(:poll_question_answer, poll: create(:poll, :future)) } + let(:current_answer) { create(:poll_question_answer, poll: create(:poll)) } + + it "displays the destroy action when the poll has not started" do + document = create(:document, documentable: future_answer) + + render_inline Admin::Poll::Questions::Answers::Documents::TableActionsComponent.new(document) + + expect(page).to have_link "Download file" + expect(page).to have_button "Delete" + expect(page).not_to have_link "Edit" + end + + it "does not display the destroy action when the poll has started" do + document = create(:document, documentable: current_answer) + + render_inline Admin::Poll::Questions::Answers::Documents::TableActionsComponent.new(document) + + expect(page).to have_link "Download file" + expect(page).not_to have_button "Delete" + expect(page).not_to have_link "Edit" + end +end diff --git a/spec/components/admin/poll/questions/answers/table_actions_component_spec.rb b/spec/components/admin/poll/questions/answers/table_actions_component_spec.rb new file mode 100644 index 000000000..4e6080a11 --- /dev/null +++ b/spec/components/admin/poll/questions/answers/table_actions_component_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +describe Admin::Poll::Questions::Answers::TableActionsComponent, controller: Admin::BaseController do + before { sign_in(create(:administrator).user) } + + it "displays the edit and destroy actions when the poll has not started" do + answer = create(:poll_question_answer, poll: create(:poll, :future)) + + render_inline Admin::Poll::Questions::Answers::TableActionsComponent.new(answer) + + expect(page).to have_link "Edit" + expect(page).to have_button "Delete" + end + + it "does not display the edit and destroy actions when the poll has started" do + answer = create(:poll_question_answer, poll: create(:poll)) + + render_inline Admin::Poll::Questions::Answers::TableActionsComponent.new(answer) + + expect(page).not_to have_link "Edit" + expect(page).not_to have_button "Delete" + end +end diff --git a/spec/components/admin/poll/questions/answers/videos/table_actions_component_spec.rb b/spec/components/admin/poll/questions/answers/videos/table_actions_component_spec.rb new file mode 100644 index 000000000..e8449103d --- /dev/null +++ b/spec/components/admin/poll/questions/answers/videos/table_actions_component_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +describe Admin::Poll::Questions::Answers::Videos::TableActionsComponent, controller: Admin::BaseController do + before { sign_in(create(:administrator).user) } + + it "displays the edit and destroy actions when the poll has not started" do + video = create(:poll_answer_video, poll: create(:poll, :future)) + + render_inline Admin::Poll::Questions::Answers::Videos::TableActionsComponent.new(video) + + expect(page).to have_link "Edit" + expect(page).to have_button "Delete" + end + + it "does not display the edit and destroy actions when the poll has started" do + video = create(:poll_answer_video, poll: create(:poll)) + + render_inline Admin::Poll::Questions::Answers::Videos::TableActionsComponent.new(video) + + expect(page).not_to have_link "Edit" + expect(page).not_to have_button "Delete" + end +end diff --git a/spec/components/admin/poll/questions/form_component_spec.rb b/spec/components/admin/poll/questions/form_component_spec.rb new file mode 100644 index 000000000..13f38085f --- /dev/null +++ b/spec/components/admin/poll/questions/form_component_spec.rb @@ -0,0 +1,37 @@ +require "rails_helper" + +describe Admin::Poll::Questions::FormComponent do + before { sign_in(create(:administrator).user) } + + context "question with a poll" do + let(:poll) { create(:poll) } + let(:question) { Poll::Question.new(poll: poll) } + + it "does not display the poll selector" do + render_inline Admin::Poll::Questions::FormComponent.new(question, url: "/") + + expect(page).not_to have_select "Poll" + expect(page).to have_field "poll_question[poll_id]", type: :hidden, with: poll.id + end + end + + context "question without a poll" do + let(:question) { Poll::Question.new } + + it "allows selecting polls which have not already started" do + create(:poll, :future, name: "Future poll") + + render_inline Admin::Poll::Questions::FormComponent.new(question, url: "/") + + expect(page).to have_select "Poll", options: ["Select Poll", "Future poll"] + end + + it "does not allow selecting polls which have already started" do + create(:poll, name: "Already started poll") + + render_inline Admin::Poll::Questions::FormComponent.new(question, url: "/") + + expect(page).to have_select "Poll", options: ["Select Poll"] + end + end +end diff --git a/spec/components/admin/poll/questions/table_actions_component_spec.rb b/spec/components/admin/poll/questions/table_actions_component_spec.rb new file mode 100644 index 000000000..b1b84513b --- /dev/null +++ b/spec/components/admin/poll/questions/table_actions_component_spec.rb @@ -0,0 +1,25 @@ +require "rails_helper" + +describe Admin::Poll::Questions::TableActionsComponent, controller: Admin::BaseController do + before { sign_in(create(:administrator).user) } + + it "displays the edit and destroy actions when the poll has not started" do + question = create(:poll_question, poll: create(:poll, :future)) + + render_inline Admin::Poll::Questions::TableActionsComponent.new(question) + + expect(page).to have_link "Edit answers" + expect(page).to have_link "Edit" + expect(page).to have_button "Delete" + end + + it "does not display the edit and destroy actions when the poll has started" do + question = create(:poll_question, poll: create(:poll)) + + render_inline Admin::Poll::Questions::TableActionsComponent.new(question) + + expect(page).to have_link "Edit answers" + expect(page).not_to have_link "Edit" + expect(page).not_to have_button "Delete" + end +end diff --git a/spec/controllers/admin/poll/questions/answers/documents_controller_spec.rb b/spec/controllers/admin/poll/questions/answers/documents_controller_spec.rb new file mode 100644 index 000000000..84e02f0f6 --- /dev/null +++ b/spec/controllers/admin/poll/questions/answers/documents_controller_spec.rb @@ -0,0 +1,35 @@ +require "rails_helper" + +describe Admin::Poll::Questions::Answers::DocumentsController, :admin do + let(:current_answer) { create(:poll_question_answer, poll: create(:poll)) } + let(:future_answer) { create(:poll_question_answer, poll: create(:poll, :future)) } + + describe "POST create" do + let(:answer_attributes) do + { + documents_attributes: { + "0" => { + attachment: fixture_file_upload("clippy.pdf"), + title: "Title", + user_id: User.last.id + } + } + } + end + + it "is not possible for an already started poll" do + post :create, params: { poll_question_answer: answer_attributes, answer_id: current_answer } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'update' on Answer." + expect(Document.count).to eq 0 + end + + it "is possible for a not started poll" do + post :create, params: { poll_question_answer: answer_attributes, answer_id: future_answer } + + expect(response).to redirect_to admin_answer_documents_path(future_answer) + expect(flash[:notice]).to eq "Document uploaded successfully" + expect(Document.count).to eq 1 + end + end +end diff --git a/spec/controllers/admin/poll/questions/answers/images_controller_spec.rb b/spec/controllers/admin/poll/questions/answers/images_controller_spec.rb new file mode 100644 index 000000000..6e2c27b82 --- /dev/null +++ b/spec/controllers/admin/poll/questions/answers/images_controller_spec.rb @@ -0,0 +1,52 @@ +require "rails_helper" + +describe Admin::Poll::Questions::Answers::ImagesController, :admin do + let(:current_answer) { create(:poll_question_answer, poll: create(:poll)) } + let(:future_answer) { create(:poll_question_answer, poll: create(:poll, :future)) } + + describe "POST create" do + let(:answer_attributes) do + { + images_attributes: { + "0" => { + attachment: fixture_file_upload("clippy.jpg"), + title: "Title", + user_id: User.last.id + } + } + } + end + + it "is not possible for an already started poll" do + post :create, params: { poll_question_answer: answer_attributes, answer_id: current_answer } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'update' on Answer." + expect(Image.count).to eq 0 + end + + it "is possible for a not started poll" do + post :create, params: { poll_question_answer: answer_attributes, answer_id: future_answer } + + expect(response).to redirect_to admin_answer_images_path(future_answer) + expect(flash[:notice]).to eq "Image uploaded successfully" + expect(Image.count).to eq 1 + end + end + + describe "DELETE destroy" do + it "is not possible for an already started poll" do + current_image = create(:image, imageable: current_answer) + delete :destroy, xhr: true, params: { id: current_image } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'destroy' on Image." + expect(Image.count).to eq 1 + end + + it "is possible for a not started poll" do + future_image = create(:image, imageable: future_answer) + delete :destroy, xhr: true, params: { id: future_image } + + expect(Image.count).to eq 0 + end + end +end diff --git a/spec/controllers/admin/poll/questions/answers/videos_controller_spec.rb b/spec/controllers/admin/poll/questions/answers/videos_controller_spec.rb new file mode 100644 index 000000000..65c5dc1f8 --- /dev/null +++ b/spec/controllers/admin/poll/questions/answers/videos_controller_spec.rb @@ -0,0 +1,87 @@ +require "rails_helper" + +describe Admin::Poll::Questions::Answers::VideosController, :admin do + let(:current_answer) { create(:poll_question_answer, poll: create(:poll)) } + let(:future_answer) { create(:poll_question_answer, poll: create(:poll, :future)) } + + describe "POST create" do + it "is not possible for an already started poll" do + post :create, params: { + poll_question_answer_video: { + title: "Video from started poll", + url: "https://www.youtube.com/watch?v=-JMf43st-1A" + }, + answer_id: current_answer + } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'create' on Video." + expect(Poll::Question::Answer::Video.count).to eq 0 + end + + it "is possible for a not started poll" do + post :create, params: { + poll_question_answer_video: { + title: "Video from not started poll", + url: "https://www.youtube.com/watch?v=-JMf43st-1A" + }, + answer_id: future_answer + } + + expect(response).to redirect_to admin_answer_videos_path(future_answer) + expect(flash[:notice]).to eq "Video created successfully" + expect(Poll::Question::Answer::Video.count).to eq 1 + end + end + + describe "PATCH update" do + it "is not possible for an already started poll" do + current_video = create(:poll_answer_video, answer: current_answer, title: "Sample title") + + patch :update, params: { + poll_question_answer_video: { + title: "New title" + }, + id: current_video, + answer_id: current_answer + } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'update' on Video." + expect(current_video.reload.title).to eq "Sample title" + end + + it "is possible for a not started poll" do + future_video = create(:poll_answer_video, answer: future_answer) + + patch :update, params: { + poll_question_answer_video: { + title: "New title" + }, + id: future_video, + answer_id: future_answer + } + + expect(response).to redirect_to admin_answer_videos_path(future_answer) + expect(flash[:notice]).to eq "Changes saved" + expect(future_video.reload.title).to eq "New title" + end + end + + describe "DELETE destroy" do + it "is not possible for an already started poll" do + current_video = create(:poll_answer_video, answer: current_answer) + delete :destroy, params: { answer_id: current_answer, id: current_video } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'destroy' on Video." + expect(Poll::Question::Answer::Video.count).to eq 1 + end + + it "is possible for a not started poll" do + future_video = create(:poll_answer_video, answer: future_answer) + delete :destroy, params: { answer_id: future_answer, id: future_video } + + expect(response).to redirect_to admin_answer_videos_path(future_answer) + expect(flash[:notice]).to eq "Answer video deleted successfully." + expect(Poll::Question::Answer::Video.count).to eq 0 + end + end +end diff --git a/spec/controllers/admin/poll/questions/answers_controller_spec.rb b/spec/controllers/admin/poll/questions/answers_controller_spec.rb new file mode 100644 index 000000000..bdee4c59a --- /dev/null +++ b/spec/controllers/admin/poll/questions/answers_controller_spec.rb @@ -0,0 +1,107 @@ +require "rails_helper" + +describe Admin::Poll::Questions::AnswersController, :admin do + let(:current_question) { create(:poll_question, poll: create(:poll)) } + let(:future_question) { create(:poll_question, poll: create(:poll, :future)) } + + describe "POST create" do + it "is not possible for an already started poll" do + post :create, params: { + poll_question_answer: { + translations_attributes: { + "0" => { + locale: "en", + title: "Answer from started poll" + } + } + }, + question_id: current_question + } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'create' on Answer." + expect(Poll::Question::Answer.count).to eq 0 + end + + it "is possible for a not started poll" do + post :create, params: { + poll_question_answer: { + translations_attributes: { + "0" => { + locale: "en", + title: "Answer from future poll" + } + } + }, + question_id: future_question + } + + expect(response).to redirect_to admin_question_path(future_question) + expect(Poll::Question::Answer.last.title).to eq "Answer from future poll" + expect(Poll::Question::Answer.count).to eq 1 + end + end + + describe "PATCH update" do + it "is not possible for an already started poll" do + current_answer = create(:poll_question_answer, question: current_question, title: "Sample title") + + patch :update, params: { + poll_question_answer: { + translations_attributes: { + "0" => { + locale: "en", + title: "New title", + id: current_answer.translations.first.id + } + } + }, + question_id: current_question, + id: current_answer + } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'update' on Answer." + expect(current_answer.reload.title).to eq "Sample title" + end + + it "is possible for a not started poll" do + future_answer = create(:poll_question_answer, question: future_question) + + patch :update, params: { + poll_question_answer: { + translations_attributes: { + "0" => { + locale: "en", + title: "New title", + id: future_answer.translations.first.id + } + } + }, + question_id: future_question, + id: future_answer + } + + expect(response).to redirect_to admin_question_path(future_question) + expect(flash[:notice]).to eq "Changes saved" + expect(future_answer.reload.title).to eq "New title" + end + end + + describe "DELETE destroy" do + it "is not possible for an already started poll" do + current_answer = create(:poll_question_answer, question: current_question) + delete :destroy, params: { question_id: current_question, id: current_answer } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'destroy' on Answer." + expect(Poll::Question::Answer.count).to eq 1 + end + + it "is possible for a not started poll" do + future_answer = create(:poll_question_answer, question: future_question) + delete :destroy, params: { question_id: future_question, id: future_answer } + + expect(response).to redirect_to admin_question_path(future_question) + expect(flash[:notice]).to eq "Answer deleted successfully" + expect(Poll::Question::Answer.count).to eq 0 + end + end +end diff --git a/spec/controllers/admin/poll/questions_controller_spec.rb b/spec/controllers/admin/poll/questions_controller_spec.rb new file mode 100644 index 000000000..8358e7a3c --- /dev/null +++ b/spec/controllers/admin/poll/questions_controller_spec.rb @@ -0,0 +1,105 @@ +require "rails_helper" + +describe Admin::Poll::QuestionsController, :admin do + let(:current_poll) { create(:poll) } + let(:future_poll) { create(:poll, :future) } + + describe "POST create" do + it "is not possible for an already started poll" do + post :create, params: { + poll_question: { + translations_attributes: { + "0" => { + locale: "en", + title: "Question from started poll" + } + }, + poll_id: current_poll + } + } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'create' on Question." + expect(Poll::Question.count).to eq 0 + end + + it "is possible for a not started poll" do + post :create, params: { + poll_question: { + translations_attributes: { + "0" => { + locale: "en", + title: "Question from future poll" + } + }, + poll_id: future_poll + } + } + + expect(response).to redirect_to admin_question_path(Poll::Question.last) + expect(Poll::Question.last.title).to eq "Question from future poll" + expect(Poll::Question.count).to eq 1 + end + end + + describe "PATCH update" do + it "is not possible for an already started poll" do + current_question = create(:poll_question, poll: current_poll, title: "Sample title") + + patch :update, params: { + poll_question: { + translations_attributes: { + "0" => { + locale: "en", + title: "New title", + id: current_question.translations.first.id + } + } + }, + id: current_question + } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'update' on Question." + expect(current_question.reload.title).to eq "Sample title" + end + + it "is possible for a not started poll" do + future_question = create(:poll_question, poll: future_poll) + + patch :update, params: { + poll_question: { + translations_attributes: { + "0" => { + locale: "en", + title: "New title", + id: future_question.translations.first.id + } + } + }, + id: future_question + } + + expect(response).to redirect_to admin_question_path(future_question) + expect(flash[:notice]).to eq "Changes saved" + expect(future_question.reload.title).to eq "New title" + end + end + + describe "DELETE destroy" do + it "is not possible for an already started poll" do + current_question = create(:poll_question, poll: current_poll) + delete :destroy, params: { id: current_question } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'destroy' on Question." + expect(Poll::Question.count).to eq 1 + end + + it "is possible for a not started poll" do + future_question = create(:poll_question, poll: future_poll) + delete :destroy, params: { id: future_question } + + expect(response).to redirect_to admin_poll_path(future_poll) + expect(flash[:notice]).to eq "Question deleted successfully" + expect(Poll::Question.count).to eq 0 + end + end +end diff --git a/spec/controllers/documents_controller_spec.rb b/spec/controllers/documents_controller_spec.rb new file mode 100644 index 000000000..9b0977034 --- /dev/null +++ b/spec/controllers/documents_controller_spec.rb @@ -0,0 +1,28 @@ +require "rails_helper" + +describe DocumentsController do + describe "DELETE destroy" do + context "Poll answers administration", :admin do + let(:current_answer) { create(:poll_question_answer, poll: create(:poll)) } + let(:future_answer) { create(:poll_question_answer, poll: create(:poll, :future)) } + + it "is not possible for an already started poll" do + document = create(:document, documentable: current_answer) + delete :destroy, params: { id: document } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action 'destroy' on Document." + expect(Document.count).to eq 1 + end + + it "is possible for a not started poll" do + document = create(:document, documentable: future_answer) + request.env["HTTP_REFERER"] = admin_answer_documents_path(future_answer) + delete :destroy, params: { id: document } + + expect(response).to redirect_to admin_answer_documents_path(future_answer) + expect(flash[:notice]).to eq "Document was deleted successfully." + expect(Document.count).to eq 0 + end + end + end +end diff --git a/spec/factories/polls.rb b/spec/factories/polls.rb index 7820532f7..03ad24670 100644 --- a/spec/factories/polls.rb +++ b/spec/factories/polls.rb @@ -6,6 +6,7 @@ FactoryBot.define do starts_at { 1.month.ago } ends_at { 1.month.from_now } + to_create { |poll| poll.save(validate: false) } trait :expired do starts_at { 1.month.ago } @@ -17,6 +18,10 @@ FactoryBot.define do ends_at { 2.months.ago } end + trait :future do + starts_at { 1.day.from_now } + end + trait :published do published { true } end @@ -75,6 +80,10 @@ FactoryBot.define do trait :with_video do after(:create) { |answer| create(:poll_answer_video, answer: answer) } end + + factory :future_poll_question_answer do + poll { association(:poll, :future) } + end end factory :poll_answer_video, class: "Poll::Question::Answer::Video" do diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index 72e1722ee..4579aa4c8 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -16,10 +16,18 @@ describe Abilities::Administrator do let(:budget_investment) { create(:budget_investment) } let(:finished_investment) { create(:budget_investment, budget: create(:budget, :finished)) } let(:legislation_question) { create(:legislation_question) } - let(:poll) { create(:poll) } - let(:poll_question) { create(:poll_question) } - let(:poll_question_answer) { create(:poll_question_answer) } - let(:answer_image) { build(:image, imageable: poll_question_answer) } + let(:current_poll) { create(:poll) } + let(:future_poll) { create(:poll, :future) } + let(:current_poll_question) { create(:poll_question) } + let(:future_poll_question) { create(:poll_question, poll: future_poll) } + let(:current_poll_question_answer) { create(:poll_question_answer) } + let(:future_poll_question_answer) { create(:poll_question_answer, poll: future_poll) } + let(:current_poll_answer_video) { create(:poll_answer_video, answer: current_poll_question_answer) } + let(:future_poll_answer_video) { create(:poll_answer_video, answer: future_poll_question_answer) } + let(:current_poll_answer_image) { build(:image, imageable: current_poll_question_answer) } + let(:future_poll_answer_image) { build(:image, imageable: future_poll_question_answer) } + let(:current_poll_answer_document) { build(:document, documentable: current_poll_question_answer) } + let(:future_poll_answer_document) { build(:document, documentable: future_poll_question_answer) } let(:past_process) { create(:legislation_process, :past) } let(:past_draft_process) { create(:legislation_process, :past, :not_published) } @@ -27,7 +35,7 @@ describe Abilities::Administrator do let(:proposal_document) { build(:document, documentable: proposal, user: proposal.author) } let(:budget_investment_document) { build(:document, documentable: budget_investment) } - let(:poll_question_document) { build(:document, documentable: poll_question) } + let(:poll_question_document) { build(:document, documentable: current_poll_question) } let(:proposal_image) { build(:image, imageable: proposal, user: proposal.author) } let(:budget_investment_image) { build(:image, imageable: budget_investment) } @@ -74,8 +82,8 @@ describe Abilities::Administrator do it { should be_able_to(:comment_as_administrator, legislation_question) } it { should_not be_able_to(:comment_as_moderator, legislation_question) } - it { should be_able_to(:comment_as_administrator, poll) } - it { should_not be_able_to(:comment_as_moderator, poll) } + it { should be_able_to(:comment_as_administrator, current_poll) } + it { should_not be_able_to(:comment_as_moderator, current_poll) } it { should be_able_to(:summary, past_process) } it { should_not be_able_to(:summary, past_draft_process) } @@ -113,15 +121,34 @@ describe Abilities::Administrator do it { should be_able_to(:manage, Dashboard::Action) } it { should be_able_to(:read, Poll::Question) } - it { should be_able_to(:create, Poll::Question) } - it { should be_able_to(:update, Poll::Question) } + it { should be_able_to(:create, future_poll_question) } + it { should be_able_to(:update, future_poll_question) } + it { should be_able_to(:destroy, future_poll_question) } + it { should_not be_able_to(:create, current_poll_question) } + it { should_not be_able_to(:update, current_poll_question) } + it { should_not be_able_to(:destroy, current_poll_question) } - it { should be_able_to(:manage, Poll::Question::Answer) } + it { should be_able_to(:read, Poll::Question::Answer) } + it { should be_able_to(:order_answers, Poll::Question::Answer) } + it { should be_able_to(:create, future_poll_question_answer) } + it { should be_able_to(:update, future_poll_question_answer) } + it { should be_able_to(:destroy, future_poll_question_answer) } + it { should_not be_able_to(:create, current_poll_question_answer) } + it { should_not be_able_to(:update, current_poll_question_answer) } + it { should_not be_able_to(:destroy, current_poll_question_answer) } - it { should be_able_to(:manage, Poll::Question::Answer::Video) } + it { should be_able_to(:create, future_poll_answer_video) } + it { should be_able_to(:update, future_poll_answer_video) } + it { should be_able_to(:destroy, future_poll_answer_video) } + it { should_not be_able_to(:create, current_poll_answer_video) } + it { should_not be_able_to(:update, current_poll_answer_video) } + it { should_not be_able_to(:destroy, current_poll_answer_video) } - it { should be_able_to(:create, answer_image) } - it { should be_able_to(:destroy, answer_image) } + it { should be_able_to(:destroy, future_poll_answer_image) } + it { should_not be_able_to(:destroy, current_poll_answer_image) } + + it { should be_able_to(:destroy, future_poll_answer_document) } + it { should_not be_able_to(:destroy, current_poll_answer_document) } it { is_expected.to be_able_to :manage, Dashboard::AdministratorTask } it { is_expected.to be_able_to :manage, dashboard_administrator_task } diff --git a/spec/models/poll/poll_spec.rb b/spec/models/poll/poll_spec.rb index 7d48c502f..00f97602e 100644 --- a/spec/models/poll/poll_spec.rb +++ b/spec/models/poll/poll_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" describe Poll do - let(:poll) { build(:poll) } + let(:poll) { build(:poll, :future) } describe "Concerns" do it_behaves_like "notifiable" @@ -22,7 +22,9 @@ describe Poll do it "is not valid without a start date" do poll.starts_at = nil + expect(poll).not_to be_valid + expect(poll.errors[:starts_at]).to eq ["Invalid date range"] end it "is not valid without an end date" do @@ -35,11 +37,70 @@ describe Poll do poll.ends_at = 2.months.ago expect(poll).not_to be_valid end + + it "is valid if start date is greater than current time" do + poll.starts_at = 1.minute.from_now + expect(poll).to be_valid + end + + it "is not valid if start date is a past date" do + poll.starts_at = 1.minute.ago + + expect(poll).not_to be_valid + expect(poll.errors[:starts_at]).to eq ["Must not be a past date"] + end + + context "persisted poll" do + let(:poll) { create(:poll, :future) } + + it "is valid if the start date changes to a future date" do + poll.starts_at = 1.minute.from_now + expect(poll).to be_valid + end + + it "is not valid if the start date changes to a past date" do + poll.starts_at = 1.minute.ago + expect(poll).not_to be_valid + end + + it "is not valid if changing the start date for an already started poll" do + poll = create(:poll, starts_at: 10.days.ago) + + poll.starts_at = 10.days.from_now + expect(poll).not_to be_valid + end + + it "is valid if changing the end date for a non-expired poll to a future date" do + poll.ends_at = 1.day.from_now + expect(poll).to be_valid + end + + it "is not valid if changing the end date to a past date" do + poll = create(:poll, starts_at: 10.days.ago, ends_at: 10.days.from_now) + + poll.ends_at = 1.day.ago + expect(poll).not_to be_valid + end + + it "is valid if the past end date is the same as it was" do + poll = create(:poll, starts_at: 3.days.ago, ends_at: 2.days.ago) + poll.ends_at = poll.ends_at + + expect(poll).to be_valid + end + + it "is not valid if changing the end date for an expired poll" do + poll = create(:poll, :expired) + + poll.ends_at = 1.day.from_now + expect(poll).not_to be_valid + end + end end describe "proposal polls specific validations" do let(:proposal) { create(:proposal) } - let(:poll) { build(:poll, related: proposal) } + let(:poll) { build(:poll, :future, related: proposal) } it "is valid when overlapping but different proposals" do other_proposal = create(:proposal) diff --git a/spec/system/admin/budget_investments_spec.rb b/spec/system/admin/budget_investments_spec.rb index 82af1feb0..c0de69ff7 100644 --- a/spec/system/admin/budget_investments_spec.rb +++ b/spec/system/admin/budget_investments_spec.rb @@ -1069,7 +1069,7 @@ describe "Admin budget investments", :admin do select "Marta desc (marta@admins.org)", from: "budget_investment[administrator_id]" click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." expect(page).to have_content "Assigned administrator: Marta" end @@ -1102,7 +1102,7 @@ describe "Admin budget investments", :admin do click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." within("#assigned_valuators") do expect(page).to have_content("Valentina (v1@valuators.org)") @@ -1127,7 +1127,7 @@ describe "Admin budget investments", :admin do click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." within("#assigned_valuator_groups") do expect(page).to have_content("Health") @@ -1151,7 +1151,7 @@ describe "Admin budget investments", :admin do click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." within("#assigned_valuator_groups") { expect(page).to have_content("Health") } within("#assigned_valuators") do @@ -1173,7 +1173,7 @@ describe "Admin budget investments", :admin do click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." within "#tags" do expect(page).to have_content "Education" @@ -1190,7 +1190,7 @@ describe "Admin budget investments", :admin do fill_in "budget_investment_valuation_tag_list", with: "Refugees, Solidarity" click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." within "#tags" do expect(page).to have_content "Refugees" @@ -1216,7 +1216,7 @@ describe "Admin budget investments", :admin do fill_in "budget_investment_valuation_tag_list", with: "Education, Environment" click_button "Update" - expect(page).to have_content "Investment project updated succesfully" + expect(page).to have_content "Investment project updated successfully" visit admin_budget_budget_investment_path(budget_investment.budget, budget_investment) @@ -1244,7 +1244,7 @@ describe "Admin budget investments", :admin do fill_in "budget_investment_valuation_tag_list", with: "Refugees, Solidarity" click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." visit budget_investment_path(budget_investment.budget, budget_investment) expect(page).to have_content "Park" @@ -1314,7 +1314,7 @@ describe "Admin budget investments", :admin do click_button "Update" - expect(page).to have_content "Investment project updated succesfully." + expect(page).to have_content "Investment project updated successfully." expect(page).to have_content("Milestone Tags: tag1, tag2") end end diff --git a/spec/system/admin/poll/polls_spec.rb b/spec/system/admin/poll/polls_spec.rb index 23f023e1f..202c6b394 100644 --- a/spec/system/admin/poll/polls_spec.rb +++ b/spec/system/admin/poll/polls_spec.rb @@ -75,8 +75,8 @@ describe "Admin polls", :admin do end scenario "Edit" do - travel_to(Time.zone.local(2015, 7, 15, 13, 32, 13)) - poll = create(:poll, :with_image, ends_at: 1.month.from_now.beginning_of_minute) + travel_to(Time.zone.local(2015, 7, 15, 13, 32, 00)) + poll = create(:poll, :with_image, ends_at: 1.month.from_now) visit admin_poll_path(poll) click_link "Edit poll" @@ -553,7 +553,7 @@ describe "Admin polls", :admin do end scenario "edit poll with sdg related list" do - poll = create(:poll, name: "Upcoming poll with SDG related content") + poll = create(:poll, :future, name: "Upcoming poll with SDG related content") poll.sdg_goals = [SDG::Goal[1], SDG::Goal[17]] visit edit_admin_poll_path(poll) diff --git a/spec/system/admin/poll/questions/answers/answers_spec.rb b/spec/system/admin/poll/questions/answers/answers_spec.rb index 3b12ce770..21e53e2b1 100644 --- a/spec/system/admin/poll/questions/answers/answers_spec.rb +++ b/spec/system/admin/poll/questions/answers/answers_spec.rb @@ -1,40 +1,55 @@ require "rails_helper" describe "Answers", :admin do - scenario "Create" do - question = create(:poll_question) + let(:future_poll) { create(:poll, :future) } + let(:current_poll) { create(:poll) } - visit admin_question_path(question) - click_link "Add answer" + describe "Create" do + scenario "Is possible for a not started poll" do + question = create(:poll_question, poll: future_poll) - expect(page).to have_link "Go back", href: admin_question_path(question) + visit admin_question_path(question) + click_link "Add answer" - fill_in "Answer", with: "The answer is always 42" - fill_in_ckeditor "Description", with: "The Hitchhiker's Guide To The Universe" + expect(page).to have_link "Go back", href: admin_question_path(question) - click_button "Save" + fill_in "Answer", with: "The answer is always 42" + fill_in_ckeditor "Description", with: "The Hitchhiker's Guide To The Universe" - expect(page).to have_content "The answer is always 42" - expect(page).to have_content "The Hitchhiker's Guide To The Universe" - end + click_button "Save" - scenario "Create second answer and place after the first one" do - question = create(:poll_question) - create(:poll_question_answer, title: "First", question: question, given_order: 1) + expect(page).to have_content "Answer created successfully" + expect(page).to have_content "The answer is always 42" + expect(page).to have_content "The Hitchhiker's Guide To The Universe" + end - visit admin_question_path(question) - click_link "Add answer" + scenario "Is not possible for an already started poll" do + question = create(:poll_question, poll: current_poll) - fill_in "Answer", with: "Second" - fill_in_ckeditor "Description", with: "Description" + visit admin_question_path(question) - click_button "Save" + expect(page).not_to have_link "Add answer" + expect(page).to have_content "Once the poll has started it will not be possible to create, edit or" + end - expect("First").to appear_before("Second") + scenario "Create second answer and place after the first one" do + question = create(:poll_question, poll: future_poll) + create(:poll_question_answer, title: "First", question: question, given_order: 1) + + visit admin_question_path(question) + click_link "Add answer" + + fill_in "Answer", with: "Second" + fill_in_ckeditor "Description", with: "Description" + + click_button "Save" + + expect("First").to appear_before("Second") + end end scenario "Update" do - question = create(:poll_question) + question = create(:poll_question, poll: future_poll) create(:poll_question_answer, question: question, title: "Answer title", given_order: 2) create(:poll_question_answer, question: question, title: "Another title", given_order: 1) @@ -56,6 +71,21 @@ describe "Answers", :admin do expect("Another title").to appear_before("New title") end + scenario "Destroy" do + answer = create(:poll_question_answer, poll: future_poll, title: "I'm not useful") + + visit admin_question_path(answer.question) + + within("tr", text: "I'm not useful") do + accept_confirm("Are you sure? This action will delete \"I'm not useful\" and can't be undone.") do + click_button "Delete" + end + end + + expect(page).to have_content "Answer deleted successfully" + expect(page).not_to have_content "I'm not useful" + end + scenario "Reorder" do question = create(:poll_question) create(:poll_question_answer, question: question, title: "First", given_order: 1) diff --git a/spec/system/admin/poll/questions/answers/documents/documents_spec.rb b/spec/system/admin/poll/questions/answers/documents/documents_spec.rb index 1ab90af53..cc6f55bc9 100644 --- a/spec/system/admin/poll/questions/answers/documents/documents_spec.rb +++ b/spec/system/admin/poll/questions/answers/documents/documents_spec.rb @@ -1,6 +1,8 @@ require "rails_helper" describe "Documents", :admin do + let(:future_poll) { create(:poll, :future) } + context "Index" do scenario "Answer with no documents" do answer = create(:poll_question_answer) @@ -22,20 +24,35 @@ describe "Documents", :admin do end end - scenario "Create document for answer" do - answer = create(:poll_question_answer) + describe "Create document for answer" do + scenario "with valid data" do + answer = create(:poll_question_answer, poll: future_poll) - visit admin_answer_documents_path(answer) + visit admin_answer_documents_path(answer) - documentable_attach_new_file(Rails.root.join("spec/fixtures/files/clippy.pdf")) - click_button "Save" + documentable_attach_new_file(Rails.root.join("spec/fixtures/files/clippy.pdf")) + click_button "Save" - expect(page).to have_content "Document uploaded succesfully" - expect(page).to have_link "clippy.pdf" + expect(page).to have_content "Document uploaded successfully" + expect(page).to have_link "clippy.pdf" + end + + scenario "with invalid data" do + answer = create(:poll_question_answer, poll: future_poll) + + visit admin_answer_documents_path(answer) + + documentable_attach_new_file(Rails.root.join("spec/fixtures/files/clippy.pdf")) + fill_in "Title", with: "" + click_button "Save" + + expect(page).to have_content "1 error prevented this Answer from being saved" + expect(page).to have_content "Documents list" + end end scenario "Remove document from answer" do - answer = create(:poll_question_answer) + answer = create(:poll_question_answer, poll: future_poll) document = create(:document, documentable: answer) visit admin_answer_documents_path(answer) @@ -45,6 +62,7 @@ describe "Documents", :admin do click_button "Delete" end + expect(page).to have_content "Document was deleted successfully." expect(page).not_to have_content(document.title) end end diff --git a/spec/system/admin/poll/questions/answers/images/images_spec.rb b/spec/system/admin/poll/questions/answers/images/images_spec.rb index 154e2e8fd..cfbc12293 100644 --- a/spec/system/admin/poll/questions/answers/images/images_spec.rb +++ b/spec/system/admin/poll/questions/answers/images/images_spec.rb @@ -1,8 +1,11 @@ require "rails_helper" describe "Images", :admin do + let(:future_poll) { create(:poll, :future) } + let(:current_poll) { create(:poll) } + it_behaves_like "nested imageable", - "poll_question_answer", + "future_poll_question_answer", "new_admin_answer_image_path", { answer_id: "id" }, nil, @@ -30,34 +33,63 @@ describe "Images", :admin do end end - scenario "Add image to answer" do - answer = create(:poll_question_answer) + describe "Add image to answer" do + scenario "Is possible for a not started poll" do + answer = create(:poll_question_answer, poll: future_poll) - visit admin_answer_images_path(answer) - expect(page).not_to have_css("img[title='clippy.jpg']") - expect(page).not_to have_content("clippy.jpg") + visit admin_answer_images_path(answer) - visit new_admin_answer_image_path(answer) - imageable_attach_new_file(file_fixture("clippy.jpg")) - click_button "Save image" + expect(page).not_to have_css "img[title='clippy.jpg']" + expect(page).not_to have_content "clippy.jpg" - expect(page).to have_css("img[title='clippy.jpg']") - expect(page).to have_content("clippy.jpg") - end + click_link "Add image" + expect(page).to have_content "Descriptive image" - scenario "Remove image from answer" do - answer = create(:poll_question_answer) - image = create(:image, imageable: answer) + imageable_attach_new_file(file_fixture("clippy.jpg")) + click_button "Save image" - visit admin_answer_images_path(answer) - expect(page).to have_css("img[title='#{image.title}']") - expect(page).to have_content(image.title) - - accept_confirm "Are you sure? Remove image \"#{image.title}\"" do - click_link "Remove image" + expect(page).to have_content "Image uploaded successfully" + expect(page).to have_css "img[title='clippy.jpg']" + expect(page).to have_content "clippy.jpg" end - expect(page).not_to have_css("img[title='#{image.title}']") - expect(page).not_to have_content(image.title) + scenario "Is not possible for an already started poll" do + answer = create(:poll_question_answer, poll: current_poll) + + visit admin_answer_images_path(answer) + + expect(page).not_to have_link "Add image" + expect(page).to have_content "Once the poll has started it will not be possible to create, edit or" + end + end + + describe "Remove image from answer" do + scenario "Is possible for a not started poll" do + answer = create(:poll_question_answer, poll: future_poll) + image = create(:image, imageable: answer) + + visit admin_answer_images_path(answer) + expect(page).to have_css "img[title='#{image.title}']" + expect(page).to have_content image.title + + accept_confirm "Are you sure? Remove image \"#{image.title}\"" do + click_link "Remove image" + end + + expect(page).not_to have_css "img[title='#{image.title}']" + expect(page).not_to have_content image.title + end + + scenario "Is not possible for an already started poll" do + answer = create(:poll_question_answer, poll: current_poll) + image = create(:image, imageable: answer) + + visit admin_answer_images_path(answer) + expect(page).to have_css "img[title='#{image.title}']" + expect(page).to have_content image.title + + expect(page).not_to have_link "Remove image" + expect(page).to have_content "Once the poll has started it will not be possible to create, edit or" + end end end diff --git a/spec/system/admin/poll/questions/answers/videos/videos_spec.rb b/spec/system/admin/poll/questions/answers/videos/videos_spec.rb index b4d2cdfab..67278fbdd 100644 --- a/spec/system/admin/poll/questions/answers/videos/videos_spec.rb +++ b/spec/system/admin/poll/questions/answers/videos/videos_spec.rb @@ -1,55 +1,72 @@ require "rails_helper" describe "Videos", :admin do - let!(:question) { create(:poll_question) } - let!(:answer) { create(:poll_question_answer, question: question) } + let(:future_poll) { create(:poll, :future) } + let(:current_poll) { create(:poll) } let(:title) { "'Magical' by Junko Ohashi" } let(:url) { "https://www.youtube.com/watch?v=-JMf43st-1A" } - scenario "Create" do - visit admin_question_path(question) + describe "Create" do + scenario "Is possible for a not started poll" do + question = create(:poll_question, poll: future_poll) + answer = create(:poll_question_answer, question: question) - within("#poll_question_answer_#{answer.id}") do - click_link "Video list" + visit admin_question_path(question) + + within("#poll_question_answer_#{answer.id}") do + click_link "Video list" + end + click_link "Add video" + + fill_in "Title", with: title + fill_in "External video", with: url + + click_button "Save" + + expect(page).to have_content "Video created successfully" + expect(page).to have_content title + expect(page).to have_content url end - click_link "Add video" - fill_in "Title", with: title - fill_in "External video", with: url + scenario "Is not possible for an already started poll" do + answer = create(:poll_question_answer, poll: current_poll) - click_button "Save" + visit admin_answer_videos_path(answer) - expect(page).to have_content title - expect(page).to have_content url + expect(page).not_to have_link "Add video" + expect(page).to have_content "Once the poll has started it will not be possible to create, edit or" + end end scenario "Update" do - video = create(:poll_answer_video, answer: answer) + video = create(:poll_answer_video, poll: future_poll) - visit edit_admin_answer_video_path(answer, video) + visit edit_admin_answer_video_path(video.answer, video) - expect(page).to have_link "Go back", href: admin_answer_videos_path(answer) + expect(page).to have_link "Go back", href: admin_answer_videos_path(video.answer) fill_in "Title", with: title fill_in "External video", with: url click_button "Save" + expect(page).to have_content "Changes saved" expect(page).to have_content title expect(page).to have_content url end scenario "Destroy" do - video = create(:poll_answer_video, answer: answer) + video = create(:poll_answer_video, poll: future_poll) - visit admin_answer_videos_path(answer) + visit admin_answer_videos_path(video.answer) - within("#poll_question_answer_video_#{video.id}") do + within("tr", text: video.title) do accept_confirm("Are you sure? This action will delete \"#{video.title}\" and can't be undone.") do click_button "Delete" end end expect(page).to have_content "Answer video deleted successfully." + expect(page).not_to have_content video.title end end diff --git a/spec/system/admin/poll/questions_spec.rb b/spec/system/admin/poll/questions_spec.rb index 14a622e37..99c81a34c 100644 --- a/spec/system/admin/poll/questions_spec.rb +++ b/spec/system/admin/poll/questions_spec.rb @@ -2,13 +2,11 @@ require "rails_helper" describe "Admin poll questions", :admin do scenario "Index" do - poll1 = create(:poll) - poll2 = create(:poll) - poll3 = create(:poll) + poll1 = create(:poll, :future) + poll2 = create(:poll, :future) proposal = create(:proposal) question1 = create(:poll_question, poll: poll1) - question2 = create(:poll_question, poll: poll2) - question3 = create(:poll_question, poll: poll3, proposal: proposal) + question2 = create(:poll_question, poll: poll2, proposal: proposal) visit admin_poll_path(poll1) expect(page).to have_content(poll1.name) @@ -25,17 +23,7 @@ describe "Admin poll questions", :admin do within("#poll_question_#{question2.id}") do expect(page).to have_content question2.title - expect(page).to have_link "Edit answers" - expect(page).to have_link "Edit" - expect(page).to have_button "Delete" - end - - visit admin_poll_path(poll3) - expect(page).to have_content(poll3.name) - - within("#poll_question_#{question3.id}") do - expect(page).to have_content question3.title - expect(page).to have_link "(See proposal)", href: proposal_path(question3.proposal) + expect(page).to have_link "(See proposal)", href: proposal_path(question2.proposal) expect(page).to have_link "Edit answers" expect(page).to have_link "Edit" expect(page).to have_button "Delete" @@ -55,25 +43,32 @@ describe "Admin poll questions", :admin do expect(page).to have_content question.author.name end - scenario "Create" do - poll = create(:poll, name: "Movies") - title = "Star Wars: Episode IV - A New Hope" + describe "Create" do + scenario "Is possible for a not started poll" do + poll = create(:poll, :future, name: "Movies") - visit admin_poll_path(poll) - click_link "Create question" + visit admin_poll_path(poll) + click_link "Create question" - expect(page).to have_content("Create question to poll Movies") - expect(page).to have_selector("input[id='poll_question_poll_id'][value='#{poll.id}']", - visible: :hidden) - fill_in "Question", with: title + expect(page).to have_content("Create question to poll Movies") + expect(page).to have_selector("input[id='poll_question_poll_id'][value='#{poll.id}']", + visible: :hidden) - click_button "Save" + fill_in "Question", with: "Star Wars: Episode IV - A New Hope" + click_button "Save" - expect(page).to have_content(title) + expect(page).to have_content "Star Wars: Episode IV - A New Hope" + end + + scenario "Is not possible for an already started poll" do + visit admin_poll_path(create(:poll)) + + expect(page).not_to have_link "Create question" + end end scenario "Create from proposal" do - create(:poll, name: "Proposals") + create(:poll, :future, name: "Proposals") proposal = create(:proposal) visit admin_proposal_path(proposal) @@ -84,7 +79,7 @@ describe "Admin poll questions", :admin do expect(page).to have_current_path(new_admin_question_path, ignore_query: true) expect(page).to have_field("Question", with: proposal.title) - select "Proposals", from: "poll_question_poll_id" + select "Proposals", from: "Poll" click_button "Save" @@ -92,7 +87,7 @@ describe "Admin poll questions", :admin do end scenario "Create from successful proposal" do - create(:poll, name: "Proposals") + create(:poll, :future, name: "Proposals") proposal = create(:proposal, :successful) visit admin_proposal_path(proposal) @@ -103,7 +98,7 @@ describe "Admin poll questions", :admin do expect(page).to have_current_path(new_admin_question_path, ignore_query: true) expect(page).to have_field("Question", with: proposal.title) - select "Proposals", from: "poll_question_poll_id" + select "Proposals", from: "Poll" click_button "Save" @@ -115,29 +110,29 @@ describe "Admin poll questions", :admin do end scenario "Update" do - poll = create(:poll) - question1 = create(:poll_question, poll: poll) + poll = create(:poll, :future) + question = create(:poll_question, poll: poll) + old_title = question.title + new_title = "Vegetables are great and everyone should have one" visit admin_poll_path(poll) - within("#poll_question_#{question1.id}") do + within("#poll_question_#{question.id}") do click_link "Edit" end expect(page).to have_link "Go back", href: admin_poll_path(poll) - old_title = question1.title - new_title = "Potatoes are great and everyone should have one" fill_in "Question", with: new_title click_button "Save" expect(page).to have_content "Changes saved" expect(page).to have_content new_title - expect(page).not_to have_content(old_title) + expect(page).not_to have_content old_title end scenario "Destroy" do - poll = create(:poll) + poll = create(:poll, :future) question1 = create(:poll_question, poll: poll) question2 = create(:poll_question, poll: poll) @@ -156,7 +151,7 @@ describe "Admin poll questions", :admin do context "Poll select box" do scenario "translates the poll name in options" do - poll = create(:poll, name_en: "Name in English", name_es: "Nombre en Español") + poll = create(:poll, :future, name_en: "Name in English", name_es: "Nombre en Español") proposal = create(:proposal) visit admin_proposal_path(proposal) @@ -172,7 +167,7 @@ describe "Admin poll questions", :admin do scenario "uses fallback if name is not translated to current locale", if: Globalize.fallbacks(:fr).reject { |locale| locale.match(/fr/) }.first == :es do - poll = create(:poll, name_en: "Name in English", name_es: "Nombre en Español") + poll = create(:poll, :future, name_en: "Name in English", name_es: "Nombre en Español") proposal = create(:proposal) visit admin_proposal_path(proposal) diff --git a/spec/system/admin/settings_spec.rb b/spec/system/admin/settings_spec.rb index d4af85d41..f5da37d3e 100644 --- a/spec/system/admin/settings_spec.rb +++ b/spec/system/admin/settings_spec.rb @@ -81,7 +81,7 @@ describe "Admin settings", :admin do click_on "Update" end - expect(page).to have_content "Map configuration updated succesfully" + expect(page).to have_content "Map configuration updated successfully" end scenario "Should display marker by default" do @@ -104,7 +104,7 @@ describe "Admin settings", :admin do end expect(find("#latitude", visible: :hidden).value).not_to eq "51.48" - expect(page).to have_content "Map configuration updated succesfully" + expect(page).to have_content "Map configuration updated successfully" end end diff --git a/spec/system/admin/site_customization/documents_spec.rb b/spec/system/admin/site_customization/documents_spec.rb index 5e86779d6..5c284c525 100644 --- a/spec/system/admin/site_customization/documents_spec.rb +++ b/spec/system/admin/site_customization/documents_spec.rb @@ -57,7 +57,7 @@ describe "Documents", :admin do attach_file("document_attachment", file_fixture("logo.pdf")) click_button "Upload" - expect(page).to have_content "Document uploaded succesfully" + expect(page).to have_content "Document uploaded successfully" expect(page).to have_link "logo.pdf" end @@ -80,7 +80,7 @@ describe "Documents", :admin do end end - expect(page).to have_content "Document deleted succesfully" + expect(page).to have_content "Document deleted successfully" expect(page).not_to have_content document.title end end diff --git a/spec/system/admin/system_emails_spec.rb b/spec/system/admin/system_emails_spec.rb index 425c5b4d0..cce079532 100644 --- a/spec/system/admin/system_emails_spec.rb +++ b/spec/system/admin/system_emails_spec.rb @@ -356,7 +356,7 @@ describe "System Emails" do expect(email).to deliver_to(voter) expect(email).to have_body_text(proposal_notification.body) - expect(page).to have_content("Pending notifications sent succesfully") + expect(page).to have_content("Pending notifications sent successfully") end end end diff --git a/spec/system/admin/translatable_spec.rb b/spec/system/admin/translatable_spec.rb index 09602ae16..ca81e8e39 100644 --- a/spec/system/admin/translatable_spec.rb +++ b/spec/system/admin/translatable_spec.rb @@ -215,7 +215,7 @@ describe "Admin edit translatable records", :admin do end context "CKEditor fields" do - let(:translatable) { create(:poll_question_answer) } + let(:translatable) { create(:poll_question_answer, poll: create(:poll, :future)) } let(:path) { edit_admin_question_answer_path(translatable.question, translatable) } scenario "Changes the existing translation" do @@ -242,7 +242,7 @@ describe "Admin edit translatable records", :admin do end context "Change value of a translated field to blank" do - let(:translatable) { create(:poll) } + let(:translatable) { create(:poll, :future) } let(:path) { edit_admin_poll_path(translatable) } scenario "Updates the field to a blank value" do @@ -383,7 +383,7 @@ describe "Admin edit translatable records", :admin do end context "Remove a translation with invalid data" do - let(:translatable) { create(:poll_question) } + let(:translatable) { create(:poll_question, poll: create(:poll, :future)) } let(:path) { edit_admin_question_path(translatable) } scenario "Doesn't remove the translation" do diff --git a/spec/system/budgets/investments_spec.rb b/spec/system/budgets/investments_spec.rb index 2395be61c..ba3a20e22 100644 --- a/spec/system/budgets/investments_spec.rb +++ b/spec/system/budgets/investments_spec.rb @@ -702,7 +702,7 @@ describe "Budget Investments" do click_button "Update Investment" - expect(page).to have_content "Investment project updated succesfully" + expect(page).to have_content "Investment project updated successfully" expect(page).to have_content "Park improvements" end @@ -1157,7 +1157,7 @@ describe "Budget Investments" do accept_confirm { click_link("Delete") } end - expect(page).to have_content "Investment project deleted succesfully" + expect(page).to have_content "Investment project deleted successfully" visit user_path(user, tab: :budget_investments) diff --git a/spec/system/polls/results_spec.rb b/spec/system/polls/results_spec.rb index 057519922..2fbfe82a1 100644 --- a/spec/system/polls/results_spec.rb +++ b/spec/system/polls/results_spec.rb @@ -19,22 +19,19 @@ describe "Poll Results" do login_as user1 vote_for_poll_via_web(poll, question1, "Yes") vote_for_poll_via_web(poll, question2, "Blue") - expect(Poll::Voter.count).to eq(1) logout login_as user2 vote_for_poll_via_web(poll, question1, "Yes") vote_for_poll_via_web(poll, question2, "Green") - expect(Poll::Voter.count).to eq(2) logout login_as user3 vote_for_poll_via_web(poll, question1, "No") vote_for_poll_via_web(poll, question2, "Yellow") - expect(Poll::Voter.count).to eq(3) logout - poll.update!(ends_at: 1.day.ago) + travel_to(poll.ends_at + 1.day) visit results_poll_path(poll)
- <%= render Admin::TableActionsComponent.new(answer, actions: [:edit]) %> + <%= render Admin::Poll::Questions::Answers::TableActionsComponent.new(answer) %>