diff --git a/OVERLOADS.md b/OVERLOADS.md index 9e19c7a757..b8b89204a4 100644 --- a/OVERLOADS.md +++ b/OVERLOADS.md @@ -170,3 +170,11 @@ end * `spec/mailers/decidim/budgets/vote_reminder_mailer_spec.rb` * `spec/services/decidim/budgets/order_reminder_generator_spec.rb` * `spec/system/admin_reminds_users_with_pending_orders_spec.rb` + +## Fix survey validation +* `app/cells/decidim/forms/step_navigation/show.erb` +* `app/packs/src/decidim/decidim_application.js` +* `app/views/decidim/forms/questionnaires/show.html.erb` +* `config/initializers/decidim_verifications.rb` +* `spec/shared/has_questionnaire.rb` +* `spec/system/survey_spec.rb` diff --git a/app/cells/decidim/forms/step_navigation/show.erb b/app/cells/decidim/forms/step_navigation/show.erb new file mode 100644 index 0000000000..4859140494 --- /dev/null +++ b/app/cells/decidim/forms/step_navigation/show.erb @@ -0,0 +1,37 @@ +
diff --git a/app/packs/src/decidim/decidim_application.js b/app/packs/src/decidim/decidim_application.js index 5d5dcf59f4..e9f797ef12 100644 --- a/app/packs/src/decidim/decidim_application.js +++ b/app/packs/src/decidim/decidim_application.js @@ -3,3 +3,38 @@ // Load images require.context("../../images", true) + +import $ from "jquery" +import "jquery-validation" + +$(() => { + if($(".submit_survey").length) { + $("body").on('DOMNodeInserted', '.confirm-reveal', function () { + $(".confirm-reveal .button:first").on("mouseup", function () { + $("form.answer-questionnaire").validate({ + ignore: "thrhwrt", + errorPlacement: function (error, element) {}, + focusInvalid: false, + invalidHandler: function(form, validator) { + + $(".questionnaire-step").each(function () { + $(this).removeClass("hide"); + }); + $(".next_survey").hide(); + $(".back_survey").hide(); + + if (!validator.numberOfInvalids()) + return; + + const y = validator.errorList[0].element.parentElement.getBoundingClientRect().top + window.pageYOffset - 10; + + window.scrollTo({top: y, behavior: 'smooth'}); + } + }); + if ($("form.answer-questionnaire").valid()) { + $(".survey-form").submit(); + } + }); + }); + } +}); diff --git a/app/views/decidim/forms/questionnaires/show.html.erb b/app/views/decidim/forms/questionnaires/show.html.erb new file mode 100644 index 0000000000..8dfe542939 --- /dev/null +++ b/app/views/decidim/forms/questionnaires/show.html.erb @@ -0,0 +1,155 @@ +<% add_decidim_meta_tags({ + title: translated_attribute(questionnaire.title), + description: translated_attribute(questionnaire.description), +}) %> + +<% columns = allow_answers? && visitor_can_answer? && @form.responses.map(&:question).any?(&:matrix?) ? 9 : 6 %> + +<%= render partial: "decidim/shared/component_announcement" if current_component.manifest_name == "surveys" %> + +<%= t(".questionnaire_not_published.body") %>
+<%= t(".questionnaire_answered.body") %>
+<%= t(".questionnaire_for_private_users.body") %>
++ <%= t(".answer_questionnaire.anonymous_user_message", sign_in_link: decidim.new_user_session_path, sign_up_link: decidim.new_user_registration_path).html_safe %> +
+ +<%= t(".questionnaire_closed.body") %>
+Survey's content
", + "ca" => "Contingut de l'enquesta
", + "es" => "Contenido de la encuesta
" + } + end + let(:user) { create(:user, :confirmed, organization: component.organization) } + let!(:questionnaire) { create(:questionnaire, title: title, description: description) } + let!(:survey) { create(:survey, component: component, questionnaire: questionnaire) } + let!(:question) { create(:questionnaire_question, questionnaire: questionnaire, position: 0) } + + include_context "with a component" + + it_behaves_like "preview component with share_token" + + context "when the survey doesn't allow answers" do + it "does not allow answering the survey" do + visit_component + + expect(page).to have_i18n_content(questionnaire.title, upcase: true) + expect(page).to have_i18n_content(questionnaire.description) + + expect(page).to have_no_i18n_content(question.body) + + expect(page).to have_content("The form is closed and cannot be answered.") + end + end + + context "when the survey requires permissions to be answered" do + before do + permissions = { + answer: { + authorization_handlers: { + "dummy_authorization_handler" => { "options" => {} } + } + } + } + + component.update!(permissions: permissions) + visit_component + end + + it "shows a modal dialog" do + expect(page).to have_content("I fill in my phone number") + end + end + + 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 }) + end + + it "does not allow answering the survey" do + visit_component + + expect(page).to have_i18n_content(questionnaire.title, upcase: true) + expect(page).to have_i18n_content(questionnaire.description) + + expect(page).to have_no_i18n_content(question.body) + + expect(page).to have_content("The form is closed and cannot be answered.") + end + end + + context "when the survey is open" do + 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 } + ) + end + + it_behaves_like "has questionnaire" + end + end + + context "when survey has action log entry" do + let!(:action_log) { create(:action_log, user: user, organization: component.organization, resource: survey, component: component, participatory_space: component.participatory_space, visibility: "all") } + let(:router) { Decidim::EngineRouter.main_proxy(component) } + + it "shows action log entry" do + page.visit decidim.profile_activity_path(nickname: user.nickname) + expect(page).to have_content("New survey at #{translated(survey.component.participatory_space.title)}") + expect(page).to have_link(translated(survey.questionnaire.title), href: router.survey_path(survey)) + end + end + + def questionnaire_public_path + main_component_path(component) + end +end diff --git a/yarn.lock b/yarn.lock index c66f0da89a..aea9f8a0bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5283,15 +5283,20 @@ dependencies: "jquery" ">=1.8.0 <4.0.0" +"jquery-validation@^1.19.5": + "integrity" "sha512-X2SmnPq1mRiDecVYL8edWx+yTBZDyC8ohWXFhXdtqFHgU9Wd4KHkvcbCoIZ0JaSaumzS8s2gXSkP8F7ivg/8ZQ==" + "resolved" "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.5.tgz" + "version" "1.19.5" + "jquery.autocomplete@1.2.0": "integrity" "sha512-aoJC3KVrPpRGaZBUo9UxhwznYmQ0UuYd+FfjP9RAKmyB+1T3OLIjuQImT8pKX6eKpBt1z9JmD48GiD2Dx303bA==" "resolved" "https://registry.npmjs.org/jquery.autocomplete/-/jquery.autocomplete-1.2.0.tgz" "version" "1.2.0" -"jquery@^3.2.1", "jquery@>=1.8.0 <4.0.0", "jquery@>=3.4.1", "jquery@>=3.6.0": - "integrity" "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" - "resolved" "https://registry.npmjs.org/jquery/-/jquery-3.6.1.tgz" - "version" "3.6.1" +"jquery@^1.7 || ^2.0 || ^3.1", "jquery@^3.2.1", "jquery@^3.6.3", "jquery@>=1.8.0 <4.0.0", "jquery@>=3.4.1", "jquery@>=3.6.0": + "integrity" "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" + "resolved" "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz" + "version" "3.6.3" "js-tokens@^3.0.0 || ^4.0.0", "js-tokens@^4.0.0": "integrity" "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="