decidim-dev 0.27.4 → 0.28.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/app/commands/decidim/{dummy_resources → dev}/create_dummy_resource.rb +1 -2
  3. data/app/controllers/decidim/{dummy_resources → dev}/dummy_resources_controller.rb +2 -2
  4. data/app/events/decidim/dev/dummy_resource_event.rb +10 -0
  5. data/app/forms/decidim/{dummy_resources → dev}/dummy_resource_form.rb +1 -1
  6. data/app/jobs/decidim/dev/hide_all_created_by_author_job.rb +13 -0
  7. data/app/mailers/decidim/{dummy_resources → dev}/dummy_resource_mailer.rb +1 -1
  8. data/app/models/decidim/dev/application_record.rb +9 -0
  9. data/app/models/decidim/dev/coauthorable_dummy_resource.rb +10 -0
  10. data/app/models/decidim/dev/dummy_resource.rb +93 -0
  11. data/app/models/decidim/dev/nested_dummy_resource.rb +10 -0
  12. data/app/packs/src/decidim/dev/accessibility.js +3 -3
  13. data/app/packs/src/decidim/dev/test/custom_map_factory.js +1 -1
  14. data/app/packs/stylesheets/decidim/dev/_accessibility.scss +24 -24
  15. data/app/packs/stylesheets/decidim/dev/_map.scss +10 -0
  16. data/app/packs/stylesheets/decidim/dev.scss +1 -0
  17. data/app/presenters/decidim/dev/official_author_presenter.rb +33 -0
  18. data/app/serializers/decidim/dev/dummy_serializer.rb +21 -0
  19. data/app/views/decidim/dev/dummy_resources/show.html.erb +25 -0
  20. data/config/environment.rb +3 -0
  21. data/config/locales/ar.yml +0 -1
  22. data/config/locales/bg.yml +0 -1
  23. data/config/locales/cs.yml +4 -4
  24. data/config/locales/de.yml +2 -2
  25. data/config/locales/el.yml +0 -1
  26. data/config/locales/en.yml +1 -1
  27. data/config/locales/es-MX.yml +1 -1
  28. data/config/locales/es-PY.yml +1 -1
  29. data/config/locales/eu.yml +14 -8
  30. data/config/locales/gl.yml +0 -1
  31. data/config/locales/hu.yml +0 -1
  32. data/config/locales/id-ID.yml +0 -1
  33. data/config/locales/it.yml +0 -1
  34. data/config/locales/lv.yml +0 -1
  35. data/config/locales/nl.yml +0 -1
  36. data/config/locales/no.yml +0 -1
  37. data/config/locales/pl.yml +0 -1
  38. data/config/locales/pt-BR.yml +0 -1
  39. data/config/locales/pt.yml +0 -1
  40. data/config/locales/ru.yml +0 -1
  41. data/config/locales/sk.yml +0 -1
  42. data/config/locales/sq-AL.yml +1 -0
  43. data/config/locales/sv.yml +1 -1
  44. data/config/locales/th-TH.yml +1 -0
  45. data/config/locales/tr-TR.yml +0 -1
  46. data/config/locales/zh-CN.yml +0 -1
  47. data/config/rubocop/disabled.yml +11 -0
  48. data/config/rubocop/faker.yml +480 -0
  49. data/config/rubocop/rails.yml +105 -0
  50. data/config/rubocop/rspec.yml +69 -0
  51. data/config/rubocop/ruby.yml +1207 -0
  52. data/lib/decidim/dev/admin.rb +8 -0
  53. data/lib/decidim/dev/admin_engine.rb +43 -0
  54. data/lib/decidim/dev/assets/import_participatory_space_private_users.csv +2 -2
  55. data/lib/decidim/dev/assets/import_participatory_space_private_users_invalid_col_sep.csv +2 -0
  56. data/lib/decidim/dev/assets/import_participatory_space_private_users_nok.csv +2 -2
  57. data/lib/decidim/dev/assets/import_participatory_space_private_users_with_bom.csv +1 -1
  58. data/lib/decidim/dev/assets/iso-8859-15.md +1 -1
  59. data/lib/decidim/dev/assets/participatory_text.md +4 -2
  60. data/lib/decidim/dev/assets/verify_user_groups.csv +22 -22
  61. data/lib/decidim/dev/component.rb +94 -0
  62. data/lib/decidim/dev/engine.rb +21 -3
  63. data/lib/decidim/dev/test/base_spec_helper.rb +1 -0
  64. data/lib/decidim/dev/test/factories.rb +50 -0
  65. data/lib/decidim/dev/test/form_to_param_shared_examples.rb +1 -1
  66. data/lib/decidim/dev/test/promoted_participatory_processes_shared_examples.rb +9 -9
  67. data/lib/decidim/dev/test/rspec_support/accessibility_examples.rb +119 -1
  68. data/lib/decidim/dev/test/rspec_support/attachment_helpers.rb +2 -2
  69. data/lib/decidim/dev/test/rspec_support/bullet.rb +32 -0
  70. data/lib/decidim/dev/test/rspec_support/capybara.rb +26 -21
  71. data/lib/decidim/dev/test/rspec_support/cell_matchers.rb +1 -1
  72. data/lib/decidim/dev/test/rspec_support/component.rb +7 -317
  73. data/lib/decidim/dev/test/rspec_support/component_context.rb +10 -10
  74. data/lib/decidim/dev/test/rspec_support/confirmation_helpers.rb +18 -14
  75. data/lib/decidim/dev/test/rspec_support/data_consent.rb +2 -2
  76. data/lib/decidim/dev/test/rspec_support/dynamic_attach.rb +19 -4
  77. data/lib/decidim/dev/test/rspec_support/editor_context.rb +35 -0
  78. data/lib/decidim/dev/test/rspec_support/engine_examples.rb +15 -0
  79. data/lib/decidim/dev/test/rspec_support/filters.rb +11 -0
  80. data/lib/decidim/dev/test/rspec_support/forms_validations.rb +20 -0
  81. data/lib/decidim/dev/test/rspec_support/geocoder.rb +7 -7
  82. data/lib/decidim/dev/test/rspec_support/helpers.rb +187 -34
  83. data/lib/decidim/dev/test/rspec_support/imports_controller_shared_examples.rb +13 -13
  84. data/lib/decidim/dev/test/rspec_support/tom_select.rb +26 -0
  85. data/lib/decidim/dev/test/rspec_support/translation_helpers.rb +8 -8
  86. data/lib/decidim/dev/test/rspec_support/warden.rb +1 -1
  87. data/lib/decidim/dev/test/rspec_support/webpacker.rb +10 -0
  88. data/lib/decidim/dev/test/spec_helper.rb +15 -4
  89. data/lib/decidim/dev/test/w3c_rspec_validators_overrides.rb +1 -5
  90. data/lib/decidim/dev/version.rb +1 -1
  91. data/lib/decidim/dev.rb +22 -0
  92. data/lib/decidim-dev.rb +1 -1
  93. data/lib/tasks/lighthouse_report.rake +29 -7
  94. data/rubocop-decidim.yml +13 -0
  95. metadata +125 -71
  96. data/app/views/decidim/dummy_resources/dummy_resources/show.html.erb +0 -15
  97. data/lib/decidim/dev/test/rspec_support/capybara_data_picker.rb +0 -36
  98. data/lib/decidim/dev/test/rspec_support/capybara_scopes_picker.rb +0 -92
  99. data/lib/decidim/dev/test/rspec_support/summary_notification.rb +0 -51
  100. data/lib/rubocop/cop/decidim/hash_shorthand_syntax_backports.rb +0 -175
  101. data/lib/rubocop/cop/decidim.rb +0 -9
  102. /data/app/views/decidim/{dummy_resources → dev}/dummy_resources/foo.html.erb +0 -0
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Dev
5
+ module Admin
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Dev
5
+ class AdminEngine < Rails::Engine
6
+ engine_name "dummy_admin"
7
+
8
+ routes do
9
+ resources :dummy_resources do
10
+ resources :nested_dummy_resources
11
+ end
12
+
13
+ root to: proc { [200, {}, ["DUMMY ADMIN ENGINE"]] }
14
+ end
15
+
16
+ initializer "dummy_admin.imports" do
17
+ class ::DummyCreator < Decidim::Admin::Import::Creator
18
+ def self.resource_klass
19
+ Decidim::Dev::DummyResource
20
+ end
21
+
22
+ def produce
23
+ resource
24
+ end
25
+
26
+ private
27
+
28
+ def resource
29
+ @resource ||= Decidim::Dev::DummyResource.new(
30
+ title: { en: "Dummy" },
31
+ author: context[:current_user],
32
+ component:
33
+ )
34
+ end
35
+
36
+ def component
37
+ context[:current_component]
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,2 +1,2 @@
1
- my_user@example.org,My User Name
2
- my_private_user@example.org,My Private User Name
1
+ john.doe@example.org;John Doe
2
+ jane.doe@example.org;Jane Doe
@@ -0,0 +1,2 @@
1
+ john.doe@example.org,John Doe
2
+ jane.doe@example.org,Jane Doe
@@ -1,2 +1,2 @@
1
- my_user@example.org,My User (Name)
2
- my_private_user@example.org,My Private User Name
1
+ john.doe@example.org;John Doe (Roe)
2
+ jane.doe@example.org;Jane Doe
@@ -1 +1 @@
1
- my_user@example.org,My User Name
1
+ john.doe@example.org;John Doe
@@ -1,4 +1,4 @@
1
1
  This file is saved in ISO-8859-1 encoding and contains the following non ASCII characters:
