diff --git a/CHANGELOG.md b/CHANGELOG.md index c0dd5ce4e..66a11145f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ CHANGELOG ========= +v0.8.4 +------ + +Compatibility: + - Decidim v0.26.x + - Decidim v0.25.x + +Features: + - Feature: Override validation rules for title and body in proposals, with constrains available + - Improve loading process to facilitate development + v0.8.3 ------ diff --git a/Gemfile.lock b/Gemfile.lock index cbd37b769..c37c9f548 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - decidim-decidim_awesome (0.8.3) + decidim-decidim_awesome (0.8.4) decidim-admin (>= 0.25.0, < 0.27) decidim-core (>= 0.25.0, < 0.27) sassc (~> 2.3) diff --git a/README.md b/README.md index 6851642be..55718732e 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,19 @@ Using a link with a query string (ie: `/take-me-somewhere?locale=es`) that will ![Custom redirections screenshot](examples/custom-redirections.png) +#### 14. Custom validation rules for title and body in proposals + +Configure as you wish how the fields "title" and "body" are validated in proposals creation. + +Rules available: + +* Minimum title and body length (defaults to 15 chars). +* Maximum percentage of capital letters for title and body (defaults to 25%). +* Maximum number of "marks" (aka: exclamation and interrogation signs) that can be consective in the title or the body (defaults to 1). +* Enable/disable forcing to start the title or the body with a capital letter (defaults to "enabled"). + +![Custom validations](examples/custom_validations.png) + #### To be continued... We're not done! Please check the [issues](/Platoniq/decidim-module-decidim_awesome/issues) (and participate) to see what's on our mind diff --git a/app/forms/decidim/decidim_awesome/admin/config_form.rb b/app/forms/decidim/decidim_awesome/admin/config_form.rb index c5b962aff..57ed59307 100644 --- a/app/forms/decidim/decidim_awesome/admin/config_form.rb +++ b/app/forms/decidim/decidim_awesome/admin/config_form.rb @@ -22,12 +22,26 @@ class ConfigForm < Decidim::Form attribute :intergram_for_admins_settings, IntergramForm attribute :intergram_for_public, Boolean attribute :intergram_for_public_settings, IntergramForm + attribute :validate_title_min_length, Integer, default: 15 + attribute :validate_title_max_caps_percent, Integer, default: 25 + attribute :validate_title_max_marks_together, Integer, default: 1 + attribute :validate_title_start_with_caps, Boolean, default: true + attribute :validate_body_min_length, Integer, default: 15 + attribute :validate_body_max_caps_percent, Integer, default: 25 + attribute :validate_body_max_marks_together, Integer, default: 1 + attribute :validate_body_start_with_caps, Boolean, default: true # collect all keys anything not specified in the params (UpdateConfig command ignores it) attr_accessor :valid_keys validate :css_syntax, if: ->(form) { form.scoped_styles.present? } validate :json_syntax, if: ->(form) { form.proposal_custom_fields.present? } + validates :validate_title_min_length, presence: true, numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 100 } + validates :validate_title_max_caps_percent, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 } + validates :validate_title_max_marks_together, presence: true, numericality: { greater_than_or_equal_to: 1 } + validates :validate_body_min_length, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :validate_body_max_caps_percent, presence: true, numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: 100 } + validates :validate_body_max_marks_together, presence: true, numericality: { greater_than_or_equal_to: 1 } # TODO: validate non general admins are here diff --git a/app/forms/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb b/app/forms/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb index 98b70a627..91bf5113c 100644 --- a/app/forms/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb +++ b/app/forms/decidim/decidim_awesome/proposals/proposal_wizard_create_step_form_override.rb @@ -11,11 +11,15 @@ module ProposalWizardCreateStepFormOverride clear_validators! validates :title, presence: true, etiquette: true - validates :title, length: { in: 15..150 } - validates :body, presence: true, etiquette: true, unless: ->(form) { form.override_validations? } + validates :title, proposal_length: { + minimum: ->(form) { form.minimum_title_length }, + maximum: 150 + } + validates :body, presence: true, unless: ->(form) { form.override_validations? || form.minimum_body_length.zero? } + validates :body, etiquette: true, unless: ->(form) { form.override_validations? } validates :body, proposal_length: { - minimum: 15, - maximum: ->(record) { record.override_validations? ? 0 : record.component.settings.proposal_length } + minimum: ->(form) { form.minimum_body_length }, + maximum: ->(form) { form.override_validations? ? 0 : form.component.settings.proposal_length } } validate :body_is_not_bare_template, unless: ->(form) { form.override_validations? } @@ -26,10 +30,24 @@ def override_validations? custom_fields.present? end + def minimum_title_length + awesome_config.config[:validate_title_min_length].to_i + end + + def minimum_body_length + awesome_config.config[:validate_body_min_length].to_i + end + def custom_fields - awesome_config = Decidim::DecidimAwesome::Config.new(context.current_organization) - awesome_config.context_from_component(context.current_component) - awesome_config.collect_sub_configs_values("proposal_custom_field") + @custom_fields ||= awesome_config.collect_sub_configs_values("proposal_custom_field") + end + + def awesome_config + @awesome_config ||= begin + conf = Decidim::DecidimAwesome::Config.new(context.current_organization) + conf.context_from_component(context.current_component) + conf + end end end end diff --git a/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb b/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb index b3bda30c4..8b514e9d2 100644 --- a/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb +++ b/app/helpers/decidim/decidim_awesome/admin/config_constraints_helpers.rb @@ -15,7 +15,11 @@ def check(status) def menus @menus ||= { editors: config_enabled?([:allow_images_in_full_editor, :allow_images_in_small_editor, :use_markdown_editor, :allow_images_in_markdown_editor]), - proposals: config_enabled?(:allow_images_in_proposals), + proposals: config_enabled?([:allow_images_in_proposals, + :validate_title_min_length, :validate_title_max_caps_percent, + :validate_title_max_marks_together, :validate_title_start_with_caps, + :validate_body_min_length, :validate_body_max_caps_percent, + :validate_body_max_marks_together, :validate_body_start_with_caps]), surveys: config_enabled?(:auto_save_forms), styles: config_enabled?(:scoped_styles), proposal_custom_fields: config_enabled?(:proposal_custom_fields), diff --git a/app/validators/concerns/decidim/decidim_awesome/etiquette_validator_override.rb b/app/validators/concerns/decidim/decidim_awesome/etiquette_validator_override.rb new file mode 100644 index 000000000..4e276892e --- /dev/null +++ b/app/validators/concerns/decidim/decidim_awesome/etiquette_validator_override.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Decidim + module DecidimAwesome + module EtiquetteValidatorOverride + extend ActiveSupport::Concern + + included do + private + + def validate_caps(record, attribute, value) + percent = awesome_config(record, "validate_#{attribute}_max_caps_percent").to_f + return if value.scan(/[[:upper:]]/).length < value.length * percent / 100 + + record.errors.add(attribute, options[:message] || I18n.t("too_much_caps", scope: "decidim.decidim_awesome.validators", percent: percent.round)) + end + + def validate_marks(record, attribute, value) + marks = awesome_config(record, "validate_#{attribute}_max_marks_together").to_i + 1 + return if value.scan(/[!?¡¿]{#{marks},}/).empty? + + record.errors.add(attribute, options[:message] || :too_many_marks) + end + + def validate_caps_first(record, attribute, value) + return unless awesome_config(record, "validate_#{attribute}_start_with_caps") + return if value.scan(/\A[[:lower:]]{1}/).empty? + + record.errors.add(attribute, options[:message] || :must_start_with_caps) + end + + def awesome_config(record, var) + config = record.try(:awesome_config)&.config + return unless config.is_a?(Hash) + + config[var.to_sym] + end + end + end + end +end diff --git a/app/views/decidim/decidim_awesome/admin/config/_form_proposals.html.erb b/app/views/decidim/decidim_awesome/admin/config/_form_proposals.html.erb index 61ad49b8f..b563aca1f 100644 --- a/app/views/decidim/decidim_awesome/admin/config/_form_proposals.html.erb +++ b/app/views/decidim/decidim_awesome/admin/config/_form_proposals.html.erb @@ -2,9 +2,89 @@ <% if config_enabled? :allow_images_in_proposals %>

