decidim-toggle 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. checksums.yaml +7 -0
  2. data/.erb-lint.yml +2134 -0
  3. data/.github/workflows/website.yml +57 -0
  4. data/.gitignore +13 -0
  5. data/.gitlab-ci.yml +165 -0
  6. data/.node-version +1 -0
  7. data/.rspec +1 -0
  8. data/.rubocop.yml +2 -0
  9. data/.ruby-version +1 -0
  10. data/.simplecov +18 -0
  11. data/CONTRIBUTING.md +17 -0
  12. data/Gemfile +33 -0
  13. data/Gemfile.lock +843 -0
  14. data/LICENSE.md +661 -0
  15. data/README.md +90 -0
  16. data/Rakefile +38 -0
  17. data/app/commands/decidim/toggle/update_authorizations_command.rb +31 -0
  18. data/app/commands/decidim/toggle/update_emails_command.rb +30 -0
  19. data/app/commands/decidim/toggle/update_file_upload_settings_command.rb +31 -0
  20. data/app/commands/decidim/toggle/update_locale_command.rb +47 -0
  21. data/app/commands/decidim/toggle/update_module_config_command.rb +31 -0
  22. data/app/commands/decidim/toggle/update_name_command.rb +31 -0
  23. data/app/commands/decidim/toggle/update_omniauth_command.rb +30 -0
  24. data/app/commands/decidim/toggle/update_security_command.rb +31 -0
  25. data/app/controllers/decidim_toggle/system/settings_tab_controller.rb +64 -0
  26. data/app/forms/decidim/toggle/update_authorizations_form.rb +54 -0
  27. data/app/forms/decidim/toggle/update_emails_form.rb +28 -0
  28. data/app/forms/decidim/toggle/update_file_upload_settings_form.rb +26 -0
  29. data/app/forms/decidim/toggle/update_locale_form.rb +116 -0
  30. data/app/forms/decidim/toggle/update_name_form.rb +63 -0
  31. data/app/forms/decidim/toggle/update_omniauth_form.rb +37 -0
  32. data/app/forms/decidim/toggle/update_security_form.rb +65 -0
  33. data/app/helpers/decidim/toggle/javascript_config_helper.rb +11 -0
  34. data/app/helpers/decidim/toggle/system_settings_tab_helper.rb +59 -0
  35. data/app/models/decidim/toggle/organization_module_config.rb +15 -0
  36. data/app/overrides/add_toggle_javascript_admin.rb +11 -0
  37. data/app/overrides/add_toggle_javascript_public.rb +11 -0
  38. data/app/packs/entrypoints/decidim_toggle.js +3 -0
  39. data/app/packs/src/decidim/toggle/organization_settings_tabs.js +114 -0
  40. data/app/packs/stylesheets/decidim/toggle/organization_settings.scss +160 -0
  41. data/app/views/decidim/system/organizations/edit.html.erb +20 -0
  42. data/app/views/decidim_toggle/system/organizations/_default_form_tab.html.erb +5 -0
  43. data/app/views/decidim_toggle/system/organizations/_encryption_not_configured_callout.html.erb +6 -0
  44. data/app/views/decidim_toggle/system/organizations/_settings_tab_active_tab_field.html.erb +1 -0
  45. data/app/views/decidim_toggle/system/organizations/_settings_tab_submit.html.erb +4 -0
  46. data/app/views/decidim_toggle/system/organizations/_settings_tabs.html.erb +31 -0
  47. data/app/views/decidim_toggle/system/organizations/tabs/_authorizations_tab.html.erb +9 -0
  48. data/app/views/decidim_toggle/system/organizations/tabs/_emails_tab.html.erb +5 -0
  49. data/app/views/decidim_toggle/system/organizations/tabs/_file_upload_tab.html.erb +5 -0
  50. data/app/views/decidim_toggle/system/organizations/tabs/_language_tab.html.erb +35 -0
  51. data/app/views/decidim_toggle/system/organizations/tabs/_omniauth_tab.html.erb +5 -0
  52. data/app/views/decidim_toggle/system/organizations/tabs/_security_tab.html.erb +20 -0
  53. data/app/views/layouts/decidim/toggle/_javascript_config.html.erb +3 -0
  54. data/bin/check +10 -0
  55. data/bin/i18n-tasks +27 -0
  56. data/bin/postversion +14 -0
  57. data/config/assets.rb +8 -0
  58. data/config/locales/decidim_toggle_en.yml +58 -0
  59. data/crowdin.yml +15 -0
  60. data/db/migrate/20260321120000_create_decidim_toggle_organization_module_configs.rb +20 -0
  61. data/decidim-toggle.gemspec +35 -0
  62. data/docker-compose.yml +41 -0
  63. data/lib/decidim/toggle/engine.rb +62 -0
  64. data/lib/decidim/toggle/expose_attributes_to_js.rb +26 -0
  65. data/lib/decidim/toggle/expose_attributes_to_js_validator.rb +32 -0
  66. data/lib/decidim/toggle/gem_registry.rb +15 -0
  67. data/lib/decidim/toggle/informative_callouts.rb +76 -0
  68. data/lib/decidim/toggle/javascript_config.rb +87 -0
  69. data/lib/decidim/toggle/module_config.rb +64 -0
  70. data/lib/decidim/toggle/module_config_form.rb +41 -0
  71. data/lib/decidim/toggle/module_config_i18n.rb +44 -0
  72. data/lib/decidim/toggle/module_configuration_presenter.rb +55 -0
  73. data/lib/decidim/toggle/organization_settings_tabs.rb +69 -0
  74. data/lib/decidim/toggle/settings_form_builder.rb +200 -0
  75. data/lib/decidim/toggle/settings_tab_item.rb +37 -0
  76. data/lib/decidim/toggle/settings_tab_registry.rb +109 -0
  77. data/lib/decidim/toggle/settings_tabs.rb +56 -0
  78. data/lib/decidim/toggle/tab_form.rb +20 -0
  79. data/lib/decidim/toggle/version.rb +14 -0
  80. data/lib/decidim/toggle.rb +36 -0
  81. data/lib/tasks/decidim/toggle/toggle_upgrade.rake +13 -0
  82. data/lib/tasks/decidim/toggle/toggle_webpacker.rake +60 -0
  83. data/log/.gitignore +2 -0
  84. data/package.json +18 -0
  85. data/prettier.config.js +15 -0
  86. data/spec/commands/decidim/toggle/update_authorizations_command_spec.rb +41 -0
  87. data/spec/commands/decidim/toggle/update_emails_command_spec.rb +84 -0
  88. data/spec/commands/decidim/toggle/update_file_upload_settings_command_spec.rb +28 -0
  89. data/spec/commands/decidim/toggle/update_locale_command_spec.rb +53 -0
  90. data/spec/commands/decidim/toggle/update_module_config_command_spec.rb +38 -0
  91. data/spec/commands/decidim/toggle/update_name_command_spec.rb +49 -0
  92. data/spec/commands/decidim/toggle/update_omniauth_command_spec.rb +80 -0
  93. data/spec/commands/decidim/toggle/update_security_command_spec.rb +25 -0
  94. data/spec/decidim/toggle/settings_tab_item_spec.rb +34 -0
  95. data/spec/decidim/toggle/settings_tab_registry_spec.rb +66 -0
  96. data/spec/decidim/toggle/settings_tabs_spec.rb +60 -0
  97. data/spec/forms/concerns/decidim/toggle/informative_callouts_spec.rb +48 -0
  98. data/spec/forms/decidim/toggle/update_authorizations_form_spec.rb +40 -0
  99. data/spec/forms/decidim/toggle/update_emails_form_spec.rb +35 -0
  100. data/spec/forms/decidim/toggle/update_file_upload_settings_form_spec.rb +20 -0
  101. data/spec/forms/decidim/toggle/update_locale_form_spec.rb +64 -0
  102. data/spec/forms/decidim/toggle/update_name_form_spec.rb +57 -0
  103. data/spec/forms/decidim/toggle/update_omniauth_form_spec.rb +56 -0
  104. data/spec/forms/decidim/toggle/update_security_form_spec.rb +32 -0
  105. data/spec/helpers/decidim/toggle/system_settings_tab_helper_spec.rb +99 -0
  106. data/spec/lib/decidim/toggle/expose_attributes_to_js_spec.rb +31 -0
  107. data/spec/lib/decidim/toggle/expose_attributes_to_js_validator_spec.rb +30 -0
  108. data/spec/lib/decidim/toggle/gem_registry_spec.rb +30 -0
  109. data/spec/lib/decidim/toggle/javascript_config_spec.rb +169 -0
  110. data/spec/lib/decidim/toggle/module_config_form_spec.rb +45 -0
  111. data/spec/lib/decidim/toggle/module_config_spec.rb +74 -0
  112. data/spec/lib/decidim/toggle/module_configuration_presenter_spec.rb +53 -0
  113. data/spec/lib/decidim/toggle/settings_form_builder_spec.rb +115 -0
  114. data/spec/requests/decidim_toggle/system/settings_tab_spec.rb +144 -0
  115. data/spec/spec_helper.rb +12 -0
  116. data/spec/support/decidim_factories.rb +3 -0
  117. data/spec/support/devise.rb +5 -0
  118. data/spec/system/decidim_toggle/javascript_config_spec.rb +56 -0
  119. data/website/.gitignore +4 -0
  120. data/website/docs/developer/_category_.json +8 -0
  121. data/website/docs/developer/code-of-conduct.md +99 -0
  122. data/website/docs/developer/contribute.md +50 -0
  123. data/website/docs/developer/deface-usage.md +37 -0
  124. data/website/docs/developer/documentation.md +58 -0
  125. data/website/docs/developer/error-handling.md +51 -0
  126. data/website/docs/developer/tab-registry.md +51 -0
  127. data/website/docs/developer/view-customization.md +49 -0
  128. data/website/docs/index.md +80 -0
  129. data/website/docs/integrate/_category_.json +8 -0
  130. data/website/docs/integrate/attributes.md +121 -0
  131. data/website/docs/integrate/customize-views.md +62 -0
  132. data/website/docs/integrate/index.md +38 -0
  133. data/website/docs/integrate/informative_callout.md +80 -0
  134. data/website/docs/integrate/javascript.md +84 -0
  135. data/website/docs/integrate/labels.md +91 -0
  136. data/website/docs/integrate/quickstart.md +77 -0
  137. data/website/docusaurus.config.ts +100 -0
  138. data/website/package.json +31 -0
  139. data/website/sidebars.ts +7 -0
  140. data/website/src/css/custom.css +37 -0
  141. data/website/static/img/logo.svg +1 -0
  142. data/website/static/img/schema_overview.png +0 -0
  143. data/website/static/img/screenshot_informative_callouts.png +0 -0
  144. data/website/static/img/screenshot_saved_flash.png +0 -0
  145. data/website/static/img/screenshots_locale_tab.png +0 -0
  146. data/website/static/img/screenshots_name_tab.png +0 -0
  147. data/website/static/img/screenshots_security_tab.png +0 -0
  148. data/website/tsconfig.json +6 -0
  149. data/website/yarn.lock +8336 -0
  150. data/yarn.lock +13 -0
  151. metadata +249 -0
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe SettingsTabRegistry do
8
+ after do
9
+ described_class.destroy(:test_registry)
10
+ end
11
+
12
+ describe ".register" do
13
+ it "creates registry and stores configuration blocks" do
14
+ described_class.register(:test_registry) { |_tabs| nil }
15
+ described_class.register(:test_registry) { |_tabs| nil }
16
+ reg = described_class.find(:test_registry)
17
+ expect(reg.configurations.length).to eq(2)
18
+ end
19
+ end
20
+
21
+ describe ".find / .create" do
22
+ it "returns nil when missing" do
23
+ expect(described_class.find(:missing)).to be_nil
24
+ end
25
+
26
+ it "create stores a new registry" do
27
+ r = described_class.create(:test_registry)
28
+ expect(described_class.find(:test_registry)).to eq(r)
29
+ end
30
+ end
31
+
32
+ describe "#register_form_tab and #form_tab" do
33
+ it "stores form/command mapping" do
34
+ reg = described_class.create(:test_registry)
35
+ reg.register_form_tab(:foo, String, Integer)
36
+ expect(reg.form_tab(:foo)).to eq(form: String, command: Integer)
37
+ end
38
+
39
+ it "stores module_name for #form_tab_for_module" do
40
+ reg = described_class.create(:test_registry)
41
+ reg.register_form_tab(:foo, String, Integer, module_name: :decidim_geo)
42
+ expect(reg.form_tab_for_module(:decidim_geo)).to eq(
43
+ form: String,
44
+ command: Integer,
45
+ tab_identifier: :foo
46
+ )
47
+ end
48
+
49
+ it "drops module mapping when the same tab is re-registered without module_name" do
50
+ reg = described_class.create(:test_registry)
51
+ reg.register_form_tab(:foo, String, Integer, module_name: :decidim_geo)
52
+ reg.register_form_tab(:foo, String, Integer)
53
+ expect(reg.form_tab_for_module(:decidim_geo)).to be_nil
54
+ end
55
+
56
+ it "raises when the same tab id is re-registered with a different form in test" do
57
+ reg = described_class.create(:test_registry)
58
+ reg.register_form_tab(:foo, String, Integer)
59
+ expect do
60
+ reg.register_form_tab(:foo, Array, Integer)
61
+ end.to raise_error(Decidim::Toggle::DuplicateTabRegistrationError, /:foo/)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe SettingsTabs do
8
+ after do
9
+ SettingsTabRegistry.destroy(:tabs_spec)
10
+ end
11
+
12
+ it "raises when registry is missing" do
13
+ expect do
14
+ described_class.new(:tabs_spec).build_for(Object.new)
15
+ end.to raise_error(RuntimeError, /not registered/)
16
+ end
17
+
18
+ it "builds items from registered blocks" do
19
+ SettingsTabRegistry.register(:tabs_spec) do |tabs|
20
+ tabs.add_tab :one, "One", form: String, command: Integer, position: 1, module_name: :demo_mod
21
+ end
22
+
23
+ ctx = Object.new
24
+ list = described_class.new(:tabs_spec)
25
+ list.build_for(ctx)
26
+ expect(list.items.size).to eq(1)
27
+ expect(list.items.first.identifier).to eq(:one)
28
+ expect(list.items.first.module_name).to eq(:demo_mod)
29
+ end
30
+
31
+ it "add_tab supports form_layout_partial" do
32
+ SettingsTabRegistry.register(:tabs_spec) do |tabs|
33
+ tabs.add_tab :x,
34
+ "X",
35
+ form: String,
36
+ command: Integer,
37
+ form_layout_partial: "decidim/foo/tab",
38
+ position: 2
39
+ end
40
+
41
+ list = described_class.new(:tabs_spec)
42
+ list.build_for(Object.new)
43
+ expect(list.items.first.form_layout_partial).to eq("decidim/foo/tab")
44
+ expect(list.items.first.form_tab?).to be(true)
45
+ end
46
+
47
+ it "remove_tab filters items" do
48
+ SettingsTabRegistry.register(:tabs_spec) do |tabs|
49
+ tabs.add_tab :a, "A", form: String, command: Integer
50
+ tabs.add_tab :b, "B", form: String, command: Integer
51
+ tabs.remove_tab(:a)
52
+ end
53
+
54
+ list = described_class.new(:tabs_spec)
55
+ list.build_for(Object.new)
56
+ expect(list.items.map(&:identifier)).to eq([:b])
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe InformativeCallouts do
8
+ let(:form_class) do
9
+ Class.new(Decidim::Form) do
10
+ include InformativeCallouts
11
+
12
+ mimic :organization
13
+
14
+ info :always_visible_message
15
+ warning :conditional_message, if_predicate: ->(form) { form.show_warning? }
16
+
17
+ def always_visible_message
18
+ "Always visible"
19
+ end
20
+
21
+ def conditional_message
22
+ "Conditional"
23
+ end
24
+
25
+ def show_warning?
26
+ @show_warning
27
+ end
28
+
29
+ attr_writer :show_warning
30
+ end
31
+ end
32
+
33
+ it "registers informative entries on the form class" do
34
+ expect(form_class.informative_callouts.map(&:type)).to eq([:info, :warning])
35
+ end
36
+
37
+ it "filters entries with visible_informative_callouts" do
38
+ form = form_class.from_params({})
39
+ form.show_warning = false
40
+
41
+ expect(form.visible_informative_callouts.map { |entry| entry.message_for(form) }).to eq(["Always visible"])
42
+
43
+ form.show_warning = true
44
+ expect(form.visible_informative_callouts.map { |entry| entry.message_for(form) }).to eq(["Always visible", "Conditional"])
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe UpdateAuthorizationsForm do
8
+ let(:organization) { create(:organization, available_authorizations: []) }
9
+
10
+ describe ".from_model" do
11
+ it "loads workflow names as strings" do
12
+ org = create(:organization, available_authorizations: %w(dummy_authorization_handler))
13
+
14
+ form = described_class.from_model(org)
15
+
16
+ expect(form.available_authorizations).to eq(%w(dummy_authorization_handler))
17
+ end
18
+ end
19
+
20
+ describe ".from_params" do
21
+ it "defaults missing available_authorizations to an empty array" do
22
+ form = described_class.from_params(organization: {}).with_context(current_organization: organization)
23
+
24
+ expect(form.available_authorizations).to eq([])
25
+ end
26
+ end
27
+
28
+ describe "validations" do
29
+ it "rejects authorization names that are not registered workflows" do
30
+ form = described_class.from_params(
31
+ organization: { available_authorizations: %w(unknown_handler) }
32
+ ).with_context(current_organization: organization)
33
+
34
+ expect(form).not_to be_valid
35
+ expect(form.errors[:available_authorizations]).to be_present
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe UpdateEmailsForm do
8
+ let(:organization) { create(:organization) }
9
+
10
+ it "exposes host and users_registration_mode from the organization" do
11
+ form = described_class.from_model(organization)
12
+
13
+ expect(form.host).to eq(organization.host)
14
+ expect(form.users_registration_mode).to eq(organization.users_registration_mode)
15
+ end
16
+
17
+ it "extracts host/users_registration_mode from params[:organization]" do
18
+ organization.update!(host: "current-host.example.org")
19
+
20
+ params_host = "params-host.example.org"
21
+ params = {
22
+ organization: {
23
+ host: params_host,
24
+ users_registration_mode: organization.users_registration_mode
25
+ }
26
+ }
27
+
28
+ form = described_class.from_params(params).with_context(current_organization: organization)
29
+
30
+ expect(form.host).to eq(params_host)
31
+ expect(form.users_registration_mode).to eq(organization.users_registration_mode)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe UpdateFileUploadSettingsForm do
8
+ let(:organization) { create(:organization) }
9
+
10
+ describe ".from_model" do
11
+ it "wraps the organization file_upload_settings for editing" do
12
+ form = described_class.from_model(organization)
13
+
14
+ expect(form.file_upload_settings).to be_a(Decidim::System::FileUploadSettingsForm)
15
+ expect(form.file_upload_settings.maximum_file_size).to be_present
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe UpdateLocaleForm do
8
+ let(:organization) { create(:organization) }
9
+
10
+ describe ".from_model" do
11
+ it "normalizes available_locales and default_locale to strings" do
12
+ org = Struct.new(:available_locales, :default_locale, :enable_machine_translations, :machine_translation_display_priority).new(
13
+ [:en, :ca, :es], :en, false, "original"
14
+ )
15
+
16
+ form = described_class.from_model(org)
17
+
18
+ expect(form.available_locales).to eq(%w(en ca es))
19
+ expect(form.default_locale).to eq("en")
20
+ end
21
+
22
+ it "loads locales from the Organization record when the source only has id (e.g. system UpdateOrganizationForm)" do
23
+ org = create(:organization, available_locales: %w(en ca), default_locale: "ca")
24
+ form_like = Struct.new(:id, :enable_machine_translations, :machine_translation_display_priority).new(
25
+ org.id, false, "original"
26
+ )
27
+
28
+ form = described_class.from_model(form_like)
29
+
30
+ expect(form.available_locales).to eq(%w(en ca))
31
+ expect(form.default_locale).to eq("ca")
32
+ end
33
+ end
34
+
35
+ describe ".from_params with hash available_locales" do
36
+ it "maps checked keys to array" do
37
+ form = described_class.from_params(
38
+ organization: { available_locales: { "en" => "1", "ca" => "1" }, default_locale: "en" }
39
+ ).with_context(current_organization: organization)
40
+
41
+ expect(form.available_locales).to include("en", "ca")
42
+ end
43
+ end
44
+
45
+ describe "validations" do
46
+ it "rejects default_locale not in available_locales" do
47
+ form = described_class.from_params(
48
+ organization: { available_locales: %w(en), default_locale: "ca" }
49
+ ).with_context(current_organization: organization)
50
+
51
+ expect(form).not_to be_valid
52
+ end
53
+
54
+ it "rejects available_locales outside I18n" do
55
+ form = described_class.from_params(
56
+ organization: { available_locales: %w(zz), default_locale: "zz" }
57
+ ).with_context(current_organization: organization)
58
+
59
+ expect(form).not_to be_valid
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe UpdateNameForm do
8
+ let(:organization) { create(:organization) }
9
+
10
+ describe ".from_params with name_* keys" do
11
+ it "maps name_en into nested name hash" do
12
+ form = described_class.from_params(
13
+ organization: { "name_en" => "Hello", :host => "x.lvh.me" }
14
+ ).with_context(current_organization: organization)
15
+
16
+ expect(form.name["en"]).to eq("Hello")
17
+ end
18
+ end
19
+
20
+ describe ".from_model" do
21
+ it "joins array secondary hosts with newlines" do
22
+ organization.update!(secondary_hosts: %w(a.example.org b.example.org))
23
+
24
+ form = described_class.from_model(organization)
25
+
26
+ expect(form.secondary_hosts).to eq("a.example.org\nb.example.org")
27
+ end
28
+
29
+ it "preserves string secondary hosts" do
30
+ allow(organization).to receive(:secondary_hosts).and_return("legacy.example.org")
31
+
32
+ form = described_class.from_model(organization)
33
+
34
+ expect(form.secondary_hosts).to eq("legacy.example.org")
35
+ end
36
+
37
+ it "defaults secondary hosts to an empty string for unexpected values" do
38
+ allow(organization).to receive(:secondary_hosts).and_return(nil)
39
+
40
+ form = described_class.from_model(organization)
41
+
42
+ expect(form.secondary_hosts).to eq("")
43
+ end
44
+ end
45
+
46
+ describe "#clean_secondary_hosts" do
47
+ it "splits lines and drops blanks" do
48
+ form = described_class.from_params(
49
+ organization: { host: organization.host, secondary_hosts: "a.com\n\nb.com" }
50
+ ).with_context(current_organization: organization)
51
+
52
+ expect(form.clean_secondary_hosts).to eq(%w(a.com b.com))
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe UpdateOmniauthForm do
8
+ let(:organization) { create(:organization) }
9
+
10
+ it "exposes host and users_registration_mode from the organization" do
11
+ form = described_class.from_model(organization)
12
+
13
+ expect(form.host).to eq(organization.host)
14
+ expect(form.users_registration_mode).to eq(organization.users_registration_mode)
15
+ end
16
+
17
+ it "builds omniauth encrypted settings from params[:organization]" do
18
+ provider = Decidim::OmniauthProvider.available.keys.first
19
+ omniauth_secrets = Rails.application.secrets.dig(:omniauth, provider) || {}
20
+
21
+ # Pick the first setting other than :enabled. For `developer` this is typically :icon.
22
+ setting_key = (omniauth_secrets.keys.map(&:to_sym) - [:enabled]).first
23
+ skip "No omniauth provider settings found in secrets" if provider.blank? || setting_key.blank?
24
+
25
+ params = {
26
+ organization: {
27
+ :host => organization.host,
28
+ :users_registration_mode => organization.users_registration_mode,
29
+ "omniauth_settings_#{provider}_enabled" => true,
30
+ "omniauth_settings_#{provider}_#{setting_key}" => "foo-#{setting_key}"
31
+ }
32
+ }
33
+
34
+ form = described_class.from_params(params).with_context(current_organization: organization)
35
+
36
+ expect(form.omniauth_settings).to include(
37
+ "omniauth_settings_#{provider}_enabled" => true,
38
+ "omniauth_settings_#{provider}_#{setting_key}" => "foo-#{setting_key}"
39
+ )
40
+ expect(form.encrypted_omniauth_settings).to include("omniauth_settings_#{provider}_#{setting_key}")
41
+ end
42
+
43
+ it "registers space page info callout only when decidim-space_page is in the bundle" do
44
+ form = described_class.from_params({})
45
+ entries = form.visible_informative_callouts
46
+
47
+ if Decidim::Toggle.gem_present?("decidim-space_page")
48
+ expect(entries.map(&:type)).to eq([:info])
49
+ expect(entries.first.message_for(form)).to include("decidim space page")
50
+ else
51
+ expect(entries).to be_empty
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe UpdateSecurityForm do
8
+ let(:organization) { create(:organization) }
9
+
10
+ describe "#content_security_policy" do
11
+ it "compacts blank directive values" do
12
+ form = described_class.from_model(organization)
13
+ form.send(:"default-src=", "'self'")
14
+ form.send(:"img-src=", nil)
15
+
16
+ csp = form.content_security_policy
17
+ expect(csp["default-src"]).to eq("'self'")
18
+ expect(csp).not_to have_key("img-src")
19
+ end
20
+ end
21
+
22
+ describe "validations" do
23
+ it "rejects invalid users_registration_mode" do
24
+ form = described_class.from_model(organization)
25
+ form.users_registration_mode = "invalid_mode"
26
+
27
+ expect(form).not_to be_valid
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe SystemSettingsTabHelper do
8
+ describe "#decidim_toggle_update_settings_tab_organization_path" do
9
+ let(:helper_host) do
10
+ Class.new do
11
+ include Decidim::Toggle::SystemSettingsTabHelper
12
+ end.new
13
+ end
14
+
15
+ it "prefixes engine path with /decidim_toggle once" do
16
+ org = create(:organization)
17
+ path = helper_host.decidim_toggle_update_settings_tab_organization_path(org, tab_id: "name")
18
+ expect(path).to start_with("/decidim_toggle")
19
+ expect(path).to include("settings_tab")
20
+ expect(path).not_to include("/decidim_toggle/decidim_toggle")
21
+ end
22
+ end
23
+
24
+ describe "#decidim_toggle_settings_tab_form" do
25
+ let(:helper_host) do
26
+ Class.new do
27
+ include Decidim::Toggle::SystemSettingsTabHelper
28
+ include ActionView::Helpers::FormHelper
29
+ include ActionView::Helpers::FormTagHelper
30
+ include ActionView::Helpers::OutputSafetyHelper
31
+ include ActionView::Context
32
+
33
+ def flash
34
+ @flash ||= {}
35
+ end
36
+ end.new
37
+ end
38
+
39
+ let(:tab) do
40
+ SettingsTabItem.new(
41
+ :security,
42
+ "Security",
43
+ form_class: UpdateSecurityForm,
44
+ command_class: UpdateSecurityCommand
45
+ )
46
+ end
47
+ let(:form) { UpdateSecurityForm.from_params(organization: { host: "example.org", users_registration_mode: "enabled" }) }
48
+ let(:organization) { instance_double(Decidim::Organization, id: 1) }
49
+
50
+ before do
51
+ allow(UpdateSecurityForm).to receive(:from_model).with(organization).and_return(form)
52
+ allow(helper_host).to receive(:render).and_return("".html_safe)
53
+ allow(helper_host).to receive(:decidim_toggle_update_settings_tab_organization_path)
54
+ .with(organization, tab_id: :security)
55
+ .and_return("/decidim_toggle/system/organizations/1/settings_tab/security")
56
+ end
57
+
58
+ it "renders form shell markup" do
59
+ html = helper_host.decidim_toggle_settings_tab_form(organization, tab) { |_tf| "body" }
60
+ expect(html).to include("settings_tab_form_security")
61
+ expect(html).to include('action="/decidim_toggle/system/organizations/1/settings_tab/security"')
62
+ end
63
+ end
64
+
65
+ describe "#encryption_configured?" do
66
+ let(:helper_host) do
67
+ Class.new do
68
+ include Decidim::Toggle::SystemSettingsTabHelper
69
+ end.new
70
+ end
71
+
72
+ around do |example|
73
+ Decidim::AttributeEncryptor.remove_instance_variable(:@cryptor) if Decidim::AttributeEncryptor.instance_variable_defined?(:@cryptor)
74
+ example.run
75
+ Decidim::AttributeEncryptor.remove_instance_variable(:@cryptor) if Decidim::AttributeEncryptor.instance_variable_defined?(:@cryptor)
76
+ end
77
+
78
+ it "returns true when encryption round-trips" do
79
+ allow(Rails.application).to receive(:secret_key_base).and_return(SecureRandom.hex(64))
80
+
81
+ expect(helper_host.encryption_configured?).to be(true)
82
+ end
83
+
84
+ it "returns false when secret_key_base is missing" do
85
+ allow(Rails.application).to receive(:secret_key_base).and_return(nil)
86
+
87
+ expect(helper_host.encryption_configured?).to be(false)
88
+ end
89
+
90
+ it "returns false when encryption fails" do
91
+ allow(Rails.application).to receive(:secret_key_base).and_return("too-short")
92
+ allow(Decidim::AttributeEncryptor).to receive(:encrypt).and_raise(ArgumentError)
93
+
94
+ expect(helper_host.encryption_configured?).to be(false)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe ExposeAttributesToJs do
8
+ let(:form_class) do
9
+ Class.new(Decidim::Form) do
10
+ include ExposeAttributesToJs
11
+
12
+ mimic :organization
13
+ attribute :enabled, :boolean
14
+ attribute :search_bar, :boolean
15
+ attribute :secret, :string
16
+
17
+ expose_to_javascript :enabled, :search_bar
18
+ end
19
+ end
20
+
21
+ it "registers attributes via expose_to_javascript" do
22
+ expect(form_class.javascript_exposed_attribute_names).to contain_exactly("enabled", "search_bar")
23
+ end
24
+
25
+ it "deduplicates exposed attribute names" do
26
+ form_class.expose_to_javascript(:enabled)
27
+ expect(form_class.javascript_exposed_attribute_names).to contain_exactly("enabled", "search_bar")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe ExposeAttributesToJsValidator do
8
+ it "warns when an exposed attribute is not declared on the form" do
9
+ form_class = Class.new(Decidim::Form) do
10
+ include ExposeAttributesToJs
11
+
12
+ mimic :organization
13
+ attribute :enabled, :boolean
14
+
15
+ expose_to_javascript :missing_flag
16
+ end
17
+
18
+ expect(Rails.logger).to receive(:warn).with(
19
+ /decidim-toggle.*missing_flag/
20
+ )
21
+
22
+ described_class.validate_form!(form_class)
23
+ end
24
+
25
+ it "validates every registered organization settings form without error" do
26
+ expect { described_class.validate! }.not_to raise_error
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ module Decidim
6
+ module Toggle
7
+ describe GemRegistry do
8
+ describe ".present?" do
9
+ it "returns true when the gem is in the bundle" do
10
+ expect(described_class.present?("decidim-core")).to be(true)
11
+ end
12
+
13
+ it "returns false when the gem is not in the bundle" do
14
+ expect(described_class.present?("decidim-space_page")).to be(false)
15
+ end
16
+
17
+ it "returns false for blank names" do
18
+ expect(described_class.present?("")).to be(false)
19
+ expect(described_class.present?(nil)).to be(false)
20
+ end
21
+ end
22
+ end
23
+
24
+ describe "Decidim::Toggle.gem_present?" do
25
+ it "returns true for gems in the bundle" do
26
+ expect(Decidim::Toggle.gem_present?("decidim-core")).to be(true)
27
+ end
28
+ end
29
+ end
30
+ end