2
+
2
3
  - ca: can��, cami�, votar�, seg�ent
3
4
  - es: espa�a
4
-
@@ -1,3 +1,5 @@
1
+ <!-- markdown-lint-disable-file single-h1 -->
2
+
1
3
  # The great title for a new law
2
4
 
3
5
  ## A co-creation process to create creative creations
@@ -27,8 +29,8 @@ When an organization finishes a co-creation process, the accountability process
27
29
  blah, blah, blah...
28
30
 
29
31
  1. one
30
- 2. two
31
- 3. three
32
+ 1. two
33
+ 1. three
32
34
 
33
35
  ### Following up accounted results
34
36
 
@@ -1,22 +1,22 @@
1
- Email,,,,,,
2
- my_usergroup@example.org,,,,,,
3
- ,,,,,,
4
- ,,,,,,
5
- ,,,,,,
6
- ,,,,,,
7
- ,,,,,,
8
- ,,,,,,
9
- ,,,,,,
10
- ,,,,,,
11
- ,,,,,,
12
- ,,,,,,
13
- ,,,,,,
14
- ,,,,,,
15
- ,,,,,,
16
- ,,,,,,
17
- ,,,,,,
18
- ,,,,,,
19
- ,,,,,,
20
- ,,,,,,
21
- ,,,,,,
22
- ,,,,,,
1
+ Email;;;;;;
2
+ acme@example.org;;;;;;
3
+ ;;;;;;
4
+ ;;;;;;
5
+ ;;;;;;
6
+ ;;;;;;
7
+ ;;;;;;
8
+ ;;;;;;
9
+ ;;;;;;
10
+ ;;;;;;
11
+ ;;;;;;
12
+ ;;;;;;
13
+ ;;;;;;
14
+ ;;;;;;
15
+ ;;;;;;
16
+ ;;;;;;
17
+ ;;;;;;
18
+ ;;;;;;
19
+ ;;;;;;
20
+ ;;;;;;
21
+ ;;;;;;
22
+ ;;;;;;
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ Decidim.register_component(:dummy) do |component|
4
+ component.engine = Decidim::Dev::Engine
5
+ component.admin_engine = Decidim::Dev::AdminEngine
6
+ component.icon = "media/images/decidim_dev_dummy.svg"
7
+
8
+ component.actions = %w(foo bar)
9
+
10
+ component.newsletter_participant_entities = ["Decidim::Dev::DummyResource"]
11
+
12
+ component.settings(:global) do |settings|
13
+ settings.attribute :scopes_enabled, type: :boolean, default: false
14
+ settings.attribute :scope_id, type: :scope
15
+ settings.attribute :comments_enabled, type: :boolean, default: true
16
+ settings.attribute :comments_max_length, type: :integer, required: false
17
+ settings.attribute :resources_permissions_enabled, type: :boolean, default: true
18
+ settings.attribute :dummy_global_attribute1, type: :boolean
19
+ settings.attribute :dummy_global_attribute2, type: :boolean, readonly: ->(_context) { false }
20
+ settings.attribute :readonly_attribute, type: :boolean, default: true, readonly: ->(_context) { true }
21
+ settings.attribute :enable_pads_creation, type: :boolean, default: false
22
+ settings.attribute :amendments_enabled, type: :boolean, default: false
23
+ settings.attribute :dummy_global_translatable_text, type: :text, translated: true, editor: true, required: true
24
+ end
25
+
26
+ component.settings(:step) do |settings|
27
+ settings.attribute :comments_blocked, type: :boolean, default: false
28
+ settings.attribute :dummy_step_attribute1, type: :boolean
29
+ settings.attribute :dummy_step_attribute2, type: :boolean, readonly: ->(_context) { false }
30
+ settings.attribute :dummy_step_translatable_text, type: :text, translated: true, editor: true, required: true
31
+ settings.attribute :readonly_step_attribute, type: :boolean, default: true, readonly: ->(_context) { true }
32
+ settings.attribute :amendment_creation_enabled, type: :boolean, default: true
33
+ settings.attribute :amendment_reaction_enabled, type: :boolean, default: true
34
+ settings.attribute :amendment_promotion_enabled, type: :boolean, default: true
35
+ settings.attribute :amendments_visibility, type: :string, default: "all"
36
+ settings.attribute :endorsements_enabled, type: :boolean, default: false
37
+ settings.attribute :endorsements_blocked, type: :boolean, default: false
38
+ end
39
+
40
+ component.register_resource(:dummy_resource) do |resource|
41
+ resource.name = :dummy
42
+ resource.model_class_name = "Decidim::Dev::DummyResource"
43
+ resource.template = "decidim/dummy_resource/linked_dummys"
44
+ resource.actions = %w(foo)
45
+ resource.searchable = true
46
+ end
47
+
48
+ component.register_resource(:nested_dummy_resource) do |resource|
49
+ resource.name = :nested_dummy
50
+ resource.model_class_name = "Decidim::Dev::NestedDummyResource"
51
+ end
52
+
53
+ component.register_resource(:coauthorable_dummy_resource) do |resource|
54
+ resource.name = :coauthorable_dummy
55
+ resource.model_class_name = "Decidim::Dev::CoauthorableDummyResource"
56
+ resource.template = "decidim/coauthorabledummy_resource/linked_dummys"
57
+ resource.actions = %w(foo-coauthorable)
58
+ resource.searchable = false
59
+ end
60
+
61
+ component.register_stat :dummies_count_high, primary: true, priority: Decidim::StatsRegistry::HIGH_PRIORITY do |components, _start_at, _end_at|
62
+ components.count * 10
63
+ end
64
+
65
+ component.register_stat :dummies_count_medium, primary: true, priority: Decidim::StatsRegistry::MEDIUM_PRIORITY do |components, _start_at, _end_at|
66
+ components.count * 100
67
+ end
68
+
69
+ component.exports :dummies do |exports|
70
+ exports.collection do
71
+ [1, 2, 3]
72
+ end
73
+
74
+ exports.serializer Decidim::Dev::DummySerializer
75
+ end
76
+
77
+ component.imports :dummies do |imports|
78
+ imports.messages do |msg|
79
+ msg.set(:resource_name) { |count: 1| count == 1 ? "Dummy" : "Dummies" }
80
+ msg.set(:title) { "Import dummies" }
81
+ msg.set(:label) { "Import dummies from a file" }
82
+ end
83
+
84
+ imports.creator DummyCreator
85
+ imports.example do |import_component|
86
+ locales = import_component.organization.available_locales
87
+ translated = ->(name) { locales.map { |l| "#{name}/#{l}" } }
88
+ [
89
+ translated.call("title") + %w(body) + translated.call("translatable_text") + %w(address latitude longitude),
90
+ locales.map { "Title text" } + ["Body text"] + locales.map { "Translatable text" } + ["Fake street 1", 1.0, 1.0]
91
+ ]
92
+ end
93
+ end
94
+ end
@@ -7,15 +7,33 @@ module Decidim
7
7
  isolate_namespace Decidim::Dev