<%= t("rich_text_editor_in_public_views", scope: "decidim.decidim_awesome.admin.config") if current_organization.rich_text_editor_in_public_views %>

- <%= form.check_box :allow_images_in_proposals %> + <%= form.check_box :allow_images_in_proposals, disabled: current_organization.rich_text_editor_in_public_views %>

<%= t("help.allow_images_in_proposals", scope: "decidim.decidim_awesome.admin.config.form") %>

- <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :allow_images_in_proposals, constraints: constraints_for(:allow_images_in_proposals) }) %> + <% unless current_organization.rich_text_editor_in_public_views %> + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :allow_images_in_proposals, constraints: constraints_for(:allow_images_in_proposals) }) %> + <% end %> <% end %> + +<% if config_enabled? %i(validate_title_min_length validate_title_max_caps_percent validate_title_max_marks_together validate_title_start_with_caps) %> + + + +
+
+

<%= t("validators.title", scope: "decidim.decidim_awesome.admin.config.form") %>

+
+
+ +
+ <% if config_enabled? :validate_title_start_with_caps %> + <%= form.check_box :validate_title_start_with_caps %> + + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_title_start_with_caps, constraints: constraints_for(:validate_title_start_with_caps) }) %> + <% end %> + + <% if config_enabled? :validate_title_min_length %> + <%= form.number_field :validate_title_min_length %> +

<%= t("help.validate_title_min_length", scope: "decidim.decidim_awesome.admin.config.form") %>

+ + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_title_min_length, constraints: constraints_for(:validate_title_min_length) }) %> + <% end %> + + <% if config_enabled? :validate_title_max_caps_percent %> + <%= form.number_field :validate_title_max_caps_percent %> +

<%= t("help.validate_title_max_caps_percent", scope: "decidim.decidim_awesome.admin.config.form") %>

+ + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_title_max_caps_percent, constraints: constraints_for(:validate_title_max_caps_percent) }) %> + <% end %> + + <% if config_enabled? :validate_title_max_marks_together %> + <%= form.number_field :validate_title_max_marks_together %> +

<%= t("help.validate_title_max_marks_together", scope: "decidim.decidim_awesome.admin.config.form") %>

+ + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_title_max_marks_together, constraints: constraints_for(:validate_title_max_marks_together) }) %> + <% end %> +
+<% end %> + +<% if config_enabled? %i(validate_body_min_length validate_body_max_caps_percent validate_body_max_marks_together validate_body_start_with_caps) %> +
+
+ +
+
+

<%= t("validators.body", scope: "decidim.decidim_awesome.admin.config.form") %>

+
+
+ <% if config_enabled? :validate_body_start_with_caps %> + <%= form.check_box :validate_body_start_with_caps %> + + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_body_start_with_caps, constraints: constraints_for(:validate_body_start_with_caps) }) %> + <% end %> + + <% if config_enabled? :validate_body_min_length %> + <%= form.number_field :validate_body_min_length %> +

<%= t("help.validate_body_min_length", scope: "decidim.decidim_awesome.admin.config.form") %>

+ + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_body_min_length, constraints: constraints_for(:validate_body_min_length) }) %> + <% end %> + + <% if config_enabled? :validate_body_max_caps_percent %> + <%= form.number_field :validate_body_max_caps_percent %> +

<%= t("help.validate_body_max_caps_percent", scope: "decidim.decidim_awesome.admin.config.form") %>

+ + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_body_max_caps_percent, constraints: constraints_for(:validate_body_max_caps_percent) }) %> + <% end %> + + <% if config_enabled? :validate_body_max_marks_together %> + <%= form.number_field :validate_body_max_marks_together %> +

<%= t("help.validate_body_max_marks_together", scope: "decidim.decidim_awesome.admin.config.form") %>

