diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 21a287ca6b9f5..42f16e8b6dcd7 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -133,6 +133,7 @@ ignore_unused: - booleans.* - '{date,time.formats}.*' - datetime.distance_in_words.* + - decidim.admin.actions.share - decidim.admin.participatory_process_steps.default_title - decidim.admin.admin_log.changeset.* - decidim.authorization_handlers.{direct,multistep} @@ -147,6 +148,10 @@ ignore_unused: - decidim.forms.file_help.* - decidim.forms.user_answers_serializer.* - decidim.forms.admin_log.questionnaire.update + - decidim.surveys.admin_log.survey.create + - decidim.surveys.admin_log.survey.delete + - decidim.surveys.admin_log.survey.publish + - decidim.surveys.admin_log.survey.update - decidim.proposals.answers.* - decidim.proposals.collaborative_drafts.show.see_other_versions - decidim.proposals.collaborative_drafts.wizard_aside.back diff --git a/decidim-core/config/locales/en.yml b/decidim-core/config/locales/en.yml index 836c6b84071db..449f69b5e3c1c 100644 --- a/decidim-core/config/locales/en.yml +++ b/decidim-core/config/locales/en.yml @@ -1794,6 +1794,7 @@ en: no_stats: There are no statistics yet. pages_count: Pages participants_count: Participants + surveys_count: Surveys users_count: Participants tags: filter_results_for_taxonomy: 'Filter results for: %{resource}' diff --git a/decidim-core/lib/decidim/core/engine.rb b/decidim-core/lib/decidim/core/engine.rb index 754ada3fc4a12..8a72effa5ea8c 100644 --- a/decidim-core/lib/decidim/core/engine.rb +++ b/decidim-core/lib/decidim/core/engine.rb @@ -153,6 +153,7 @@ class Engine < ::Rails::Engine Decidim.icons.register(name: "treasure-map-line", icon: "treasure-map-line", category: "system", description: "", engine: :core) Decidim.icons.register(name: "chat-new-line", icon: "chat-new-line", category: "system", description: "", engine: :core) Decidim.icons.register(name: "history", icon: "history-line", category: "system", description: "History timeline", engine: :core) + Decidim.icons.register(name: "survey-line", icon: "survey-line", category: "system", description: "Survey line", engine: :core) Decidim.icons.register(name: "draft-line", icon: "draft-line", category: "system", description: "", engine: :core) Decidim.icons.register(name: "user-voice-line", icon: "user-voice-line", category: "system", description: "", engine: :core) diff --git a/decidim-forms/app/commands/decidim/forms/admin/update_questionnaire.rb b/decidim-forms/app/commands/decidim/forms/admin/update_questionnaire.rb index caf07f5d56e4c..72a74df6ab681 100644 --- a/decidim-forms/app/commands/decidim/forms/admin/update_questionnaire.rb +++ b/decidim-forms/app/commands/decidim/forms/admin/update_questionnaire.rb @@ -25,14 +25,7 @@ def call Decidim.traceability.perform_action!("update", @questionnaire, @user) do - Decidim::Forms::Questionnaire.transaction do - if @questionnaire.questions_editable? - update_questionnaire_questions - delete_answers unless @questionnaire.published? - end - - update_questionnaire - end + update_questionnaire end broadcast(:ok) @@ -40,85 +33,11 @@ def call private - def update_questionnaire_questions - @form.questions.each do |form_question| - update_questionnaire_question(form_question) - end - end - - def update_questionnaire_question(form_question) - question_attributes = { - body: form_question.body, - description: form_question.description, - position: form_question.position, - mandatory: form_question.mandatory, - question_type: form_question.question_type, - max_choices: form_question.max_choices, - max_characters: form_question.max_characters - } - - update_nested_model(form_question, question_attributes, @questionnaire.questions) do |question| - form_question.answer_options.each do |form_answer_option| - answer_option_attributes = { - body: form_answer_option.body, - free_text: form_answer_option.free_text - } - - update_nested_model(form_answer_option, answer_option_attributes, question.answer_options) - end - - form_question.display_conditions.each do |form_display_condition| - type = form_display_condition.condition_type - - display_condition_attributes = { - condition_question: form_display_condition.condition_question, - condition_type: form_display_condition.condition_type, - condition_value: type == "match" ? form_display_condition.condition_value : nil, - answer_option: %w(equal not_equal).include?(type) ? form_display_condition.answer_option : nil, - mandatory: form_display_condition.mandatory - } - - next if form_display_condition.deleted? && form_display_condition.id.blank? - - update_nested_model(form_display_condition, display_condition_attributes, question.display_conditions) - end - - form_question.matrix_rows_by_position.each_with_index do |form_matrix_row, idx| - matrix_row_attributes = { - body: form_matrix_row.body, - position: form_matrix_row.position || idx - } - - update_nested_model(form_matrix_row, matrix_row_attributes, question.matrix_rows) - end - end - end - - def update_nested_model(form, attributes, parent_association) - record = parent_association.find_by(id: form.id) || parent_association.build(attributes) - - yield record if block_given? - - if record.persisted? - if form.deleted? - record.destroy! - else - record.update!(attributes) - end - else - record.save! - end - end - def update_questionnaire @questionnaire.update!(title: @form.title, description: @form.description, tos: @form.tos) end - - def delete_answers - @questionnaire.answers.destroy_all - end end end end diff --git a/decidim-forms/app/commands/decidim/forms/admin/update_questions.rb b/decidim-forms/app/commands/decidim/forms/admin/update_questions.rb new file mode 100644 index 0000000000000..27d1a9f90a54e --- /dev/null +++ b/decidim-forms/app/commands/decidim/forms/admin/update_questions.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +module Decidim + module Forms + module Admin + # This command is executed when the user changes a Questionnaire questions from the admin + # panel. + class UpdateQuestions < Decidim::Command + # Initializes a UpdateQuestions Command. + # + # form - The form from which to get the data. + # questionnaire - The current instance of the questionnaire questions to be updated. + def initialize(form, questionnaire) + @form = form + @questionnaire = questionnaire + end + + # Updates the questionnaire if valid. + # + # Broadcasts :ok if successful, :invalid otherwise. + def call + return broadcast(:invalid) if @form.invalid? + + Decidim.traceability.perform_action!("update", + @questionnaire, + @form.current_user) do + Decidim::Forms::Questionnaire.transaction do + update_questionnaire_questions if @questionnaire.questions_editable? + end + end + + broadcast(:ok) + end + + private + + def update_questionnaire_questions + @form.questions.each do |form_question| + update_questionnaire_question(form_question) + end + end + + def update_questionnaire_question(form_question) + question_attributes = { + body: form_question.body, + description: form_question.description, + position: form_question.position, + mandatory: form_question.mandatory, + question_type: form_question.question_type, + max_choices: form_question.max_choices, + max_characters: form_question.max_characters + } + + update_nested_model(form_question, question_attributes, @questionnaire.questions) do |question| + form_question.answer_options.each do |form_answer_option| + answer_option_attributes = { + body: form_answer_option.body, + free_text: form_answer_option.free_text + } + + update_nested_model(form_answer_option, answer_option_attributes, question.answer_options) + end + + form_question.display_conditions.each do |form_display_condition| + type = form_display_condition.condition_type + + display_condition_attributes = { + condition_question: form_display_condition.condition_question, + condition_type: form_display_condition.condition_type, + condition_value: type == "match" ? form_display_condition.condition_value : nil, + answer_option: %w(equal not_equal).include?(type) ? form_display_condition.answer_option : nil, + mandatory: form_display_condition.mandatory + } + + next if form_display_condition.deleted? && form_display_condition.id.blank? + + update_nested_model(form_display_condition, display_condition_attributes, question.display_conditions) + end + + form_question.matrix_rows_by_position.each_with_index do |form_matrix_row, idx| + matrix_row_attributes = { + body: form_matrix_row.body, + position: form_matrix_row.position || idx + } + + update_nested_model(form_matrix_row, matrix_row_attributes, question.matrix_rows) + end + end + end + + def update_nested_model(form, attributes, parent_association) + record = parent_association.find_by(id: form.id) || parent_association.build(attributes) + + yield record if block_given? + + if record.persisted? + if form.deleted? + record.destroy! + else + record.update!(attributes) + end + else + record.save! + end + end + end + end + end +end diff --git a/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb b/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb index bf52ccfff36cf..81a27b9fa4e02 100644 --- a/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb +++ b/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire.rb @@ -38,13 +38,12 @@ def edit @form = form(Admin::QuestionnaireForm).from_model(questionnaire) - render template: "decidim/forms/admin/questionnaires/edit" + render template: edit_template end def update enforce_permission_to(:update, :questionnaire, questionnaire:) - params["published_at"] = Time.current if params.has_key? "save_and_publish" @form = form(Admin::QuestionnaireForm).from_params(params) Admin::UpdateQuestionnaire.call(@form, questionnaire, current_user) do @@ -57,7 +56,30 @@ def update on(:invalid) do # i18n-tasks-use t("decidim.forms.admin.questionnaires.update.invalid") flash.now[:alert] = I18n.t("update.invalid", scope: i18n_flashes_scope) - render template: "decidim/forms/admin/questionnaires/edit" + render template: edit_template + end + end + end + + def edit_questions + @form = form(Admin::QuestionsForm).from_model(questionnaire) + + render template: edit_questions_template + end + + # i18n-tasks-use t("decidim.forms.admin.questionnaires.questions_form.update.success") + # i18n-tasks-use t("decidim.forms.admin.questionnaires.update.invalid") + def update_questions + @form = form(Admin::QuestionsForm).from_params(params) + Admin::UpdateQuestions.call(@form, questionnaire) do + on(:ok) do + flash[:notice] = I18n.t("update.success", scope: i18n_questions_flashes_scope) + redirect_to after_update_url + end + + on(:invalid) do + flash.now[:alert] = I18n.t("update.invalid", scope: i18n_flashes_scope) + render template: edit_questions_template end end end @@ -96,6 +118,12 @@ def public_url raise "#{self.class.name} is expected to implement #public_url" end + # Implement this method in your controller to set the URL + # where the user will be render while editing the questionnaire questions + def edit_questions_template + "decidim/forms/admin/questionnaires/edit_questions" + end + # Returns the url to get the answer options json (for the display conditions form) # for the question with id = params[:id] def answer_options_url(params) @@ -110,10 +138,18 @@ def edit_questionnaire_title private + def edit_template + "decidim/forms/admin/questionnaires/edit" + end + def i18n_flashes_scope "decidim.forms.admin.questionnaires" end + def i18n_questions_flashes_scope + "decidim.forms.admin.questionnaires.questions_form" + end + def questionnaire @questionnaire ||= Questionnaire.find_by(questionnaire_for:) end diff --git a/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb b/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb index dac0672eb4112..9bfcdfeba2a2b 100644 --- a/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb +++ b/decidim-forms/app/controllers/decidim/forms/admin/concerns/has_questionnaire_answers.rb @@ -37,7 +37,7 @@ def index def show enforce_permission_to :show, :questionnaire_answers - @participant = participant(participants_query.participant(params[:session_token])) + @participant = participant(participants_query.participant(params[:id])) render template: "decidim/forms/admin/questionnaires/answers/show" end @@ -45,7 +45,7 @@ def show def export_response enforce_permission_to :export_response, :questionnaire_answers - session_token = params[:session_token] + session_token = params[:id] answers = QuestionnaireUserAnswers.for(questionnaire) # i18n-tasks-use t("decidim.forms.admin.questionnaires.answers.export_response.title") diff --git a/decidim-forms/app/forms/decidim/forms/admin/questionnaire_form.rb b/decidim-forms/app/forms/decidim/forms/admin/questionnaire_form.rb index f360103318be0..1b2421b3edf8f 100644 --- a/decidim-forms/app/forms/decidim/forms/admin/questionnaire_form.rb +++ b/decidim-forms/app/forms/decidim/forms/admin/questionnaire_form.rb @@ -11,16 +11,7 @@ class QuestionnaireForm < Decidim::Form translatable_attribute :description, Decidim::Attributes::RichText translatable_attribute :tos, Decidim::Attributes::RichText - attribute :published_at, Decidim::Attributes::TimeWithZone - attribute :questions, Array[QuestionForm] - validates :title, :tos, translatable_presence: true - - def map_model(model) - self.questions = model.questions.map do |question| - QuestionForm.from_model(question) - end - end end end end diff --git a/decidim-forms/app/forms/decidim/forms/admin/questions_form.rb b/decidim-forms/app/forms/decidim/forms/admin/questions_form.rb new file mode 100644 index 0000000000000..90c3c64d98310 --- /dev/null +++ b/decidim-forms/app/forms/decidim/forms/admin/questions_form.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Decidim + module Forms + module Admin + # This class holds a Form to update questionnaires questions from Decidim's admin panel. + class QuestionsForm < Decidim::Form + attribute :questions, Array[QuestionForm] + + def map_model(model) + self.questions = model.questions.map do |question| + QuestionForm.from_model(question) + end + end + end + end + end +end diff --git a/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb b/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb index cc6a17e9db99f..577ab02e7f4e8 100644 --- a/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb +++ b/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_pagination_helper.rb @@ -40,7 +40,7 @@ def participant_ids end def current_idx - participant_ids.index(params[:session_token]) + participant_ids.index(params[:id]) end end end diff --git a/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb b/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb index 375f5605762e4..cdfa0472a264a 100644 --- a/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb +++ b/decidim-forms/app/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper.rb @@ -21,17 +21,17 @@ def questionnaire_url # You can implement this method in your controller to change the URL # where the questionnaire participants' info will be shown. def questionnaire_participants_url - url_for([:index, questionnaire.questionnaire_for, { format: nil }]) + url_for([questionnaire.questionnaire_for, { format: nil }]) end # You can implement this method in your controller to change the URL # where the user's questionnaire answers will be shown. - def questionnaire_participant_answers_url(session_token) - url_for([:show, questionnaire.questionnaire_for, { session_token: }]) + def questionnaire_participant_answers_url(id) + url_for([questionnaire.questionnaire_for, { id: }]) end - def questionnaire_export_response_url(session_token) - url_for([:export_response, questionnaire.questionnaire_for, { session_token:, format: "pdf" }]) + def questionnaire_export_response_url(id) + url_for([questionnaire.questionnaire_for, { id:, format: "pdf" }]) end end end diff --git a/decidim-forms/app/queries/decidim/forms/questionnaire_user_answers.rb b/decidim-forms/app/queries/decidim/forms/questionnaire_user_answers.rb index d8c327c2e4639..a1f37bbd0e705 100644 --- a/decidim-forms/app/queries/decidim/forms/questionnaire_user_answers.rb +++ b/decidim-forms/app/queries/decidim/forms/questionnaire_user_answers.rb @@ -25,7 +25,7 @@ def query .joins(:question) .where(questionnaire: @questionnaire) - answers.sort_by { |answer| answer.question.position }.group_by { |a| a.user || a.session_token }.values + answers.sort_by { |answer| answer.question.position.to_i }.group_by { |a| a.user || a.session_token }.values end end end diff --git a/decidim-forms/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb b/decidim-forms/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb index e5e25d6b63de1..62ed642303b91 100644 --- a/decidim-forms/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb +++ b/decidim-forms/app/views/decidim/forms/admin/questionnaires/_answer_option_template.html.erb @@ -1,7 +1,7 @@ <% question = form.object %> diff --git a/decidim-forms/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb b/decidim-forms/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb index 899f0ebc2cb56..fb16e8df76438 100644 --- a/decidim-forms/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb +++ b/decidim-forms/app/views/decidim/forms/admin/questionnaires/_display_condition_template.html.erb @@ -1,7 +1,7 @@ <% question = form.object %> diff --git a/decidim-forms/app/views/decidim/forms/admin/questionnaires/_form.html.erb b/decidim-forms/app/views/decidim/forms/admin/questionnaires/_form.html.erb index bf84e1a6c7f8c..2552b01ecb499 100644 --- a/decidim-forms/app/views/decidim/forms/admin/questionnaires/_form.html.erb +++ b/decidim-forms/app/views/decidim/forms/admin/questionnaires/_form.html.erb @@ -13,92 +13,3 @@ - -
New description
", - ca: "Nova descripció
", - es: "Nueva descripción
" - } - - within "form.edit_questionnaire" do - fill_in_i18n_editor(:questionnaire_description, "#questionnaire-description-tabs", new_description) - click_on "Save" - end - - expect(page).to have_admin_callout("successfully") - - visit questionnaire_public_path - - expect(page).to have_content("New description") - end - context "when the questionnaire is not already answered" do before do - visit questionnaire_edit_path + visit manage_questions_path end it_behaves_like "add questions" @@ -61,7 +40,7 @@ let!(:answer) { create(:answer, questionnaire:, question:) } it "cannot modify questionnaire questions" do - visit questionnaire_edit_path + visit manage_questions_path expect(page).to have_no_content("Add question") expect(page).to have_no_content("Remove") @@ -112,8 +91,8 @@ def expand_all_questions find(".button.expand-all").click end - def visit_questionnaire_edit_path_and_expand_all - visit questionnaire_edit_path + def visit_manage_questions_and_expand_all + click_on "Manage questions" expand_all_questions end end diff --git a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb index 7a486f09997a6..69365459606b2 100644 --- a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb +++ b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_display_conditions.rb @@ -17,7 +17,8 @@ let!(:question) { create(:questionnaire_question, questionnaire:, body:, question_type: "short_answer") } before do - visit_questionnaire_edit_path_and_expand_all + click_on "Save" + visit_manage_questions_and_expand_all end it "does not display an add display condition button" do @@ -26,12 +27,10 @@ context "when creating a new question" do it "disables the add display condition button if the question has not been saved" do - within "form.edit_questionnaire" do - click_on "Add question" - expand_all_questions + click_on "Add question" + expand_all_questions - expect(page).to have_button("Add display condition", disabled: true) - end + expect(page).to have_button("Add display condition", disabled: true) end end end @@ -71,29 +70,28 @@ let(:questions) { [question_short_answer, question_long_answer, question_single_option, question_multiple_option] } before do - visit_questionnaire_edit_path_and_expand_all + click_on "Save" + visit_manage_questions_and_expand_all end context "when clicking add display condition button" do it "adds a new display condition form with all correct elements" do - within "form.edit_questionnaire" do - within_add_display_condition do - expect(page).to have_select("Question") - expect(page).to have_select("Condition") - expect(page).to have_css("[id$=mandatory]") + within_add_display_condition do + expect(page).to have_select("Question") + expect(page).to have_select("Condition") + expect(page).to have_css("[id$=mandatory]") - select question_single_option.body["en"], from: "Question" - select "Answered", from: "Condition" + select question_single_option.body["en"], from: "Question" + select "Answered", from: "Condition" - expect(page).to have_no_select("Answer option") - expect(page).to have_no_css("[id$=condition_value_en]", visible: :visible) + expect(page).to have_no_select("Answer option") + expect(page).to have_no_css("[id$=condition_value_en]", visible: :visible) - select question_single_option.body["en"], from: "Question" - select "Equal", from: "Condition" + select question_single_option.body["en"], from: "Question" + select "Equal", from: "Condition" - expect(page).to have_select("Answer option") - expect(page).to have_no_css("[id$=condition_value_en]", visible: :visible) - end + expect(page).to have_select("Answer option") + expect(page).to have_no_css("[id$=condition_value_en]", visible: :visible) end end diff --git a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb index 60ec5b61b2481..8a3814a01de13 100644 --- a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb +++ b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/add_questions.rb @@ -37,29 +37,26 @@ it "adds a few questions and separators to the questionnaire" do fields_body = ["This is the first question", "This is the second question", "This is the first title and description"] + click_on "Add question" + click_on "Add separator" + click_on "Add title and description" + click_on "Add question" - within "form.edit_questionnaire" do - click_on "Add question" - click_on "Add separator" - click_on "Add title and description" - click_on "Add question" - - expect(page).to have_css(".questionnaire-question", count: 4) + expect(page).to have_css(".questionnaire-question", count: 4) - expand_all_questions + expand_all_questions - page.all(".questionnaire-question .collapsible").each_with_index do |field, idx| - within field do - fill_in find_nested_form_field_locator("body_en"), with: fields_body[idx] - end + page.all(".questionnaire-question .collapsible").each_with_index do |field, idx| + within field do + fill_in find_nested_form_field_locator("body_en"), with: fields_body[idx] end - - click_on "Save" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all expect(page).to have_css("input[value='This is the first question']") expect(page).to have_css("input[value='This is the second question']") @@ -68,59 +65,45 @@ end it "adds a question with a rich text description" do - within "form.edit_questionnaire" do - click_on "Add question" - expand_all_questions - - within ".questionnaire-question" do - fill_in find_nested_form_field_locator("body_en"), with: "Body" + click_on "Add question" + expand_all_questions - fill_in_editor find_nested_form_field_locator("description_en", visible: false), with: "\nSuperkalifragilistic description\n
" - end + within ".questionnaire-question" do + fill_in find_nested_form_field_locator("body_en"), with: "Body" - click_on "Save" + fill_in_editor find_nested_form_field_locator("description_en", visible: false), with: "\nSuperkalifragilistic description\n
" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - component.update!( - step_settings: { - component.participatory_space.active_step.id => { - allow_answers: true - } - } - ) + update_component_settings_or_attributes visit questionnaire_public_path + see_questionnaire_questions expect(page).to have_css("strong", text: "Superkalifragilistic description") end it "adds a title-and-description" do - within "form.edit_questionnaire" do - click_on "Add title and description" - expand_all_questions + click_on "Add title and description" + expand_all_questions - within ".questionnaire-question" do - fill_in find_nested_form_field_locator("body_en"), with: "Body" + within ".questionnaire-question" do + fill_in find_nested_form_field_locator("body_en"), with: "Body" - fill_in_editor find_nested_form_field_locator("description_en", visible: false), with: "\nSuperkalifragilistic description\n
" - end - - click_on "Save" + fill_in_editor find_nested_form_field_locator("description_en", visible: false), with: "\nSuperkalifragilistic description\n
" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - component.update!( - step_settings: { - component.participatory_space.active_step.id => { - allow_answers: true - } - } - ) + update_component_settings_or_attributes visit questionnaire_public_path + see_questionnaire_questions expect(page).to have_css("strong", text: "Superkalifragilistic description") end @@ -140,40 +123,38 @@ ] ] - within "form.edit_questionnaire" do - click_on "Add question" - click_on "Add question" - expand_all_questions + click_on "Add question" + click_on "Add question" + expand_all_questions - page.all(".questionnaire-question").each_with_index do |question, idx| - within question do - fill_in find_nested_form_field_locator("body_en"), with: question_body[idx] - end + page.all(".questionnaire-question").each_with_index do |question, idx| + within question do + fill_in find_nested_form_field_locator("body_en"), with: question_body[idx] end + end - expect(page).to have_no_content "Add answer option" + expect(page).to have_no_content "Add answer option" - page.all(".questionnaire-question").each do |question| - within question do - select "Single option", from: "Type" - click_on "Add answer option" - end + page.all(".questionnaire-question").each do |question| + within question do + select "Single option", from: "Type" + click_on "Add answer option" end + end - page.all(".questionnaire-question").each_with_index do |question, question_idx| - question.all(".questionnaire-question-answer-option").each_with_index do |question_answer_option, answer_option_idx| - within question_answer_option do - fill_in find_nested_form_field_locator("body_en"), with: answer_options_body[question_idx][answer_option_idx] - end + page.all(".questionnaire-question").each_with_index do |question, question_idx| + question.all(".questionnaire-question-answer-option").each_with_index do |question_answer_option, answer_option_idx| + within question_answer_option do + fill_in find_nested_form_field_locator("body_en"), with: answer_options_body[question_idx][answer_option_idx] end end - - click_on "Save" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all expect(page).to have_css("input[value='This is the first question']") expect(page).to have_css("input[value='This is the Q1 first option']") @@ -278,11 +259,12 @@ click_on "Add question" expand_all_questions + expect(page).to have_text("Type") select "Long answer", from: "Type" click_on "Save" expand_all_questions - expect(page).to have_select("Type", selected: "Long answer") + expect(page).to have_select("Type", selected: "Long answer", wait: 10) end it "does not preserve spurious answer options from previous type selections" do @@ -300,6 +282,7 @@ click_on "Save" expand_all_questions + expect(page).to have_text("Type") select "Single option", from: "Type" within ".questionnaire-question-answer-option:first-of-type" do @@ -322,6 +305,7 @@ click_on "Save" expand_all_questions + expect(page).to have_text("Type") select "Matrix (Single option)", from: "Type" within ".questionnaire-question-matrix-row:first-of-type" do @@ -350,6 +334,7 @@ click_on "Save" expand_all_questions + expect(page).to have_css(".questionnaire-question-answer-option") within ".questionnaire-question-answer-option:first-of-type" do expect(page).to have_nested_field("body_en", with: "Something") end @@ -383,7 +368,6 @@ it "allows switching translated field tabs after form failures" do click_on "Add question" - click_on "Save" expand_all_questions @@ -405,20 +389,16 @@ let(:single_option_string) { "Single option" } before do - visit questionnaire_edit_path - - within "form.edit_questionnaire" do - click_on "Add question" - - expand_all_questions + click_on "Add question" - within ".questionnaire-question" do - fill_in find_nested_form_field_locator("body_en"), with: "This is the first question" - end + expand_all_questions - expect(page).to have_no_content "Add answer option" - expect(page).to have_no_select("Maximum number of choices") + within ".questionnaire-question" do + fill_in find_nested_form_field_locator("body_en"), with: "This is the first question" end + + expect(page).to have_no_content "Add answer option" + expect(page).to have_no_select("Maximum number of choices") end it "updates the free text option selector according to the selected question type" do @@ -442,20 +422,16 @@ let(:single_option_string) { "Matrix (Single option)" } before do - visit questionnaire_edit_path - - within "form.edit_questionnaire" do - click_on "Add question" - expand_all_questions - - within ".questionnaire-question" do - fill_in find_nested_form_field_locator("body_en"), with: "This is the first question" - end + click_on "Add question" + expand_all_questions - expect(page).to have_no_content "Add answer option" - expect(page).to have_no_content "Add row" - expect(page).to have_no_select("Maximum number of choices") + within ".questionnaire-question" do + fill_in find_nested_form_field_locator("body_en"), with: "This is the first question" end + + expect(page).to have_no_content "Add answer option" + expect(page).to have_no_content "Add row" + expect(page).to have_no_select("Maximum number of choices") end it "updates the free text option selector according to the selected question type" do diff --git a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb index 4f991412b5962..87f7da12c5f99 100644 --- a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb +++ b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_display_conditions.rb @@ -19,7 +19,8 @@ end before do - visit_questionnaire_edit_path_and_expand_all + click_on "Save" + visit_manage_questions_and_expand_all end it "the related form appears" do @@ -59,35 +60,31 @@ click_on "Save" - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all expect(page).to have_css(".questionnaire-question-display-condition", count: 0) end it "still removes the question even if previous editions rendered the conditions invalid" do - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 2) - - within ".questionnaire-question-display-condition:first-of-type" do - select condition_question.body["en"], from: "Question" - select "Includes text", from: "Condition" - fill_in find_nested_form_field_locator("condition_value_en"), with: "" - end + expect(page).to have_css(".questionnaire-question", count: 2) - within ".questionnaire-question:last-of-type" do - click_on "Remove", match: :first - end + within ".questionnaire-question-display-condition:first-of-type" do + select condition_question.body["en"], from: "Question" + select "Includes text", from: "Condition" + fill_in find_nested_form_field_locator("condition_value_en"), with: "" + end - click_on "Save" + within ".questionnaire-question:last-of-type" do + click_on "Remove", match: :first end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 1) - end + expect(page).to have_css(".questionnaire-question", count: 1) end end end diff --git a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb index b9964a1c92270..fbce42a920187 100644 --- a/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb +++ b/decidim-forms/lib/decidim/forms/test/shared_examples/manage_questionnaires/update_questions.rb @@ -7,68 +7,61 @@ let!(:question) { create(:questionnaire_question, questionnaire:, body:) } before do - visit questionnaire_edit_path - expand_all_questions + click_on "Save" + visit_manage_questions_and_expand_all end it "modifies the question when the information is valid" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - fill_in "questionnaire_questions_#{question.id}_body_en", with: "Modified question" - fill_in "questionnaire_questions_#{question.id}_max_characters", with: 30 - check "Mandatory" - select "Long answer", from: "Type" - end - - click_on "Save" + within ".questionnaire-question" do + fill_in "questions_questions_#{question.id}_body_en", with: "Modified question" + fill_in "questions_questions_#{question.id}_max_characters", with: 30 + check "Mandatory" + select "Long answer", from: "Type" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all expect(page).to have_css("input[value='Modified question']") expect(page).to have_no_css("input[value='This is the first question']") - expect(page).to have_css("input#questionnaire_questions_#{question.id}_mandatory[checked]") - expect(page).to have_css("input#questionnaire_questions_#{question.id}_max_characters[value='30']") - expect(page).to have_css("select#questionnaire_questions_#{question.id}_question_type option[value='long_answer'][selected]") + expect(page).to have_css("input#questions_questions_#{question.id}_mandatory[checked]") + expect(page).to have_css("input#questions_questions_#{question.id}_max_characters[value='30']") + expect(page).to have_css("select#questions_questions_#{question.id}_question_type option[value='long_answer'][selected]") end it "re-renders the form when the information is invalid and displays errors" do expand_all_questions - within "form.edit_questionnaire" do - within ".questionnaire-question" do - expect(page).to have_content("Statement*") - fill_in "questionnaire_questions_#{question.id}_body_en", with: "" - fill_in "questionnaire_questions_#{question.id}_max_characters", with: -3 - check "Mandatory" - select "Matrix (Multiple option)", from: "Type" - select "2", from: "Maximum number of choices" - end - - click_on "Save" + within ".questionnaire-question" do + expect(page).to have_content("Statement*") + fill_in "questions_questions_#{question.id}_body_en", with: "" + fill_in "questions_questions_#{question.id}_max_characters", with: -3 + check "Mandatory" + select "Matrix (Multiple option)", from: "Type" + select "2", from: "Maximum number of choices" end - expand_all_questions + click_on "Save" + click_on "Expand all questions" expect(page).to have_admin_callout("There was a problem saving") - expect(page).to have_content("cannot be blank", count: 5) # empty question, 2 empty default answer options, 2 empty default matrix rows + expect(page).to have_content("cannot be blank", count: 5) expect(page).to have_content("must be greater than or equal to 0", count: 1) expect(page).to have_css("input[value='']") expect(page).to have_no_css("input[value='This is the first question']") - expect(page).to have_css("input#questionnaire_questions_#{question.id}_mandatory[checked]") - expect(page).to have_css("input#questionnaire_questions_#{question.id}_max_characters[value='-3']") + expect(page).to have_css("input#questions_questions_#{question.id}_mandatory[checked]") + expect(page).to have_css("input#questions_questions_#{question.id}_max_characters[value='-3']") + expect(page).to have_css("select#questions_questions_#{question.id}_question_type option[value='matrix_multiple'][selected]") expect(page).to have_select("Maximum number of choices", selected: "2") - expect(page).to have_css("select#questionnaire_questions_#{question.id}_question_type option[value='matrix_multiple'][selected]") end it "preserves deleted status across submission failures" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - click_on "Remove" - end + within ".questionnaire-question" do + click_on "Remove" end click_on "Add question" @@ -84,36 +77,28 @@ end it "removes the question" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - click_on "Remove" - end - - click_on "Save" + within ".questionnaire-question" do + click_on "Remove" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit questionnaire_edit_path + click_on "Manage questions" - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 0) - end + expect(page).to have_css(".questionnaire-question", count: 0) end it "cannot be moved up" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - expect(page).to have_no_button("Up") - end + within ".questionnaire-question" do + expect(page).to have_no_button("Up") end end it "cannot be moved down" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - expect(page).to have_no_button("Down") - end + within ".questionnaire-question" do + expect(page).to have_no_button("Down") end end end @@ -122,22 +107,20 @@ let!(:question) { create(:questionnaire_question, :title_and_description, questionnaire:, body: title_and_description_body) } before do - visit questionnaire_edit_path - expand_all_questions + click_on "Save" + visit_manage_questions_and_expand_all end it "modifies the question when the information is valid" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - fill_in "questionnaire_questions_#{question.id}_body_en", with: "Modified title and description" - end - - click_on "Save" + within ".questionnaire-question" do + fill_in "questions_questions_#{question.id}_body_en", with: "Modified title and description" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all expect(page).to have_css("input[value='Modified title and description']") expect(page).to have_no_css("input[value='This is the first title and description']") @@ -146,14 +129,12 @@ it "re-renders the form when the information is invalid and displays errors" do expand_all_questions - within "form.edit_questionnaire" do - within ".questionnaire-question" do - fill_in "questionnaire_questions_#{question.id}_body_en", with: "" - end - - click_on "Save" + within ".questionnaire-question" do + fill_in "questions_questions_#{question.id}_body_en", with: "" end + click_on "Save" + expand_all_questions expect(page).to have_admin_callout("There was a problem saving") @@ -163,10 +144,8 @@ end it "preserves deleted status across submission failures" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - click_on "Remove" - end + within ".questionnaire-question" do + click_on "Remove" end click_on "Add question" @@ -182,36 +161,28 @@ end it "removes the question" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - click_on "Remove" - end - - click_on "Save" + within ".questionnaire-question" do + click_on "Remove" end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit questionnaire_edit_path + click_on "Manage questions" - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 0) - end + expect(page).to have_css(".questionnaire-question", count: 0) end it "cannot be moved up" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - expect(page).to have_no_button("Up") - end + within ".questionnaire-question" do + expect(page).to have_no_button("Up") end end it "cannot be moved down" do - within "form.edit_questionnaire" do - within ".questionnaire-question" do - expect(page).to have_no_button("Down") - end + within ".questionnaire-question" do + expect(page).to have_no_button("Down") end end end @@ -233,7 +204,8 @@ end before do - visit questionnaire_edit_path + click_on "Save" + click_on "Manage questions" end it "allows deleting answer options" do @@ -245,35 +217,31 @@ click_on "Save" - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all expect(page).to have_css(".questionnaire-question-answer-option", count: 2) end it "still removes the question even if previous editions rendered the options invalid" do - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 1) - - expand_all_questions + expect(page).to have_css(".questionnaire-question", count: 1) - within ".questionnaire-question-answer-option:first-of-type" do - fill_in find_nested_form_field_locator("body_en"), with: "" - end + expand_all_questions - within ".questionnaire-question" do - click_on "Remove", match: :first - end + within ".questionnaire-question-answer-option:first-of-type" do + fill_in find_nested_form_field_locator("body_en"), with: "" + end - click_on "Save" + within ".questionnaire-question" do + click_on "Remove", match: :first end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 0) - end + expect(page).to have_css(".questionnaire-question", count: 0) end end @@ -300,7 +268,8 @@ end before do - visit_questionnaire_edit_path_and_expand_all + click_on "Save" + visit_manage_questions_and_expand_all end it "allows deleting matrix rows" do @@ -310,7 +279,7 @@ click_on "Save" - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all within ".questionnaire-question:last-of-type" do expect(page).to have_css(".questionnaire-question-matrix-row", count: 2) @@ -319,27 +288,23 @@ end it "still removes the question even if previous editions rendered the rows invalid" do - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 2) - - within ".questionnaire-question-matrix-row:first-of-type" do - fill_in find_nested_form_field_locator("body_en"), with: "" - end + expect(page).to have_css(".questionnaire-question", count: 2) - within ".questionnaire-question:last-of-type" do - click_on "Remove", match: :first - end + within ".questionnaire-question-matrix-row:first-of-type" do + fill_in find_nested_form_field_locator("body_en"), with: "" + end - click_on "Save" + within ".questionnaire-question:last-of-type" do + click_on "Remove", match: :first end + click_on "Save" + expect(page).to have_admin_callout("successfully") - visit_questionnaire_edit_path_and_expand_all + visit_manage_questions_and_expand_all - within "form.edit_questionnaire" do - expect(page).to have_css(".questionnaire-question", count: 1) - end + expect(page).to have_css(".questionnaire-question", count: 1) end end @@ -361,8 +326,8 @@ end before do - visit questionnaire_edit_path - expand_all_questions + click_on "Save" + visit_manage_questions_and_expand_all end shared_examples_for "switching questions order" do diff --git a/decidim-forms/spec/commands/decidim/forms/admin/update_questionnaire_spec.rb b/decidim-forms/spec/commands/decidim/forms/admin/update_questionnaire_spec.rb index 4883c0693bcca..e2f30be5e7c94 100644 --- a/decidim-forms/spec/commands/decidim/forms/admin/update_questionnaire_spec.rb +++ b/decidim-forms/spec/commands/decidim/forms/admin/update_questionnaire_spec.rb @@ -27,173 +27,7 @@ module Admin "en" => "Content
", "ca" => "Contingut
", "es" => "Contenido
" - }, - "questions" => { - "0" => { - "body" => { - "en" => "First question", - "ca" => "Primera pregunta", - "es" => "Primera pregunta" - }, - "position" => "0", - "question_type" => "short_answer", - "max_characters" => "0", - "answer_options" => {} - }, - "1" => { - "body" => { - "en" => "Second question", - "ca" => "Segona pregunta", - "es" => "Segunda pregunta" - }, - "description" => { "en" => "Description" }, - "position" => "1", - "mandatory" => "1", - "question_type" => "long_answer", - "max_characters" => "100", - "answer_options" => {} - }, - "2" => { - "body" => { - "en" => "Third question", - "ca" => "Tercera pregunta", - "es" => "Tercera pregunta" - }, - "position" => "2", - "question_type" => "single_option", - "max_characters" => "0", - "answer_options" => { - "0" => { - "body" => { - "en" => "First answer", - "ca" => "Primera resposta", - "es" => "Primera respuesta" - }, - "free_text" => "0" - }, - "1" => { - "body" => { - "en" => "Second answer", - "ca" => "Segona resposta", - "es" => "Segunda respuesta" - } - } - } - }, - "3" => { - "body" => { - "en" => "Fourth question", - "ca" => "Quarta pregunta", - "es" => "Cuarta pregunta" - }, - "position" => "3", - "question_type" => "multiple_option", - "max_choices" => "2", - "answer_options" => { - "0" => { - "body" => { - "en" => "First answer", - "ca" => "Primera resposta", - "es" => "Primera respuesta" - }, - "free_text" => "1" - }, - "1" => { - "body" => { - "en" => "Second answer", - "ca" => "Segona resposta", - "es" => "Segunda respuesta" - } - } - } - }, - "4" => { - "body" => { - "en" => "Fifth question", - "ca" => "Cinquena pregunta", - "es" => "Quinta pregunta" - }, - "position" => "4", - "question_type" => "matrix_single", - "answer_options" => { - "0" => { - "body" => { - "en" => "First answer", - "ca" => "Primera resposta", - "es" => "Primera respuesta" - }, - "free_text" => "1" - }, - "1" => { - "body" => { - "en" => "Second answer", - "ca" => "Segona resposta", - "es" => "Segunda respuesta" - } - } - }, - "matrix_rows" => { - "0" => { - "body" => { - "en" => "First row", - "ca" => "Primera fila", - "es" => "Primera fila" - } - }, - "1" => { - "body" => { - "en" => "Second row", - "ca" => "Segona fila", - "es" => "Segunda fila" - } - } - } - }, - "5" => { - "body" => { - "en" => "Sixth question", - "ca" => "Sisena pregunta", - "es" => "Sexta pregunta" - }, - "position" => "5", - "question_type" => "matrix_multiple", - "max_choices" => "2", - "answer_options" => { - "0" => { - "body" => { - "en" => "First answer", - "ca" => "Primera resposta", - "es" => "Primera respuesta" - }, - "free_text" => "1" - }, - "1" => { - "body" => { - "en" => "Second answer", - "ca" => "Segona resposta", - "es" => "Segunda respuesta" - } - } - }, - "matrix_rows" => { - "0" => { - "body" => { - "en" => "First row", - "ca" => "Primera fila", - "es" => "Primera fila" - } - }, - "1" => { - "body" => { - "en" => "Second row", - "ca" => "Segona fila", - "es" => "Segunda fila" - } - } - } - } - }, - "published_at" => published_at + } } end let(:form) do @@ -221,6 +55,10 @@ module Admin end describe "when the form is valid" do + before do + allow(form).to receive(:invalid?).and_return(false) + end + it "broadcasts ok" do expect { command.call }.to broadcast(:ok) end @@ -230,40 +68,8 @@ module Admin questionnaire.reload expect(questionnaire.description["en"]).to eq("Content
") - expect(questionnaire.questions.length).to eq(6) - - questionnaire.questions.each_with_index do |question, idx| - expect(question.body["en"]).to eq(form_params["questions"][idx.to_s]["body"]["en"]) - end - - expect(questionnaire.questions[1]).to be_mandatory - expect(questionnaire.questions[1].description["en"]).to eq(form_params["questions"]["1"]["description"]["en"]) - expect(questionnaire.questions[1].question_type).to eq("long_answer") - expect(questionnaire.questions[1].max_characters).to eq(100) - expect(questionnaire.questions[2].answer_options[1]["body"]["en"]).to eq(form_params["questions"]["2"]["answer_options"]["1"]["body"]["en"]) - - expect(questionnaire.questions[2].question_type).to eq("single_option") - expect(questionnaire.questions[2].max_choices).to be_nil - expect(questionnaire.questions[2].max_characters).to eq(0) - - expect(questionnaire.questions[3].question_type).to eq("multiple_option") - expect(questionnaire.questions[2].answer_options[0].free_text).to be(false) - expect(questionnaire.questions[2].max_choices).to be_nil - - expect(questionnaire.questions[3].question_type).to eq("multiple_option") - expect(questionnaire.questions[3].answer_options[0].free_text).to be(true) - expect(questionnaire.questions[3].max_choices).to eq(2) - - expect(questionnaire.questions[4].question_type).to eq("matrix_single") - expect(questionnaire.questions[4].answer_options[0].free_text).to be(true) - 2.times do |idx| - expect(questionnaire.questions[4].matrix_rows[idx].body["en"]).to eq(form_params["questions"]["4"]["matrix_rows"][idx.to_s]["body"]["en"]) - expect(questionnaire.questions[4].matrix_rows[idx].position).to eq(idx) - end - - expect(questionnaire.questions[5].question_type).to eq("matrix_multiple") - expect(questionnaire.questions[5].answer_options[0].free_text).to be(true) - expect(questionnaire.questions[5].matrix_rows[0].body["en"]).to eq(form_params["questions"]["5"]["matrix_rows"]["0"]["body"]["en"]) + expect(questionnaire.title["en"]).to eq("Title") + expect(questionnaire.tos["en"]).to eq("TOS
") end it "traces the action", versioning: true do @@ -278,122 +84,6 @@ module Admin expect(action_log.version).to be_present end end - - describe "when the questionnaire has an existing question" do - let!(:question) { create(:questionnaire_question, questionnaire:) } - - context "and the question should be removed" do - let(:form_params) do - { - "title" => { - "en" => "Title", - "ca" => "Title", - "es" => "Title" - }, - "description" => { - "en" => "Content
", - "ca" => "Contingut
", - "es" => "Contenido
" - }, - "tos" => { - "en" => "TOS
", - "ca" => "TOS
", - "es" => "TOS
" - }, - "questions" => [ - { - "id" => question.id, - "body" => question.body, - "position" => 0, - "question_type" => "short_answer", - "deleted" => "true" - } - ] - } - end - - it "deletes the questionnaire question" do - command.call - questionnaire.reload - - expect(questionnaire.questions.length).to eq(0) - end - end - end - - describe "when the questionnaire has existing questions" do - let!(:questions) { 0.upto(3).to_a.map { |x| create(:questionnaire_question, questionnaire:, position: x) } } - let!(:question_2_answer_options) { create_list(:answer_option, 3, question: questions.second) } - - context "and display conditions are to be created" do - let(:form_params) do - { - "title" => { - "en" => "Title", - "ca" => "Títol", - "es" => "Título" - }, - "description" => { - "en" => "Content
", - "ca" => "Contingut
", - "es" => "Contenido
" - }, - "tos" => { - "en" => "TOS
", - "ca" => "TOS
", - "es" => "TOS
" - }, - "questions" => { - "1" => { - "id" => questions[0].id, - "body" => questions[0].body, - "position" => 0, - "question_type" => "short_answer" - }, - "2" => { - "id" => questions[1].id, - "body" => questions[1].body, - "position" => 1, - "question_type" => "single_option", - "answer_options" => question_2_answer_options.to_h do |answer_option| - [answer_option.id.to_s, { "id" => answer_option.id, "body" => answer_option.body, "free_text" => answer_option.free_text, "deleted" => false }] - end - }, - "3" => { - "id" => questions[2].id, - "body" => questions[2].body, - "position" => 2, - "question_type" => "short_answer", - "deleted" => "false", - "display_conditions" => { - "1" => { - "decidim_condition_question_id" => questions[0].id, - "decidim_question_id" => questions[2].id, - "condition_type" => "answered" - }, - "2" => { - "decidim_condition_question_id" => questions[1].id, - "decidim_question_id" => questions[2].id, - "condition_type" => "equal", - "decidim_answer_option_id" => question_2_answer_options.first.id - } - } - } - } - } - end - - it "saves the questionnaire" do - expect { command.call }.to broadcast(:ok) - questionnaire.reload - - expect(questionnaire.questions[2].display_conditions).not_to be_empty - expect(questionnaire.questions[2].display_conditions.first.condition_type).to eq("answered") - expect(questionnaire.questions[2].display_conditions.second.condition_type).to eq("equal") - expect(questionnaire.questions[2].display_conditions.second.decidim_answer_option_id).to eq(question_2_answer_options.first.id) - end - end - end end end end diff --git a/decidim-forms/spec/commands/decidim/forms/admin/update_questions_spec.rb b/decidim-forms/spec/commands/decidim/forms/admin/update_questions_spec.rb new file mode 100644 index 0000000000000..282207ff9a4a5 --- /dev/null +++ b/decidim-forms/spec/commands/decidim/forms/admin/update_questions_spec.rb @@ -0,0 +1,355 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Forms + module Admin + describe UpdateQuestions do + let(:current_organization) { create(:organization) } + let(:participatory_process) { create(:participatory_process, organization: current_organization) } + let(:questionnaire) { create(:questionnaire, questionnaire_for: participatory_process) } + let(:user) { create(:user, organization: current_organization) } + let(:published_at) { nil } + let(:form_params) do + { + "questions" => { + "0" => { + "body" => { + "en" => "First question", + "ca" => "Primera pregunta", + "es" => "Primera pregunta" + }, + "position" => "0", + "question_type" => "short_answer", + "max_characters" => "0", + "answer_options" => {} + }, + "1" => { + "body" => { + "en" => "Second question", + "ca" => "Segona pregunta", + "es" => "Segunda pregunta" + }, + "description" => { "en" => "Description" }, + "position" => "1", + "mandatory" => "1", + "question_type" => "long_answer", + "max_characters" => "100", + "answer_options" => {} + }, + "2" => { + "body" => { + "en" => "Third question", + "ca" => "Tercera pregunta", + "es" => "Tercera pregunta" + }, + "position" => "2", + "question_type" => "single_option", + "max_characters" => "0", + "answer_options" => { + "0" => { + "body" => { + "en" => "First answer", + "ca" => "Primera resposta", + "es" => "Primera respuesta" + }, + "free_text" => "0" + }, + "1" => { + "body" => { + "en" => "Second answer", + "ca" => "Segona resposta", + "es" => "Segunda respuesta" + } + } + } + }, + "3" => { + "body" => { + "en" => "Fourth question", + "ca" => "Quarta pregunta", + "es" => "Cuarta pregunta" + }, + "position" => "3", + "question_type" => "multiple_option", + "max_choices" => "2", + "answer_options" => { + "0" => { + "body" => { + "en" => "First answer", + "ca" => "Primera resposta", + "es" => "Primera respuesta" + }, + "free_text" => "1" + }, + "1" => { + "body" => { + "en" => "Second answer", + "ca" => "Segona resposta", + "es" => "Segunda respuesta" + } + } + } + }, + "4" => { + "body" => { + "en" => "Fifth question", + "ca" => "Cinquena pregunta", + "es" => "Quinta pregunta" + }, + "position" => "4", + "question_type" => "matrix_single", + "answer_options" => { + "0" => { + "body" => { + "en" => "First answer", + "ca" => "Primera resposta", + "es" => "Primera respuesta" + }, + "free_text" => "1" + }, + "1" => { + "body" => { + "en" => "Second answer", + "ca" => "Segona resposta", + "es" => "Segunda respuesta" + } + } + }, + "matrix_rows" => { + "0" => { + "body" => { + "en" => "First row", + "ca" => "Primera fila", + "es" => "Primera fila" + } + }, + "1" => { + "body" => { + "en" => "Second row", + "ca" => "Segona fila", + "es" => "Segunda fila" + } + } + } + }, + "5" => { + "body" => { + "en" => "Sixth question", + "ca" => "Sisena pregunta", + "es" => "Sexta pregunta" + }, + "position" => "5", + "question_type" => "matrix_multiple", + "max_choices" => "2", + "answer_options" => { + "0" => { + "body" => { + "en" => "First answer", + "ca" => "Primera resposta", + "es" => "Primera respuesta" + }, + "free_text" => "1" + }, + "1" => { + "body" => { + "en" => "Second answer", + "ca" => "Segona resposta", + "es" => "Segunda respuesta" + } + } + }, + "matrix_rows" => { + "0" => { + "body" => { + "en" => "First row", + "ca" => "Primera fila", + "es" => "Primera fila" + } + }, + "1" => { + "body" => { + "en" => "Second row", + "ca" => "Segona fila", + "es" => "Segunda fila" + } + } + } + } + }, + "published_at" => published_at + } + end + let(:form) do + QuestionsForm.from_params( + questions: form_params + ).with_context( + current_organization:, + current_user: user + ) + end + let(:command) { described_class.new(form, questionnaire) } + + describe "when the form is invalid" do + before do + allow(form).to receive(:invalid?).and_return(true) + end + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + + it "does not update the questionnaire" do + expect(questionnaire).not_to receive(:update!) + command.call + end + end + + describe "when the form is valid" do + it "broadcasts ok" do + expect { command.call }.to broadcast(:ok) + end + + it "updates the questions" do + command.call + questionnaire.reload + + expect(questionnaire.questions.length).to eq(6) + + questionnaire.questions.each_with_index do |question, idx| + expect(question.body["en"]).to eq(form_params["questions"][idx.to_s]["body"]["en"]) + end + + expect(questionnaire.questions[1]).to be_mandatory + expect(questionnaire.questions[1].description["en"]).to eq(form_params["questions"]["1"]["description"]["en"]) + expect(questionnaire.questions[1].question_type).to eq("long_answer") + expect(questionnaire.questions[1].max_characters).to eq(100) + expect(questionnaire.questions[2].answer_options[1]["body"]["en"]).to eq(form_params["questions"]["2"]["answer_options"]["1"]["body"]["en"]) + + expect(questionnaire.questions[2].question_type).to eq("single_option") + expect(questionnaire.questions[2].max_choices).to be_nil + expect(questionnaire.questions[2].max_characters).to eq(0) + + expect(questionnaire.questions[3].question_type).to eq("multiple_option") + expect(questionnaire.questions[2].answer_options[0].free_text).to be(false) + expect(questionnaire.questions[2].max_choices).to be_nil + + expect(questionnaire.questions[3].question_type).to eq("multiple_option") + expect(questionnaire.questions[3].answer_options[0].free_text).to be(true) + expect(questionnaire.questions[3].max_choices).to eq(2) + + expect(questionnaire.questions[4].question_type).to eq("matrix_single") + expect(questionnaire.questions[4].answer_options[0].free_text).to be(true) + 2.times do |idx| + expect(questionnaire.questions[4].matrix_rows[idx].body["en"]).to eq(form_params["questions"]["4"]["matrix_rows"][idx.to_s]["body"]["en"]) + expect(questionnaire.questions[4].matrix_rows[idx].position).to eq(idx) + end + + expect(questionnaire.questions[5].question_type).to eq("matrix_multiple") + expect(questionnaire.questions[5].answer_options[0].free_text).to be(true) + expect(questionnaire.questions[5].matrix_rows[0].body["en"]).to eq(form_params["questions"]["5"]["matrix_rows"]["0"]["body"]["en"]) + end + + it "traces the action", versioning: true do + expect(Decidim.traceability) + .to receive(:perform_action!) + .with("update", questionnaire, user) + .and_call_original + + expect { command.call }.to change(Decidim::ActionLog, :count) + action_log = Decidim::ActionLog.last + expect(action_log.action).to eq("update") + expect(action_log.version).to be_present + end + end + + describe "when the questionnaire has an existing question" do + let!(:question) { create(:questionnaire_question, questionnaire:) } + + context "and the question should be removed" do + let(:form_params) do + { + "questions" => [ + { + "id" => question.id, + "body" => question.body, + "position" => 0, + "question_type" => "short_answer", + "deleted" => "true" + } + ] + } + end + + it "deletes the questionnaire question" do + command.call + questionnaire.reload + + expect(questionnaire.questions.length).to eq(0) + end + end + end + + describe "when the questionnaire has existing questions" do + let!(:questions) { 0.upto(3).to_a.map { |x| create(:questionnaire_question, questionnaire:, position: x) } } + let!(:question_2_answer_options) { create_list(:answer_option, 3, question: questions.second) } + + context "and display conditions are to be created" do + let(:form_params) do + { + "questions" => { + "1" => { + "id" => questions[0].id, + "body" => questions[0].body, + "position" => 0, + "question_type" => "short_answer" + }, + "2" => { + "id" => questions[1].id, + "body" => questions[1].body, + "position" => 1, + "question_type" => "single_option", + "answer_options" => question_2_answer_options.to_h do |answer_option| + [answer_option.id.to_s, { "id" => answer_option.id, "body" => answer_option.body, "free_text" => answer_option.free_text, "deleted" => false }] + end + }, + "3" => { + "id" => questions[2].id, + "body" => questions[2].body, + "position" => 2, + "question_type" => "short_answer", + "deleted" => "false", + "display_conditions" => { + "1" => { + "decidim_condition_question_id" => questions[0].id, + "decidim_question_id" => questions[2].id, + "condition_type" => "answered" + }, + "2" => { + "decidim_condition_question_id" => questions[1].id, + "decidim_question_id" => questions[2].id, + "condition_type" => "equal", + "decidim_answer_option_id" => question_2_answer_options.first.id + } + } + } + } + } + end + + it "saves the questionnaire" do + expect { command.call }.to broadcast(:ok) + questionnaire.reload + + expect(questionnaire.questions[2].display_conditions).not_to be_empty + expect(questionnaire.questions[2].display_conditions.first.condition_type).to eq("answered") + expect(questionnaire.questions[2].display_conditions.second.condition_type).to eq("equal") + expect(questionnaire.questions[2].display_conditions.second.decidim_answer_option_id).to eq(question_2_answer_options.first.id) + end + end + end + end + end + end +end diff --git a/decidim-forms/spec/forms/decidim/forms/admin/questionnaire_form_spec.rb b/decidim-forms/spec/forms/decidim/forms/admin/questionnaire_form_spec.rb index 23b4f1fccc777..61bbe99ed0c78 100644 --- a/decidim-forms/spec/forms/decidim/forms/admin/questionnaire_form_spec.rb +++ b/decidim-forms/spec/forms/decidim/forms/admin/questionnaire_form_spec.rb @@ -38,41 +38,14 @@ module Admin } end - let(:body_english) { "First question" } let(:tos_english) { "TOS: content
" } - let(:questions) do - [ - { - body: { - "en" => body_english, - "ca" => "Primera pregunta", - "es" => "Primera pregunta" - }, - position: 0, - question_type: "short_answer" - }, - { - body: { - "en" => "Second question", - "ca" => "Segona pregunta", - "es" => "Segunda pregunta" - }, - position: 1, - mandatory: true, - question_type: "short_answer", - max_characters: 30 - } - ] - end - let(:attributes) do { "questionnaire" => { "tos" => tos, "title" => title, - "description" => description, - "questions" => questions + "description" => description } } end @@ -81,12 +54,6 @@ module Admin it { is_expected.to be_valid } end - context "when a question is not valid" do - let(:body_english) { "" } - - it { is_expected.not_to be_valid } - end - context "when tos is not valid" do let(:tos_english) { "" } diff --git a/decidim-forms/spec/forms/decidim/forms/admin/questions_form_spec.rb b/decidim-forms/spec/forms/decidim/forms/admin/questions_form_spec.rb new file mode 100644 index 0000000000000..240dc17165d73 --- /dev/null +++ b/decidim-forms/spec/forms/decidim/forms/admin/questions_form_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Forms + module Admin + describe QuestionsForm do + subject do + described_class.from_params(attributes).with_context( + current_organization: + ) + end + + let(:body_english) { "First question" } + let(:current_organization) { create(:organization) } + + let(:questions) do + [ + { + body: { + "en" => body_english, + "ca" => "Primera pregunta", + "es" => "Primera pregunta" + }, + position: 0, + question_type: "single_option", + answer_options: [ + { "body" => { "en" => "A" } }, + { "body" => { "en" => "B" } }, + { "body" => { "en" => "C" } } + ] + }, + { + body: { + "en" => "Second question", + "ca" => "Segona pregunta", + "es" => "Segunda pregunta" + }, + position: 1, + question_type: "multiple_option", + answer_options: [ + { "body" => { "en" => "A" } }, + { "body" => { "en" => "B" } }, + { "body" => { "en" => "C" } } + ] + } + ] + end + + let(:attributes) do + { + "questions" => questions + } + end + + context "when everything is OK" do + it { is_expected.to be_valid } + end + + context "when a question is not valid" do + let(:body_english) { "" } + + it { is_expected.not_to be_valid } + end + end + end + end +end diff --git a/decidim-forms/spec/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper_spec.rb b/decidim-forms/spec/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper_spec.rb new file mode 100644 index 0000000000000..5f5cd914649b0 --- /dev/null +++ b/decidim-forms/spec/helpers/decidim/forms/admin/concerns/has_questionnaire_answers_url_helper_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Forms + module Admin + module Concerns + describe HasQuestionnaireAnswersUrlHelper, type: :controller do + controller(ApplicationController) do + include Decidim::Forms::Admin::Concerns::HasQuestionnaireAnswersUrlHelper + + def index; end + end + + let(:questionnaire) { double("Questionnaire", questionnaire_for: "/test-path") } + + before do + allow(controller).to receive(:questionnaire).and_return(questionnaire) + routes.draw { get "index" => "anonymous#index" } + end + + describe "#questionnaire_url" do + it "returns the correct URL for the questionnaire" do + expect(controller.questionnaire_url).to eq("/test-path") + end + end + end + end + end + end +end diff --git a/decidim-meetings/app/controllers/decidim/meetings/admin/registration_form_controller.rb b/decidim-meetings/app/controllers/decidim/meetings/admin/registration_form_controller.rb index 33e69aef63990..af1c5367816ba 100644 --- a/decidim-meetings/app/controllers/decidim/meetings/admin/registration_form_controller.rb +++ b/decidim-meetings/app/controllers/decidim/meetings/admin/registration_form_controller.rb @@ -23,8 +23,16 @@ def public_url Decidim::EngineRouter.main_proxy(current_component).join_meeting_registration_path(meeting) end + def edit_questions_template + "decidim/meetings/admin/registration_form/edit_questions" + end + private + def i18n_questions_flashes_scope + "decidim.forms.admin.questionnaires" + end + def meeting @meeting ||= Meeting.where(component: current_component).find(params[:meeting_id]) end diff --git a/decidim-meetings/app/views/decidim/meetings/admin/registration_form/edit_questions.html.erb b/decidim-meetings/app/views/decidim/meetings/admin/registration_form/edit_questions.html.erb new file mode 100644 index 0000000000000..c6fc2f7666e41 --- /dev/null +++ b/decidim-meetings/app/views/decidim/meetings/admin/registration_form/edit_questions.html.erb @@ -0,0 +1,44 @@ +<% add_decidim_page_title(t("decidim.forms.admin.questionnaires.edit_questions.title")) %> + +<% if templates_defined? && choose_template? %> + <%= render partial: "decidim/templates/admin/questionnaire_templates/choose", locals: { target: questionnaire, form_title: t("decidim.forms.admin.questionnaires.edit.title") } %> +<% else %> + +# | +<%= first_table_th(@participants.first) %> | +<%= t("user_status", scope: "decidim.forms.user_answers_serializer") %> | +<%= t("ip_hash", scope: "decidim.forms.user_answers_serializer") %> | +<%= t("completion", scope: "decidim.forms.user_answers_serializer") %> | +<%= t("created_at", scope: "decidim.forms.user_answers_serializer") %> | ++ |
---|---|---|---|---|---|---|
<%= idx + 1 + page_offset %> | ++ <% if allowed_to? :show, :questionnaire_answers %> + <%= link_to first_table_td(participant), questionnaire_participant_answers_url(participant.session_token) %> + <% else %> + <%= first_table_td(participant) %> | + <% end %> +<%= participant.status %> | +<%= participant.ip_hash %> | +<%= display_percentage(participant.completion) %> | +<%= l participant.answered_at, format: :short %> | ++ <% if allowed_to? :show, :questionnaire_answers %> + <%= icon_link_to "eye-line", questionnaire_participant_answers_url(participant.session_token), t("actions.show", scope: "decidim.forms.admin.questionnaires.answers"), class: "action-icon--eye", target: "_blank", data: { "external-link": false } %> + <% end %> + <% if allowed_to? :export_response, :questionnaire_answers %> + <%= icon_link_to "download-line", questionnaire_export_response_url(participant.session_token), t("actions.export", scope: "decidim.forms.admin.questionnaires.answers"), class: "action-icon--data-transfer-download" %> + <% end %> + | +
<%= t("session_token", scope: "decidim.forms.user_answers_serializer") %> | +<%= t("user_status", scope: "decidim.forms.user_answers_serializer") %> | +<%= t("ip_hash", scope: "decidim.forms.user_answers_serializer") %> | +<%= t("completion", scope: "decidim.forms.user_answers_serializer") %> | +<%= t("created_at", scope: "decidim.forms.user_answers_serializer") %> | ++ |
---|---|---|---|---|---|
<%= @participant.session_token %> | +<%= @participant.status %> | +<%= @participant.ip_hash %> | +<%= display_percentage(@participant.completion) %> | +<%= l @participant.answered_at, format: :short %> | +
<%= t("models.survey.fields.title", scope: "decidim.surveys") %> | +<%= t("models.survey.fields.questions", scope: "decidim.surveys") %> | +<%= t("models.survey.fields.answers", scope: "decidim.surveys") %> | +<%= t("models.survey.fields.status", scope: "decidim.surveys") %> | +<%= t("actions.title", scope: "decidim.surveys") %> | +
---|---|---|---|---|
<%= decidim_sanitize_translated(survey.title) %> | +<%= survey.questionnaire.questions.size %> | +<%= survey.questionnaire.answers.size %> | +<%= survey.open? ? t("models.survey.status.open", scope: "decidim.surveys") : t("models.survey.status.closed", scope: "decidim.surveys") %> | ++ <%= icon_link_to "pencil-line", edit_survey_path(survey), t("actions.edit", scope: "decidim.surveys"), class: "action-icon--edit" %> + <%= icon_link_to "survey-line", edit_questions_survey_path(survey), t("actions.manage_questions", scope: "decidim.surveys"), class: "action-icon--copy" %> + <% if allowed_to? :preview, :questionnaire %> + <%= icon_link_to "eye-line", resource_locator(survey).path, t("actions.preview", scope: "decidim.surveys"), class: "action-icon--preview", target: :blank, data: { "external-link": false } %> + <% end %> + <% if allowed_to?(:update, :questionnaire) %> + <% if survey.published? %> + <%= icon_link_to "close-circle-line", unpublish_survey_path(survey), t("actions.unpublish", scope: "decidim.admin"), method: :put, class: "action-icon--unpublish" %> + <% elsif survey.clean_after_publish? %> + <%= icon_link_to "check-line", publish_survey_path(survey), t("actions.publish", scope: "decidim.admin"), method: :put, class: "action-icon--publish", data: { confirm: t("actions.answers_alert", scope: "decidim.surveys", answers_count: survey.questionnaire.answers.size) } %> + <% else %> + <%= icon_link_to "check-line", publish_survey_path(survey), t("actions.publish", scope: "decidim.admin"), method: :put, class: "action-icon--publish" %> + <% end %> + <% end %> + <%= resource_permissions_link(survey) %> + <%= icon_link_to "delete-bin-line", survey_path(survey), t("actions.destroy", scope: "decidim.surveys"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.surveys") } %> + | +
New description
", + ca: "Nova descripció
", + es: "Nueva descripción
" + } + end + let!(:questionnaire) { create(:questionnaire, description:) } + let!(:survey) { create(:survey, :published, component:, questionnaire:) } + + it "updates the questionnaire description" do + visit questionnaire_public_path + choose "All" + + expect(page).to have_content("New description") + end + end + + def manage_questions_path + Decidim::EngineRouter.admin_proxy(component).edit_questions_survey_path(survey) + end + + def update_component_settings_or_attributes + survey.update!(allow_answers: true) + end + + def see_questionnaire_questions + choose "All" + click_on decidim_sanitize_translated(questionnaire.title) + end + def questionnaire_edit_path - manage_component_path(component) + Decidim::EngineRouter.admin_proxy(component).edit_survey_path(survey) end def questionnaire_public_path diff --git a/decidim-surveys/spec/system/private_space_survey_spec.rb b/decidim-surveys/spec/system/private_space_survey_spec.rb index b0fdea87d5f32..3cc5c2679d8ef 100644 --- a/decidim-surveys/spec/system/private_space_survey_spec.rb +++ b/decidim-surveys/spec/system/private_space_survey_spec.rb @@ -28,7 +28,7 @@ let!(:participatory_space_private_user) { create(:participatory_space_private_user, user: another_user, privatable_to: participatory_space_private) } let!(:questionnaire) { create(:questionnaire, title:, description:) } - let!(:survey) { create(:survey, component:, questionnaire:) } + let!(:survey) { create(:survey, :published, :allow_answers, component:, questionnaire:) } let!(:question) { create(:questionnaire_question, questionnaire:, position: 0) } let!(:question_conditioned) { create(:questionnaire_question, :conditioned, questionnaire:, position: 1) } @@ -38,7 +38,6 @@ before do switch_to_host(organization.host) - component.update!(default_step_settings: { allow_answers: true }) end def visit_component @@ -51,6 +50,7 @@ def visit_component context "when the user is not logged in" do it "does not allow answering the survey" do visit_component + click_on translated_attribute(questionnaire.title) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) @@ -72,6 +72,7 @@ def visit_component it "allows answering the survey" do visit_component + click_on translated_attribute(questionnaire.title) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) @@ -98,6 +99,7 @@ def visit_component it "not allows answering the survey" do visit_component + click_on translated_attribute(questionnaire.title) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) @@ -133,10 +135,13 @@ def visit_component it "allows answering the survey" do visit_component + choose "All" expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) + click_on translated_attribute(questionnaire.title) + fill_in question.body["en"], with: "My first answer" check "questionnaire_tos_agreement" diff --git a/decidim-surveys/spec/system/registered_user_survey_spec.rb b/decidim-surveys/spec/system/registered_user_survey_spec.rb index 56f05fe7706fc..6f4775bd5b9e7 100644 --- a/decidim-surveys/spec/system/registered_user_survey_spec.rb +++ b/decidim-surveys/spec/system/registered_user_survey_spec.rb @@ -23,7 +23,7 @@ } end let!(:questionnaire) { create(:questionnaire, title:, description:) } - let!(:survey) { create(:survey, component:, questionnaire:) } + let!(:survey) { create(:survey, :published, component:, questionnaire:) } let!(:question) { create(:questionnaire_question, questionnaire:, position: 0) } let(:mailer) { double(deliver_later: true) } @@ -32,6 +32,8 @@ context "when the survey does not allow answers" do it "does not allow answering the survey" do visit_component + choose "All" + click_on translated_attribute(questionnaire.title) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) @@ -45,17 +47,9 @@ context "when the survey allow answers" do let(:organization) { create(:organization) } let!(:user) { create(:user, :confirmed, organization:) } + let!(:survey) { create(:survey, :published, :allow_answers, allow_unregistered: false, component:, questionnaire:) } before do - component.update!( - step_settings: { - component.participatory_space.active_step.id => { - allow_answers: true, - allow_unregistered: false - } - } - ) - login_as user, scope: :user end @@ -63,6 +57,7 @@ allow(Decidim::Surveys::SurveyConfirmationMailer).to receive(:confirmation).and_return(mailer) visit_component + click_on translated_attribute(questionnaire.title) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) diff --git a/decidim-surveys/spec/system/survey_spec.rb b/decidim-surveys/spec/system/survey_spec.rb index d845b4051e030..94027f85ceca0 100644 --- a/decidim-surveys/spec/system/survey_spec.rb +++ b/decidim-surveys/spec/system/survey_spec.rb @@ -28,7 +28,7 @@ end let(:user) { create(:user, :confirmed, organization: component.organization) } let!(:questionnaire) { create(:questionnaire, title:, description:) } - let!(:survey) { create(:survey, component:, questionnaire:) } + let!(:survey) { create(:survey, :published, component:, questionnaire:) } let!(:question) { create(:questionnaire_question, questionnaire:, position: 0, description: question_description) } include_context "with a component" @@ -39,11 +39,15 @@ it "does not allow answering the survey" do visit_component + choose "All" + expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) expect(page).to have_no_i18n_content(question.body) + click_on translated_attribute(questionnaire.title) + expect(page).to have_content("The form is closed and cannot be answered.") end end @@ -60,6 +64,8 @@ component.update!(permissions:) visit_component + choose "All" + click_on translated_attribute(questionnaire.title) end it_behaves_like "accessible page" @@ -72,17 +78,20 @@ context "when the survey allow answers" do context "when the survey is closed by start and end dates" do before do - component.update!(settings: { starts_at: 1.week.ago, ends_at: 1.day.ago }) + survey.update!(starts_at: 1.week.ago, ends_at: 1.day.ago) end it "does not allow answering the survey" do visit_component + choose "All" expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) expect(page).to have_no_i18n_content(question.body) + click_on translated_attribute(questionnaire.title) + expect(page).to have_content("The form is closed and cannot be answered.") end end @@ -92,14 +101,7 @@ let(:callout_success) { "Survey successfully answered." } before do - component.update!( - step_settings: { - component.participatory_space.active_step.id => { - allow_answers: true - } - }, - settings: { starts_at: 1.week.ago, ends_at: 1.day.from_now } - ) + survey.update!(allow_answers: true, starts_at: 1.week.ago, ends_at: 1.day.from_now) end it_behaves_like "has questionnaire" @@ -107,16 +109,14 @@ context "when displaying questionnaire rich content" do before do - component.update!( - step_settings: { - component.participatory_space.active_step.id => { - allow_answers: true, - allow_unregistered: true - } - }, - settings: { starts_at: 1.week.ago, ends_at: 1.day.from_now } + survey.update!( + allow_answers: true, + allow_unregistered: true, + starts_at: 1.week.ago, + ends_at: 1.day.from_now ) visit_component + click_on translated_attribute(questionnaire.title) end context "when displaying questionnaire description" do @@ -129,8 +129,22 @@ end end + context "when survey has a custom announcement" do + let!(:survey) { create(:survey, :published, :announcement, :allow_answers, :allow_unregistered, component:, questionnaire:) } + + before do + visit_component + click_on translated_attribute(questionnaire.title) + end + + it "displays the announcement in the survey" do + expect(page).to have_content("This is a custom announcement.") + end + end + context "when survey has action log entry" do - let!(:action_log) { create(:action_log, user:, organization: component.organization, resource: survey, component:, participatory_space: component.participatory_space, visibility: "all") } + let!(:action_log) { create(:action_log, user:, action: "publish", organization: component.organization, resource: survey, component:, participatory_space: component.participatory_space, visibility: "all") } + let(:router) { Decidim::EngineRouter.main_proxy(component) } it "shows action log entry" do @@ -144,4 +158,8 @@ def questionnaire_public_path main_component_path(component) end + + def see_questionnaire_questions + click_on translated_attribute(questionnaire.title) + end end diff --git a/decidim-surveys/spec/system/unregistered_user_survey_spec.rb b/decidim-surveys/spec/system/unregistered_user_survey_spec.rb index b5e616b591e17..ac1c03f4ab336 100644 --- a/decidim-surveys/spec/system/unregistered_user_survey_spec.rb +++ b/decidim-surveys/spec/system/unregistered_user_survey_spec.rb @@ -23,7 +23,7 @@ } end let!(:questionnaire) { create(:questionnaire, title:, description:) } - let!(:survey) { create(:survey, component:, questionnaire:) } + let!(:survey) { create(:survey, :published, component:, questionnaire:) } let!(:question) { create(:questionnaire_question, questionnaire:, position: 0) } include_context "with a component" @@ -31,6 +31,8 @@ context "when the survey does not allow answers" do it "does not allow answering the survey" do visit_component + choose "All" + click_on translated_attribute(questionnaire.title) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) @@ -45,18 +47,12 @@ let(:last_answer) { questionnaire.answers.last } before do - component.update!( - step_settings: { - component.participatory_space.active_step.id => { - allow_answers: true, - allow_unregistered: true - } - } - ) + survey.update!(allow_answers: true, allow_unregistered: true) end it "allows answering the questionnaire" do visit_component + click_on translated_attribute(questionnaire.title) expect(page).to have_i18n_content(questionnaire.title) expect(page).to have_i18n_content(questionnaire.description) @@ -82,6 +78,7 @@ context "and honeypot is filled" do it "fails with spam complain" do visit_component + click_on translated_attribute(questionnaire.title) fill_in question.body["en"], with: "My first answer" fill_in "honeypot_id", with: "I am a robot" diff --git a/decidim-templates/app/controllers/decidim/templates/admin/questionnaire_templates_controller.rb b/decidim-templates/app/controllers/decidim/templates/admin/questionnaire_templates_controller.rb index 7e9d4c7a7ad3a..a0eb02590d96e 100644 --- a/decidim-templates/app/controllers/decidim/templates/admin/questionnaire_templates_controller.rb +++ b/decidim-templates/app/controllers/decidim/templates/admin/questionnaire_templates_controller.rb @@ -7,8 +7,10 @@ module Admin # class QuestionnaireTemplatesController < Decidim::Templates::Admin::ApplicationController include Decidim::TranslatableAttributes + include Decidim::Forms::Admin::Concerns::HasQuestionnaire + helper Decidim::Forms::Admin::ApplicationHelper - helper_method :template + helper_method :template, :questionnaire add_breadcrumb_item_from_menu :admin_template_types_menu @@ -135,8 +137,24 @@ def skip redirect_to URI.parse(params[:url]).path end + def edit_questions_template + "decidim/templates/admin/questionnaire_templates/edit_questions" + end + + def after_update_url + edit_questionnaire_template_path(template) + end + private + def questionnaire + template.templatable + end + + def i18n_flashes_scope + "decidim.forms.admin.questionnaires" + end + def collection @collection ||= current_organization.templates.where(templatable_type: "Decidim::Forms::Questionnaire") end diff --git a/decidim-templates/app/views/decidim/templates/admin/questionnaire_templates/edit.html.erb b/decidim-templates/app/views/decidim/templates/admin/questionnaire_templates/edit.html.erb index 79e0fa10fd5d3..83a5f7ca1b529 100644 --- a/decidim-templates/app/views/decidim/templates/admin/questionnaire_templates/edit.html.erb +++ b/decidim-templates/app/views/decidim/templates/admin/questionnaire_templates/edit.html.erb @@ -17,8 +17,10 @@