8
8
  engine_name "decidim_dev"
9
9
 
10
- initializer "decidim_dev.tools" do
11
- ActiveSupport.on_load :action_controller do
12
- ActionController::Base.include Decidim::Dev::NeedsDevelopmentTools
10
+ routes do
11
+ root to: proc { [200, {}, ["DUMMY ENGINE"]] }
12
+
13
+ resources :dummy_resources do
14
+ resources :nested_dummy_resources
15
+ get :foo, on: :member
13
16
  end
14
17
  end
15
18
 
19
+ initializer "decidim_dev.tools" do
20
+ # Disable if the boost performance mode is enabled
21
+ next if Rails.application.config.try(:boost_performance)
22
+
23
+ ActiveSupport.on_load(:action_controller) { include Decidim::Dev::NeedsDevelopmentTools } if Rails.env.development? || ENV.fetch("DECIDIM_DEV_ENGINE", nil)
24
+ end
25
+
16
26
  initializer "decidim_dev.webpacker.assets_path" do
17
27
  Decidim.register_assets_path File.expand_path("app/packs", root)
18
28
  end
29
+
30
+ initializer "decidim_dev.moderation_content" do
31
+ config.to_prepare do
32
+ ActiveSupport::Notifications.subscribe("decidim.admin.block_user:after") do |_event_name, data|
33
+ Decidim::Dev::HideAllCreatedByAuthorJob.perform_later(**data)
34
+ end
35
+ end
36
+ end
19
37
  end