+ + <%= render(partial: "decidim/decidim_awesome/admin/config/constraints", locals: { key: :validate_body_max_marks_together, constraints: constraints_for(:validate_body_max_marks_together) }) %> + <% end %> +
+<% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 7dbab1f18..faa6a3d56 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -26,6 +26,18 @@ en: scoped_admins: Scoped admins group %{id} scoped_styles: Custom styles %{id} use_markdown_editor: Use a Markdown editor instead of the HTML editor + validate_body_max_caps_percent: Maximum allowed percentage of capital letters + for the body + validate_body_max_marks_together: Maximum consecutive marks symbols allowed + in the body + validate_body_min_length: Minimum required characters for the body + validate_body_start_with_caps: Force the body to start with a capital letter + validate_title_max_caps_percent: Maximum allowed percentage of capital letters + for the title + validate_title_max_marks_together: Maximum consecutive marks symbols allowed + in the title + validate_title_min_length: Minimum required characters for the title + validate_title_start_with_caps: Force the title to start with a capital letter constraint: component_id: or specifically in component_manifest: Only in components of type @@ -199,6 +211,23 @@ en: use_markdown_editor: This will substitute the Quill WYSIWYG editor, to use a Markdown editor instead. Text will be converted and saved as HTML in the database. + validate_body_max_caps_percent: Zero won't allow any capital letter, + 100 will force to write everything in capital letters + validate_body_max_marks_together: 'Limit the number of question and + exclamation marks that can be written together. Ie: if it is 2, then + ''!!!'' won''t be allowed in the text' + validate_body_min_length: This number can be zero, this will effectively + make this field non-mandatory + validate_title_max_caps_percent: Zero won't allow any capital letter, + 100 will force to write everything in capital letters + validate_title_max_marks_together: 'Limit the number of question and + exclamation marks that can be written together. Ie: if it is 2, then + ''!!!'' won''t be allowed in the text' + validate_title_min_length: Title is always mandatory and this number + cannot be zero + validators: + body: User input validations for the "body" field + title: User input validations for the "title" field form_proposal_custom_fields: new: Add a new "custom fields" box remove: Remove this "custom fields" box @@ -388,6 +417,9 @@ en: show: view_meeting: View meeting view_proposal: View proposal + validators: + too_much_caps: Is using too many capital letters (over %{percent}% of the + text) layouts: decidim: admin: diff --git a/examples/custom_validations.png b/examples/custom_validations.png new file mode 100644 index 000000000..c32b3d527 Binary files /dev/null and b/examples/custom_validations.png differ diff --git a/lib/decidim/decidim_awesome/awesome.rb b/lib/decidim/decidim_awesome/awesome.rb index e211514ab..10219e02c 100644 --- a/lib/decidim/decidim_awesome/awesome.rb +++ b/lib/decidim/decidim_awesome/awesome.rb @@ -61,6 +61,41 @@ module DecidimAwesome false end + # Configuration options to handle different validations in proposals + # (maybe in the future will apply to other places) + # Set it to :disabled if you don't want to use this feature + config_accessor :validate_title_min_length do + 15 + end + + config_accessor :validate_title_max_caps_percent do + 25 + end + + config_accessor :validate_title_max_marks_together do + 1 + end + + config_accessor :validate_title_start_with_caps do + true + end + + config_accessor :validate_body_min_length do + 15 + end + + config_accessor :validate_body_max_caps_percent do + 25 + end + + config_accessor :validate_body_max_marks_together do + 1 + end + + config_accessor :validate_body_start_with_caps do + true + end + config_accessor :intergram_for_public do false end diff --git a/lib/decidim/decidim_awesome/checksums.yml b/lib/decidim/decidim_awesome/checksums.yml index bdd1c01ee..51e064ee4 100644 --- a/lib/decidim/decidim_awesome/checksums.yml +++ b/lib/decidim/decidim_awesome/checksums.yml @@ -2,6 +2,8 @@ decidim-admin: /app/views/layouts/decidim/admin/_header.html.erb: decidim-0.25: 1aff077428830b12306d6c42e6b37216 decidim-core: + /app/validators/etiquette_validator.rb: + decidim-0.25: f7a4a652005385a994208f1ab41c4f08 /app/views/layouts/decidim/_head.html.erb: decidim-0.25: eb490aa482477ff70f541d20cddec773 decidim-0.26: 0927fc81123addec70853c2e7986c538 diff --git a/lib/decidim/decidim_awesome/config.rb b/lib/decidim/decidim_awesome/config.rb index 32ff56e32..0823c5354 100644 --- a/lib/decidim/decidim_awesome/config.rb +++ b/lib/decidim/decidim_awesome/config.rb @@ -63,8 +63,8 @@ def organization_config def unfiltered_config valid = @vars.map { |v| [v.var.to_sym, v.value] }.to_h - map_defaults do |key| - valid[key].presence + map_defaults do |key, val| + valid.has_key?(key) ? valid[key] : val end end @@ -146,8 +146,8 @@ def map_defaults defaults.map do |key, val| value = false unless val == :disabled - value = yield(key) || val - value = val.merge(value.transform_keys(&:to_sym)) if val.is_a? Hash + value = yield(key, val) + value = val.merge(value.transform_keys(&:to_sym)) if val.is_a?(Hash) && value.is_a?(Hash) end [key, value] end.to_h @@ -158,8 +158,8 @@ def calculate_config valid = @vars.filter { |item| enabled_for_organization?(item.var) && valid_in_context?(item.all_constraints) } .map { |v| [v.var.to_sym, v.value] }.to_h - map_defaults do |key| - valid[key].presence + map_defaults do |key, val| + valid.has_key?(key) ? valid[key] : val end end diff --git a/lib/decidim/decidim_awesome/engine.rb b/lib/decidim/decidim_awesome/engine.rb index ecd21d42b..d62874a18 100644 --- a/lib/decidim/decidim_awesome/engine.rb +++ b/lib/decidim/decidim_awesome/engine.rb @@ -27,25 +27,52 @@ class Engine < ::Rails::Engine # Include additional helpers globally ActionView::Base.include(Decidim::DecidimAwesome::AwesomeHelpers) - # override user's admin property - Decidim::User.include(Decidim::DecidimAwesome::UserOverride) if DecidimAwesome.enabled?(:scoped_admins) - - # redirect unauthorized scoped admins to allowed places or custom redirects if configured - Decidim::ErrorsController.include(Decidim::DecidimAwesome::NotFoundRedirect) if DecidimAwesome.enabled?([:scoped_admins, :custom_redirects]) + # Override EtiquetteValidator + EtiquetteValidator.include(Decidim::DecidimAwesome::EtiquetteValidatorOverride) if DecidimAwesome.enabled?([:validate_title_max_caps_percent, + :validate_title_max_marks_together, + :validate_title_start_with_caps, + :validate_body_max_caps_percent, + :validate_body_max_marks_together, + :validate_body_start_with_caps]) # Custom fields need to deal with several places - if DecidimAwesome.enabled?(:proposal_custom_fields) - Decidim::Proposals::ApplicationHelper.include(Decidim::DecidimAwesome::Proposals::ApplicationHelperOverride) + if DecidimAwesome.enabled?([:proposal_custom_fields, + :validate_title_min_length, + :validate_title_max_caps_percent, + :validate_title_max_marks_together, + :validate_title_start_with_caps, + :validate_body_min_length, + :validate_body_max_caps_percent, + :validate_body_max_marks_together, + :validate_body_start_with_caps]) Decidim::Proposals::ProposalWizardCreateStepForm.include(Decidim::DecidimAwesome::Proposals::ProposalWizardCreateStepFormOverride) - Decidim::AmendmentsHelper.include(Decidim::DecidimAwesome::AmendmentsHelperOverride) end + # override user's admin property + Decidim::User.include(Decidim::DecidimAwesome::UserOverride) if DecidimAwesome.enabled?(:scoped_admins) + Decidim::MenuPresenter.include(Decidim::DecidimAwesome::MenuPresenterOverride) Decidim::MenuItemPresenter.include(Decidim::DecidimAwesome::MenuItemPresenterOverride) # Late registering of components to take into account initializer values DecidimAwesome.registered_components.each do |manifest, block| - Decidim.register_component(manifest, &block) unless DecidimAwesome.disabled_components.include?(manifest) + next if DecidimAwesome.disabled_components.include?(manifest) + next if Decidim.find_component_manifest(manifest) + + Decidim.register_component(manifest, &block) + end + end + + initializer "decidim_decidim_awesome.overrides", after: "decidim.action_controller" do + config.to_prepare do + # redirect unauthorized scoped admins to allowed places or custom redirects if configured + Decidim::ErrorsController.include(Decidim::DecidimAwesome::NotFoundRedirect) if DecidimAwesome.enabled?([:scoped_admins, :custom_redirects]) + + # Custom fields need to deal with several places + if DecidimAwesome.enabled?(:proposal_custom_fields) + Decidim::Proposals::ApplicationHelper.include(Decidim::DecidimAwesome::Proposals::ApplicationHelperOverride) + Decidim::AmendmentsHelper.include(Decidim::DecidimAwesome::AmendmentsHelperOverride) + end end end diff --git a/lib/decidim/decidim_awesome/test/initializer.rb b/lib/decidim/decidim_awesome/test/initializer.rb index 18a5b3586..37410c7de 100644 --- a/lib/decidim/decidim_awesome/test/initializer.rb +++ b/lib/decidim/decidim_awesome/test/initializer.rb @@ -15,7 +15,15 @@ :proposal_custom_fields, :menu, :scoped_admins, - :custom_redirects + :custom_redirects, + :validate_title_min_length, + :validate_title_max_caps_percent, + :validate_title_max_marks_together, + :validate_title_start_with_caps, + :validate_body_min_length, + :validate_body_max_caps_percent, + :validate_body_max_marks_together, + :validate_body_start_with_caps ].each do |conf| config.send("#{conf}=", :disabled) end diff --git a/lib/decidim/decidim_awesome/test/shared_examples/summary_examples.rb b/lib/decidim/decidim_awesome/test/shared_examples/summary_examples.rb index e339a9f33..638bc7435 100644 --- a/lib/decidim/decidim_awesome/test/shared_examples/summary_examples.rb +++ b/lib/decidim/decidim_awesome/test/shared_examples/summary_examples.rb @@ -36,6 +36,7 @@ expect(Decidim::Proposals::ApplicationHelper.included_modules).to include(Decidim::DecidimAwesome::Proposals::ApplicationHelperOverride) expect(Decidim::Proposals::ProposalWizardCreateStepForm.included_modules).to include(Decidim::DecidimAwesome::Proposals::ProposalWizardCreateStepFormOverride) expect(Decidim::AmendmentsHelper.included_modules).to include(Decidim::DecidimAwesome::AmendmentsHelperOverride) + expect(EtiquetteValidator.included_modules).to include(Decidim::DecidimAwesome::EtiquetteValidatorOverride) end else it "concerns are not registered" do @@ -44,6 +45,7 @@ expect(Decidim::Proposals::ApplicationHelper.included_modules).not_to include(Decidim::DecidimAwesome::Proposals::ApplicationHelperOverride) expect(Decidim::Proposals::ProposalWizardCreateStepForm.included_modules).not_to include(Decidim::DecidimAwesome::Proposals::ProposalWizardCreateStepFormOverride) expect(Decidim::AmendmentsHelper.included_modules).not_to include(Decidim::DecidimAwesome::AmendmentsHelperOverride) + expect(EtiquetteValidator.included_modules).not_to include(Decidim::DecidimAwesome::EtiquetteValidatorOverride) end end end diff --git a/lib/decidim/decidim_awesome/version.rb b/lib/decidim/decidim_awesome/version.rb index 398d35c55..8a1120d7a 100644 --- a/lib/decidim/decidim_awesome/version.rb +++ b/lib/decidim/decidim_awesome/version.rb @@ -3,7 +3,7 @@ module Decidim # This holds the decidim-decidim_awesome version. module DecidimAwesome - VERSION = "0.8.3" + VERSION = "0.8.4" COMPAT_DECIDIM_VERSION = [">= 0.25.0", "< 0.27"].freeze end end diff --git a/spec/awesome_summary_spec.rb b/spec/awesome_summary_spec.rb index 8906801ac..3eaee713f 100644 --- a/spec/awesome_summary_spec.rb +++ b/spec/awesome_summary_spec.rb @@ -35,6 +35,15 @@ let!(:intergram_for_public) { create(:awesome_config, organization: organization, var: :intergram_for_public, value: true) } let!(:config_public_settings) { create(:awesome_config, organization: organization, var: :intergram_for_public_settings, value: intergram) } let!(:config_admins_settings) { create(:awesome_config, organization: organization, var: :intergram_for_admins_settings, value: intergram) } + let!(:validate_title_min_length) { create(:awesome_config, organization: organization, var: :validate_title_min_length, value: 10) } + let!(:validate_title_max_caps_percent) { create(:awesome_config, organization: organization, var: :validate_title_max_caps_percent, value: 10) } + let!(:validate_title_max_marks_together) { create(:awesome_config, organization: organization, var: :validate_title_max_marks_together, value: 10) } + let!(:validate_title_start_with_caps) { create(:awesome_config, organization: organization, var: :validate_title_start_with_caps, value: true) } + let!(:validate_body_min_length) { create(:awesome_config, organization: organization, var: :validate_body_min_length, value: 10) } + let!(:validate_body_max_caps_percent) { create(:awesome_config, organization: organization, var: :validate_body_max_caps_percent, value: 10) } + let!(:validate_body_max_marks_together) { create(:awesome_config, organization: organization, var: :validate_body_max_marks_together, value: 10) } + let!(:validate_body_start_with_caps) { create(:awesome_config, organization: organization, var: :validate_body_start_with_caps, value: true) } + let(:styles) { "body {background: red;}" } let(:intergram) do { chat_id: "some-id" } diff --git a/spec/controllers/admin/config_controller_spec.rb b/spec/controllers/admin/config_controller_spec.rb index 918e3c7af..5f0edc4f4 100644 --- a/spec/controllers/admin/config_controller_spec.rb +++ b/spec/controllers/admin/config_controller_spec.rb @@ -62,7 +62,17 @@ module Admin end context "and proposals is disabled" do - let(:disabled) { editors + [:allow_images_in_proposals] } + let(:disabled) do + editors + [:allow_images_in_proposals, + :validate_title_min_length, + :validate_title_max_caps_percent, + :validate_title_max_marks_together, + :validate_title_start_with_caps, + :validate_body_min_length, + :validate_body_max_caps_percent, + :validate_body_max_marks_together, + :validate_body_start_with_caps] + end it "returns surveys" do expect(controller.helpers.config_var).to eq(:surveys) diff --git a/spec/forms/admin/config_form_spec.rb b/spec/forms/admin/config_form_spec.rb index 45be4fde6..06aea9a42 100644 --- a/spec/forms/admin/config_form_spec.rb +++ b/spec/forms/admin/config_form_spec.rb @@ -29,11 +29,20 @@ module Admin let(:valid_fields) { '[{"foo":"bar"}]' } let(:invalid_fields) { '[{"foo":"bar"}]{"baz":"zet"}' } + let(:validate_title_min_length) { 15 } + let(:validate_title_max_caps_percent) { 25 } + let(:validate_title_max_marks_together) { 2 } + let(:validate_title_start_with_caps) { true } + let(:validate_body_min_length) { 15 } + let(:validate_body_max_caps_percent) { 25 } + let(:validate_body_max_marks_together) { 2 } + let(:validate_body_start_with_caps) { true } + context "when everything is OK" do it { is_expected.to be_valid } end - context "when custom styles" do + describe "custom styles" do let(:attributes) do { scoped_styles: custom_styles @@ -49,11 +58,11 @@ module Admin } end - it { is_expected.not_to be_valid } + it { is_expected.to be_invalid } end end - context "when proposal custom fields" do + describe "proposal custom fields" do let(:attributes) do { proposal_custom_fields: custom_fields @@ -69,7 +78,7 @@ module Admin } end - it { is_expected.not_to be_valid } + it { is_expected.to be_invalid } end context "and sending labels with html" do @@ -81,6 +90,125 @@ module Admin end end end + + describe "validators" do + let(:attributes) do + { + validate_title_min_length: validate_title_min_length, + validate_title_max_caps_percent: validate_title_max_caps_percent, + validate_title_max_marks_together: validate_title_max_marks_together, + validate_title_start_with_caps: validate_title_start_with_caps, + validate_body_min_length: validate_body_min_length, + validate_body_max_caps_percent: validate_body_max_caps_percent, + validate_body_max_marks_together: validate_body_max_marks_together, + validate_body_start_with_caps: validate_body_start_with_caps + } + end + + it { is_expected.to be_valid } + + context "and title start with caps is false" do + let(:validate_title_start_with_caps) { false } + + it { is_expected.to be_valid } + end + + context "and title min length is empty" do + let(:validate_title_min_length) { nil } + + it { is_expected.to be_invalid } + end + + context "and title min length is zero" do + let(:validate_title_min_length) { 0 } + + it { is_expected.to be_invalid } + end + + context "and title min length greater than 100" do + let(:validate_title_min_length) { 101 } + + it { is_expected.to be_invalid } + end + + context "and body min length is empty" do + let(:validate_body_min_length) { nil } + + it { is_expected.to be_invalid } + end + + context "and body min length is zero" do + let(:validate_body_min_length) { 0 } + + it { is_expected.to be_valid } + end + + context "and title max caps percent empty" do + let(:validate_title_max_caps_percent) { nil } + + it { is_expected.to be_invalid } + end + + context "and title max caps percent is zero" do + let(:validate_title_max_caps_percent) { 0 } + + it { is_expected.to be_valid } + end + + context "and title max caps percent is bigger than 100" do + let(:validate_title_max_caps_percent) { 101 } + + it { is_expected.to be_invalid } + end + + context "and body start with caps is false" do + let(:validate_body_start_with_caps) { false } + + it { is_expected.to be_valid } + end + + context "and body max caps percent empty" do + let(:validate_body_max_caps_percent) { nil } + + it { is_expected.to be_invalid } + end + + context "and body max caps percent is zero" do + let(:validate_body_max_caps_percent) { 0 } + + it { is_expected.to be_valid } + end + + context "and body max caps percent is bigger than 100" do + let(:validate_body_max_caps_percent) { 101 } + + it { is_expected.to be_invalid } + end + + context "and title max marks together is empty" do + let(:validate_title_max_marks_together) { nil } + + it { is_expected.to be_invalid } + end + + context "and title max marks together is zero" do + let(:validate_title_max_marks_together) { 0 } + + it { is_expected.to be_invalid } + end + + context "and body max marks together is empty" do + let(:validate_body_max_marks_together) { nil } + + it { is_expected.to be_invalid } + end + + context "and body max marks together is zero" do + let(:validate_body_max_marks_together) { 0 } + + it { is_expected.to be_invalid } + end + end end end end diff --git a/spec/forms/proposal_wizard_create_step_form_spec.rb b/spec/forms/proposal_wizard_create_step_form_spec.rb index 2cef5543c..a5b6020b4 100644 --- a/spec/forms/proposal_wizard_create_step_form_spec.rb +++ b/spec/forms/proposal_wizard_create_step_form_spec.rb @@ -106,5 +106,162 @@ module Decidim::Proposals it { is_expected.to be_valid } end end + + shared_examples "starts with caps" do |prop| + let!(:config) { create :awesome_config, organization: organization, var: "validate_#{prop}_start_with_caps", value: enabled } + let!(:constraint) { create(:config_constraint, awesome_config: config, settings: { "participatory_space_manifest" => "participatory_processes", "participatory_space_slug" => slug }) } + + let(:enabled) { false } + let(prop.to_sym) { "í don't start with caps" } + + it { is_expected.to be_valid } + + context "when scoped under different context" do + let(:slug) { "another-slug" } + + it { is_expected.to be_invalid } + + context "when starts with caps" do + let(prop.to_sym) { "Í start with caps" } + + it { is_expected.to be_valid } + end + end + + context "when enabled" do + let(:enabled) { true } + + it { is_expected.to be_invalid } + + context "when starts with caps" do + let(prop.to_sym) { "Í start with caps" } + + it { is_expected.to be_valid } + end + end + end + + shared_examples "minimum length" do |prop| + let!(:config) { create :awesome_config, organization: organization, var: "validate_#{prop}_min_length", value: min_length } + let!(:constraint) { create(:config_constraint, awesome_config: config, settings: { "participatory_space_manifest" => "participatory_processes", "participatory_space_slug" => slug }) } + + let(:min_length) { 10 } + let(prop.to_sym) { "I am 10 yo" } + + it { is_expected.to be_valid } + + context "when scoped under different context" do + let(:slug) { "another-slug" } + + it { is_expected.to be_invalid } + + context "when has more than 15 chars" do + let(prop.to_sym) { "I am 17 years old" } + + it { is_expected.to be_valid } + end + end + + context "when less than allowed" do + let(:min_length) { 11 } + + it { is_expected.to be_invalid } + end + + # rubocop:disable RSpec/EmptyExampleGroup + context "when min_length is zero" do + let(:min_length) { 0 } + let(prop.to_sym) { "" } + + if prop == :body + it { is_expected.to be_valid } + else + it { is_expected.to be_invalid } + end + end + # rubocop:enable RSpec/EmptyExampleGroup + end + + shared_examples "max caps percent" do |prop| + let!(:config) { create :awesome_config, organization: organization, var: "validate_#{prop}_max_caps_percent", value: percent } + let!(:constraint) { create(:config_constraint, awesome_config: config, settings: { "participatory_space_manifest" => "participatory_processes", "participatory_space_slug" => slug }) } + + let(:percent) { 90 } + let(prop.to_sym) { "Í ÁM A SÈMI-CÁPS text" } + + it { is_expected.to be_valid } + + shared_examples "invalid percentage" do |per| + it "error message returns percentage" do + expect(form).to be_invalid + expect(form.errors.messages.values.flatten.first).to include("over #{per}% of the text") + end + end + + context "when scoped under different context" do + let(:slug) { "another-slug" } + + it_behaves_like "invalid percentage", 25 + + context "when has less than 25% caps" do + let(prop.to_sym) { "Í only have some CÁPS" } + + it { is_expected.to be_valid } + end + end + + context "when less than allowed" do + let(:percent) { 11 } + + it_behaves_like "invalid percentage", 11 + end + end + + shared_examples "max marks together" do |prop| + let!(:config) { create :awesome_config, organization: organization, var: "validate_#{prop}_max_marks_together", value: max_marks } + let!(:constraint) { create(:config_constraint, awesome_config: config, settings: { "participatory_space_manifest" => "participatory_processes", "participatory_space_slug" => slug }) } + + let(:max_marks) { 5 } + let(prop.to_sym) { "Am I a little bit noisy??!!!" } + + it { is_expected.to be_valid } + + context "when scoped under different context" do + let(:slug) { "another-slug" } + + it { is_expected.to be_invalid } + + context "when has only 1 mark" do + let(prop.to_sym) { "I am not noisy!" } + + it { is_expected.to be_valid } + end + + context "when has 2 marks" do + let(prop.to_sym) { "I am not noisy!?" } + + it { is_expected.to be_invalid } + end + end + + context "when less than allowed" do + let(:max_marks) { 4 } + + it { is_expected.to be_invalid } + end + end + + describe "etiquette validations" do + let(:body) { "A body longer than the permitted" } + + it_behaves_like "minimum length", :title + it_behaves_like "minimum length", :body + it_behaves_like "starts with caps", :title + it_behaves_like "starts with caps", :body + it_behaves_like "max caps percent", :title + it_behaves_like "max caps percent", :body + it_behaves_like "max marks together", :title + it_behaves_like "max marks together", :body + end end end diff --git a/spec/lib/system_checker_spec.rb b/spec/lib/system_checker_spec.rb index c5c332195..4f767799d 100644 --- a/spec/lib/system_checker_spec.rb +++ b/spec/lib/system_checker_spec.rb @@ -17,7 +17,7 @@ module Decidim::DecidimAwesome end it "has 5 modified files in core" do - expect(subject.overrides["decidim-core"].files.length).to eq(5) + expect(subject.overrides["decidim-core"].files.length).to eq(6) end it "has 5 modified files in proposals" do diff --git a/spec/system/admin/admin_spec.rb b/spec/system/admin/admin_spec.rb index 9e2e40206..2781ea6cd 100644 --- a/spec/system/admin/admin_spec.rb +++ b/spec/system/admin/admin_spec.rb @@ -105,6 +105,8 @@ it "renders the page" do expect(page).to have_content(/Tweaks for proposals/i) expect(page).to have_content("\"Rich text editor for participants\" is enabled") + expect(page).to have_content("User input validations for the \"title\" field") + expect(page).to have_content("User input validations for the \"body\" field") end context "and rich text editor for participants is disabled" do @@ -115,10 +117,36 @@ expect(page).not_to have_content("\"Rich text editor for participants\" is enabled") end end + + context "when all title validators are disabled" do + let(:disabled_features) { [:validate_title_min_length, :validate_title_max_caps_percent, :validate_title_max_marks_together, :validate_title_start_with_caps] } + + it "does not show title options" do + expect(page).not_to have_content("User input validations for the \"title\" field") + expect(page).to have_content("User input validations for the \"body\" field") + end + end + + context "when all body validators are disabled" do + let(:disabled_features) { [:validate_body_min_length, :validate_body_max_caps_percent, :validate_body_max_marks_together, :validate_body_start_with_caps] } + + it "does not show body options" do + expect(page).to have_content("User input validations for the \"title\" field") + expect(page).not_to have_content("User input validations for the \"body\" field") + end + end + end + + context "when some proposals hacks are disabled" do + [:allow_images_in_proposals, :validate_title_min_length, :validate_title_max_caps_percent, :validate_title_max_marks_together, :validate_title_start_with_caps, :validate_body_min_length, :validate_body_max_caps_percent, :validate_body_max_marks_together, :validate_body_start_with_caps].each do |var| + let(:disabled_features) { [var] } + + it_behaves_like "has menu link", "proposals" + end end - context "when proposal hacks are disabled" do - let(:disabled_features) { [:allow_images_in_proposals] } + context "when all proposals hacks are disabled" do + let(:disabled_features) { [:allow_images_in_proposals, :validate_title_min_length, :validate_title_max_caps_percent, :validate_title_max_marks_together, :validate_title_start_with_caps, :validate_body_min_length, :validate_body_max_caps_percent, :validate_body_max_marks_together, :validate_body_start_with_caps] } it_behaves_like "do not have menu link", "proposals" end