20
38
  end
21
39
  end
@@ -10,6 +10,7 @@ engine_spec_dir = File.join(Dir.pwd, "spec")
10
10
  require "simplecov" if ENV["SIMPLECOV"]
11
11
 
12
12
  require "decidim/core"
13
+ require "decidim/dev/component"
13
14
  require "decidim/core/test"
14
15
  require "decidim/admin/test"
15
16
  require "decidim/api/test"
@@ -1,8 +1,58 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "decidim/components/namer"
4
+
3
5
  FactoryBot.define do
4
6
  factory :dummy_component, parent: :component do
5
7
  name { Decidim::Components::Namer.new(participatory_space.organization.available_locales, :surveys).i18n_name }
6
8
  manifest_name { :dummy }
7
9
  end
10
+
11
+ factory :dummy_resource, class: "Decidim::Dev::DummyResource" do
12
+ transient do
13
+ users { nil }
14
+ # user_groups correspondence to users is by sorting order
15
+ user_groups { [] }
16
+ end
17
+ title { Decidim::Faker::Localized.localized { generate(:name) } }
18
+ component { create(:component, manifest_name: "dummy") }
19
+ author { create(:user, :confirmed, organization: component.organization) }
20
+ scope { create(:scope, organization: component.organization) }
21
+
22
+ trait :published do
23
+ published_at { Time.current }
24
+ end
25
+
26
+ trait :with_endorsements do
27
+ after :create do |resource|
28
+ 5.times.collect do
29
+ create(:endorsement, resource:, author: build(:user, organization: resource.component.organization))
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ factory :nested_dummy_resource, class: "Decidim::Dev::NestedDummyResource" do
36
+ title { generate(:name) }
37
+ dummy_resource { create(:dummy_resource) }
38
+ end
39
+
40
+ factory :coauthorable_dummy_resource, class: "Decidim::Dev::CoauthorableDummyResource" do
41
+ title { generate(:name) }
42
+ component { create(:component, manifest_name: "dummy") }
43
+
44
+ transient do
45
+ authors_list { [create(:user, organization: component.organization)] }
46
+ end
47
+
48
+ after :build do |resource, evaluator|
49
+ evaluator.authors_list.each do |coauthor|
50
+ resource.coauthorships << if coauthor.is_a?(Decidim::UserGroup)
51
+ build(:coauthorship, author: coauthor.users.first, user_group: coauthor, coauthorable: resource, organization: evaluator.component.organization)
52
+ else
53
+ build(:coauthorship, author: coauthor, coauthorable: resource, organization: evaluator.component.organization)
54
+ end
55
+ end
56
+ end
57
+ end
8
58
  end
@@ -4,7 +4,7 @@ shared_examples "form to param" do |options|
4
4
  method_name = options[:method_name] || :to_param
5
5
 
6
6
  describe "##{method_name}" do
7
- subject { described_class.new(id: id) }
7
+ subject { described_class.new(id:) }
8
8
 
9
9
  context "with actual ID" do
10
10
  let(:id) { double }
@@ -9,14 +9,14 @@ shared_examples "with promoted participatory processes and groups" do
9
9
  it "includes promoted participatory processes and groups placing groups in first place" do
10
10
  create(
11
11
  :participatory_process_group,
12
- organization: organization
12
+ organization:
13
13
  )
14
14
 
15
15
  unpromoted_process = create(
16
16
  :participatory_process,
17
17
  :with_steps,
18
18
  :published,
19
- organization: organization
19
+ organization:
20
20
  )
21
21
  unpromoted_process.active_step.update!(end_date: Time.current.advance(days: 1))
22
22
 
@@ -25,14 +25,14 @@ shared_examples "with promoted participatory processes and groups" do
25
25
  :with_steps,
26
26
  :published,
27
27
  :promoted,
28
- organization: organization
28
+ organization:
29
29
  )
30
30
  promoted_process.active_step.update!(end_date: Time.current.advance(days: 2))
31
31
 
32
32
  promoted_group = create(
33
33
  :participatory_process_group,
34
34
  :promoted,
35
- organization: organization
35
+ organization:
36
36
  )
37
37
 
38
38
  _external_promoted_group = create(
@@ -51,14 +51,14 @@ shared_examples "with promoted participatory processes and groups" do
51
51
  :with_steps,
52
52
  :unpublished,
53
53
  :promoted,
54
- organization: organization
54
+ organization:
55
55
  )
56
56
 
57
57
  create(
58
58
  :participatory_process,
59
59
  :with_steps,
60
60
  :unpublished,
61
- organization: organization
61
+ organization:
62
62
  )
63
63
 
64
64
  last =
@@ -67,7 +67,7 @@ shared_examples "with promoted participatory processes and groups" do
67
67
  :with_steps,
68
68
  :published,
69
69
  :promoted,
70
- organization: organization
70
+ organization:
71
71
  )
72
72
 
73
73
  last.active_step.update!(end_date: nil)
@@ -78,7 +78,7 @@ shared_examples "with promoted participatory processes and groups" do
78
78
  :with_steps,
79
79
  :published,
80
80
  :promoted,
81
- organization: organization,
81
+ organization:,
82
82
  end_date: Time.current.advance(days: 10)
83
83
  )
84
84
 
@@ -90,7 +90,7 @@ shared_examples "with promoted participatory processes and groups" do
90
90
  :with_steps,
91
91
  :published,
92
92
  :promoted,
93
- organization: organization,
93
+ organization:,
94
94
  end_date: Time.current.advance(days: 8)
95
95
  )
96
96
 
@@ -1,5 +1,123 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ module AxeMatchers
4
+ def self.axe_version
5
+ @axe_version ||= begin
6
+ package = JSON.load_file(Rails.root.join("node_modules/axe-core/package.json"))
7
+ package["version"]
8
+ end
9
+ end
10
+
11
+ def self.axe_mainline_version
12
+ @axe_mainline_version ||= axe_version.split(".")[0..1].join(".")
13
+ end
14
+
15
+ class ResultFormatter
16
+ def initialize(result)
17
+ @result = result
18
+ @violations = result["violations"]
19
+ end
20
+
21
+ def format
22
+ <<~MESSAGE
23
+
24
+ Found #{violations.count} accessibility #{violations.count == 1 ? "violation" : "violations"}:
25
+
26
+ #{violation_messages.join("\n")}
27
+ MESSAGE
28
+ end
29
+
30
+ def violation_messages
31
+ violations.each_with_index.map do |violation, index|
32
+ nodes = violation["nodes"]
33
+ [
34
+ "#{index + 1}) #{violation["id"]}: #{violation["help"]} (#{violation["impact"]})",
35
+ indent_lines(violation["helpUrl"], 1),
36
+ indent_lines("The following #{nodes.length} #{nodes.length == 1 ? "node" : "nodes"} violate this rule:", 1),
37
+ "",
38
+ indent_lines(node_messages_for(nodes), 2),
39
+ ""
40
+ ]
41
+ end.flatten
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :result, :violations
47
+
48
+ def indent_lines(lines, indent_level = 1)
49
+ indent = " " * indent_level
50
+ Array(lines).flatten.map { |line| line.length.positive? ? "#{indent}#{line}" : "" }.join("\n")
51
+ end
52
+
53
+ def node_messages_for(nodes)
54
+ nodes.map do |node|
55
+ [
56
+ "Selector: #{Array(node["target"]).join(", ")}",
57
+ ("HTML: #{node["html"].gsub(/^\s*|\n*/, "")}" unless node["html"].nil?),
58
+ fix(node["all"], "Fix all of the following:"),
59
+ fix(node["none"], "Fix all of the following:"),
60
+ fix(node["any"], "Fix any of the following:")
61
+ ].compact.presence.tap { |messages| messages&.push("") }
62
+ end.compact
63
+ end
64
+
65
+ def fix(checks, message)
66
+ valid_checks = checks.compact
67
+ [
68
+ (message unless valid_checks.empty?),
69
+ *valid_checks.map { |check| "- #{check["message"]}" }
70
+ ].compact
71
+ end
72
+ end
73
+
74
+ class BeAxeClean
75
+ def matches?(page)
76
+ @results = execute_axe(page)
77
+ results["violations"].count.zero?
78
+ end
79
+
80
+ def failure_message
81
+ ResultFormatter.new(results).format
82
+ end
83
+
84
+ def failure_message_when_negated
85
+ "Expected to find accessibility violations. None were detected."
86
+ end
87
+
88
+ private
89
+
90
+ attr_reader :results
91
+
92
+ def execute_axe(page)
93
+ load_axe(page)
94
+
95
+ script = <<-JS
96
+ var callback = arguments[arguments.length - 1];
97
+ var context = document;
98
+ var options = {};
99
+ axe.run(context, options).then(res => JSON.parse(JSON.stringify(res))).then(callback);
100
+ JS
101
+ page = page.driver if page.respond_to?("driver")
102
+ page = page.browser if page.respond_to?("browser") && !page.browser.is_a?(::Symbol)
103
+ page.execute_async_script(script)
104
+ end
105
+
106
+ def load_axe(page)
107
+ jslib = Rails.root.join("node_modules/axe-core/axe.min.js")
108
+ page.execute_script jslib.read
109
+ end
110
+ end
111
+
112
+ def be_axe_clean
113
+ BeAxeClean.new
114
+ end
115
+ end
116
+
117
+ RSpec.configure do |config|
118
+ config.include AxeMatchers
119
+ end
120
+
3
121
  shared_examples_for "accessible page" do
4
122
  it "passes accessibility tests" do
5
123
  expect(page).to be_axe_clean
@@ -7,7 +125,7 @@ shared_examples_for "accessible page" do
7
125
 
8
126
  it "passes HTML validation" do
9
127
  # Capybara is stripping the doctype out of the HTML which is required for
10
- # the validation. If it doesn't exist, add it there.
128
+ # the validation. If it does not exist, add it there.
11
129
  html = page.source
12
130
  html = "<!DOCTYPE html>\n#{html}" unless html.strip.match?(/^<!DOCTYPE/i)
13
131
 
@@ -18,8 +18,8 @@ module AttachmentHelpers
18
18
 
19
19
  blob = ActiveStorage::Blob.create_and_upload!(
20
20
  io: File.open(file),
21
- filename: filename,
22
- content_type: content_type
21
+ filename:,
22
+ content_type:
23
23
  )
24
24
  return blob if options[:return_blob]
25
25
 
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bullet"
4
+
5
+ RSpec.configure do |config|
6
+ Decidim::ApplicationJob.include(Bullet::ActiveJob) if defined?(Bullet::ActiveJob)
7
+
8
+ Rails.application.config.after_initialize do
9
+ Bullet.enable = Decidim::Env.new("DECIDIM_BULLET_ENABLED", "true").present?
10
+ Bullet.rails_logger = true
11
+ Bullet.raise = true
12
+ Bullet.stacktrace_includes = %w(decidim-)
13
+
14
+ # Detect N+1 queries
15
+ Bullet.n_plus_one_query_enable = Decidim::Env.new("DECIDIM_BULLET_N_PLUS_ONE", "false").present?
16
+ # Detect eager-loaded associations which are not used
17
+ Bullet.unused_eager_loading_enable = Decidim::Env.new("DECIDIM_BULLET_UNUSED_EAGER", "false").present?
18
+ # Detect unnecessary COUNT queries which could be avoided with a counter_cache
19
+ Bullet.counter_cache_enable = Decidim::Env.new("DECIDIM_BULLET_COUNTER_CACHE", "true").present?
20
+ end
21
+
22
+ if Bullet.enable?
23
+ config.before(:each) do
24
+ Bullet.start_request
25
+ end
26
+
27
+ config.after(:each) do
28
+ Bullet.perform_out_of_channel_notifications if Bullet.notification?
29
+ Bullet.end_request
30
+ end
31
+ end
32
+ end