
">
diff --git a/app/views/decidim/proposals/proposals/index.js.erb b/app/views/decidim/proposals/proposals/index.js.erb
new file mode 100644
index 0000000..7960009
--- /dev/null
+++ b/app/views/decidim/proposals/proposals/index.js.erb
@@ -0,0 +1,17 @@
+var $proposals = $('#proposals');
+var $orderFilterInput = $('.order_filter');
+
+$proposals.html('<%= j(render partial: "proposals").strip.html_safe %>');
+$orderFilterInput.val('<%= order %>');
+
+<% if Decidim::Map.available?(:geocoding, :dynamic) && component_settings.geocoding_enabled? %>
+var $map = $("#map");
+var controller = $map.data("map-controller");
+if (controller) {
+ var markerData = JSON.parse('<%= escape_javascript proposals_data_for_map(@proposals).to_json.html_safe %>');
+ controller.clearMarkers();
+ if (markerData.length > 0 ) {
+ controller.addMarkers(markerData);
+ }
+}
+<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index fd86324..52715c8 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -29,3 +29,8 @@ en:
proposals:
external_proposal:
view_from: View from platform %{platform}
+ layouts:
+ decidim:
+ shared:
+ layout_item:
+ view_from: view from plateform %{platform}
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index ab2a842..46377e4 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -29,3 +29,8 @@ fr:
proposals:
external_proposal:
view_from: Vue de la plateforme %{platform}
+ layouts:
+ decidim:
+ shared:
+ layout_item:
+ view_from: vue de la plateforme %{platform}
diff --git a/lib/decidim/dataspace/engine.rb b/lib/decidim/dataspace/engine.rb
index 221ad09..3777699 100644
--- a/lib/decidim/dataspace/engine.rb
+++ b/lib/decidim/dataspace/engine.rb
@@ -41,12 +41,13 @@ class Engine < ::Rails::Engine
initializer "dataspace-extends" do
config.after_initialize do
- require "extends/controllers/decidim/proposals/proposals_controller_extends"
+ require "extends/controllers/decidim/proposals/proposals_controller_dataspace_extends"
require "extends/models/decidim/comments/comment_extends"
require "extends/lib/decidim/core_extends"
require "extends/commands/decidim/system/create_organization_extends"
require "extends/commands/decidim/system/update_organization_extends"
- require "extends/forms/decidim/system/base_organization_form_extends"
+ require "extends/forms/decidim/system/register_organization_form_extends"
+ require "extends/forms/decidim/system/update_organization_form_extends"
end
end
diff --git a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb b/lib/extends/controllers/decidim/proposals/proposals_controller_dataspace_extends.rb
similarity index 98%
rename from lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb
rename to lib/extends/controllers/decidim/proposals/proposals_controller_dataspace_extends.rb
index c8bc70e..1fa2ce9 100644
--- a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb
+++ b/lib/extends/controllers/decidim/proposals/proposals_controller_dataspace_extends.rb
@@ -3,7 +3,7 @@
require "active_support/concern"
require "uri"
-module ProposalsControllerExtends
+module ProposalsControllerDataspaceExtends
extend ActiveSupport::Concern
included do
@@ -128,4 +128,4 @@ def create_pagination_object(total_count, current_page, per_page)
end
end
-Decidim::Proposals::ProposalsController.include(ProposalsControllerExtends)
+Decidim::Proposals::ProposalsController.include(ProposalsControllerDataspaceExtends)
diff --git a/lib/extends/forms/decidim/system/register_organization_form_extends.rb b/lib/extends/forms/decidim/system/register_organization_form_extends.rb
new file mode 100644
index 0000000..472475d
--- /dev/null
+++ b/lib/extends/forms/decidim/system/register_organization_form_extends.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module RegisterOrganizationFormExtends
+ extend ActiveSupport::Concern
+
+ included do
+ attribute :enable_dataspace, Decidim::AttributeObject::TypeMap::Boolean
+ end
+end
+
+Decidim::System::RegisterOrganizationForm.include(RegisterOrganizationFormExtends)
diff --git a/lib/extends/forms/decidim/system/base_organization_form_extends.rb b/lib/extends/forms/decidim/system/update_organization_form_extends.rb
similarity index 58%
rename from lib/extends/forms/decidim/system/base_organization_form_extends.rb
rename to lib/extends/forms/decidim/system/update_organization_form_extends.rb
index 85a451e..6b850a4 100644
--- a/lib/extends/forms/decidim/system/base_organization_form_extends.rb
+++ b/lib/extends/forms/decidim/system/update_organization_form_extends.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module BaseOrganizationFormExtends
+module UpdateOrganizationFormExtends
extend ActiveSupport::Concern
included do
@@ -8,4 +8,4 @@ module BaseOrganizationFormExtends
end
end
-Decidim::System::BaseOrganizationForm.include(BaseOrganizationFormExtends)
+Decidim::System::UpdateOrganizationForm.include(UpdateOrganizationFormExtends)
diff --git a/spec/forms/register_organization_form_spec.rb b/spec/forms/register_organization_form_spec.rb
new file mode 100644
index 0000000..acbc62b
--- /dev/null
+++ b/spec/forms/register_organization_form_spec.rb
@@ -0,0 +1,379 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim::System
+ describe RegisterOrganizationForm do
+ subject do
+ described_class.new(
+ name: "Gotham City",
+ host: "decide.example.org",
+ secondary_hosts: "foo.example.org\r\n\r\nbar.example.org",
+ reference_prefix: "JKR",
+ organization_admin_name: "Fiorello Henry La Guardia",
+ organization_admin_email: "f.laguardia@example.org",
+ available_locales: ["en"],
+ default_locale: "en",
+ users_registration_mode: "enabled",
+ force_users_to_authenticate_before_access_organization: "false",
+ enable_dataspace:,
+ **smtp_settings,
+ **omniauth_settings
+ )
+ end
+
+ let(:enable_dataspace) { false }
+ let(:omniauth_settings) do
+ {
+ "omniauth_settings_facebook_enabled" => true,
+ "omniauth_settings_facebook_app_id" => facebook_app_id,
+ "omniauth_settings_facebook_app_secret" => facebook_app_secret
+ }
+ end
+ let(:smtp_settings) do
+ {
+ "address" => "mail.example.org",
+ "port" => 25,
+ "user_name" => "f.laguardia",
+ "password" => password,
+ "from_email" => "decide@example.org",
+ "from_label" => from_label
+ }
+ end
+ let(:password) { "secret_password" }
+ let(:from_label) { "Decide Gotham" }
+ let(:facebook_app_id) { "plain-text-facebook-app-id" }
+ let(:facebook_app_secret) { "plain-text-facebook-app-secret" }
+
+ context "when everything is OK" do
+ it { is_expected.to be_valid }
+
+ describe "enable_dataspace" do
+ context "when enable_dataspace is true" do
+ let(:enable_dataspace) { true }
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ describe "omniauth_settings" do
+ it "contains attributes as plain text" do
+ expect(subject.omniauth_settings_facebook_enabled).to be(true)
+ expect(subject.omniauth_settings_facebook_app_id).to eq(facebook_app_id)
+ expect(subject.omniauth_settings_facebook_app_secret).to eq(facebook_app_secret)
+ end
+
+ context "when all values are blank" do
+ let(:omniauth_settings) do
+ {
+ "omniauth_settings_facebook_enabled" => nil,
+ "omniauth_settings_facebook_app_id" => nil,
+ "omniauth_settings_facebook_app_secret" => nil
+ }
+ end
+
+ it "returns nil" do
+ expect(subject.encrypted_omniauth_settings).to be_nil
+ end
+ end
+ end
+
+ describe "encrypted_omniauth_settings" do
+ it "encrypts sensible attributes" do
+ encrypted_settings = subject.encrypted_omniauth_settings
+
+ expect(encrypted_settings["omniauth_settings_facebook_enabled"]).to be(true)
+ expect(
+ Decidim::AttributeEncryptor.decrypt(encrypted_settings["omniauth_settings_facebook_app_id"])
+ ).to eq(facebook_app_id)
+ expect(
+ Decidim::AttributeEncryptor.decrypt(encrypted_settings["omniauth_settings_facebook_app_secret"])
+ ).to eq(facebook_app_secret)
+ end
+ end
+
+ describe "#set_from" do
+ it "concatenates from_label and from_email" do
+ from = subject.set_from
+
+ expect(from).to eq("Decide Gotham
")
+ end
+
+ context "when from_label is empty" do
+ let(:from_label) { "" }
+
+ it "returns the email" do
+ from = subject.set_from
+
+ expect(from).to eq("decide@example.org")
+ end
+ end
+ end
+
+ describe "smtp_settings" do
+ it "handles SMTP password properly" do
+ expect(subject.smtp_settings).to eq(smtp_settings.except("password"))
+ expect(Decidim::AttributeEncryptor.decrypt(subject.encrypted_smtp_settings[:encrypted_password])).to eq(password)
+ end
+
+ context "when all values are blank" do
+ let(:smtp_settings) do
+ {
+ "address" => "",
+ "port" => "",
+ "user_name" => "",
+ "password" => "",
+ "from_email" => "",
+ "from_label" => ""
+ }
+ end
+
+ it "returns nil" do
+ expect(subject.encrypted_smtp_settings).to be_nil
+ end
+ end
+ end
+ end
+
+ describe "validations" do
+ describe "organization_admin_email" do
+ context "when organization_admin_email is blank" do
+ before { subject.organization_admin_email = "" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:organization_admin_email]).to include("cannot be blank")
+ end
+ end
+
+ context "when organization_admin_email is nil" do
+ before { subject.organization_admin_email = nil }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe "organization_admin_name" do
+ context "when organization_admin_name is blank" do
+ before { subject.organization_admin_name = "" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:organization_admin_name]).to include("cannot be blank")
+ end
+ end
+
+ context "when organization_admin_name is nil" do
+ before { subject.organization_admin_name = nil }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe "name" do
+ context "when name is blank" do
+ before { subject.name = "" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:name]).to include("cannot be blank")
+ end
+ end
+
+ context "when name is nil" do
+ before { subject.name = nil }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe "reference_prefix" do
+ context "when reference_prefix is blank" do
+ before { subject.reference_prefix = "" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:reference_prefix]).to include("cannot be blank")
+ end
+ end
+
+ context "when reference_prefix is nil" do
+ before { subject.reference_prefix = nil }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe "available_locales" do
+ context "when available_locales is blank" do
+ before { subject.available_locales = [] }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:available_locales]).to include("cannot be blank")
+ end
+ end
+
+ context "when available_locales is nil" do
+ before { subject.available_locales = nil }
+
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ describe "default_locale" do
+ context "when default_locale is blank" do
+ before { subject.default_locale = "" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:default_locale]).to include("cannot be blank")
+ end
+ end
+
+ context "when default_locale is nil" do
+ before { subject.default_locale = nil }
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context "when default_locale is not included in available_locales" do
+ before do
+ subject.available_locales = %w(en es)
+ subject.default_locale = "fr"
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:default_locale]).to include("is not included in the list")
+ end
+ end
+
+ context "when default_locale is included in available_locales" do
+ before do
+ subject.available_locales = %w(en es fr)
+ subject.default_locale = "fr"
+ end
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ describe "organization uniqueness" do
+ let!(:existing_organization) do
+ create(
+ :organization,
+ name: { en: "Existing City", es: "Ciudad Existente" },
+ host: "existing.example.org"
+ )
+ end
+
+ context "when organization name already exists (case-insensitive)" do
+ before { subject.name = "EXISTING CITY" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:name]).to include("has already been taken")
+ end
+ end
+
+ context "when organization name already exists in different locale" do
+ before { subject.name = "Ciudad Existente" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:name]).to include("has already been taken")
+ end
+ end
+
+ context "when host already exists" do
+ before { subject.host = "existing.example.org" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:host]).to include("has already been taken")
+ end
+ end
+
+ context "when organization name is unique" do
+ before { subject.name = "Unique City" }
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when host is unique" do
+ before { subject.host = "unique.example.org" }
+
+ it { is_expected.to be_valid }
+ end
+ end
+ end
+
+ describe "#map_model" do
+ subject { described_class.from_model(organization) }
+
+ let(:organization) do
+ create(
+ :organization,
+ secondary_hosts: ["foobar.example.org", "foobaz.example.org"],
+ omniauth_settings: {
+ omniauth_settings_facebook_enabled: Decidim::AttributeEncryptor.encrypt(true),
+ omniauth_settings_facebook_app_id: Decidim::AttributeEncryptor.encrypt("foo")
+ },
+ file_upload_settings: {
+ allowed_file_extensions: {
+ default: %w(jpg jpeg),
+ admin: %w(jpg jpeg png),
+ image: %w(jpg jpeg png)
+ },
+ allowed_content_types: {
+ default: %w(image/*),
+ admin: %w(image/*)
+ },
+ maximum_file_size: {
+ default: 7.2,
+ avatar: 2.4
+ }
+ }
+ )
+ end
+
+ it "maps the organization attributes correctly" do
+ expect(subject.secondary_hosts).to eq(organization.secondary_hosts.join("\n"))
+ expect(subject.omniauth_settings).to eq(
+ {
+ "omniauth_settings_facebook_app_id" => "foo",
+ "omniauth_settings_facebook_enabled" => true
+ }
+ )
+ expect(subject.file_upload_settings.final).to eq(
+ {
+ allowed_content_types: { "admin" => %w(image/*), "default" => %w(image/*) },
+ allowed_file_extensions: { "admin" => %w(jpg jpeg png), "default" => %w(jpg jpeg), "image" => %w(jpg jpeg png) },
+ maximum_file_size: { "avatar" => 2.4, "default" => 7.2 }
+ }
+ )
+ end
+ end
+ end
+end
diff --git a/spec/forms/update_organization_form_spec.rb b/spec/forms/update_organization_form_spec.rb
new file mode 100644
index 0000000..e41c94a
--- /dev/null
+++ b/spec/forms/update_organization_form_spec.rb
@@ -0,0 +1,408 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim::System
+ describe UpdateOrganizationForm do
+ subject do
+ described_class.new(
+ name: { ca: "", en: "Gotham City", es: "" },
+ host: "decide.example.org",
+ secondary_hosts: "foo.example.org\r\n\r\nbar.example.org",
+ reference_prefix: "JKR",
+ organization_admin_name: "Fiorello Henry La Guardia",
+ organization_admin_email: "f.laguardia@example.org",
+ available_locales: ["en"],
+ default_locale: "en",
+ users_registration_mode: "enabled",
+ force_users_to_authenticate_before_access_organization: "false",
+ enable_dataspace:,
+ **smtp_settings,
+ **omniauth_settings
+ )
+ end
+
+ let(:enable_dataspace) { false }
+ let(:omniauth_settings) do
+ {
+ "omniauth_settings_facebook_enabled" => true,
+ "omniauth_settings_facebook_app_id" => facebook_app_id,
+ "omniauth_settings_facebook_app_secret" => facebook_app_secret
+ }
+ end
+ let(:smtp_settings) do
+ {
+ "address" => "mail.example.org",
+ "port" => 25,
+ "user_name" => "f.laguardia",
+ "password" => password,
+ "from_email" => "decide@example.org",
+ "from_label" => from_label
+ }
+ end
+ let(:password) { "secret_password" }
+ let(:from_label) { "Decide Gotham" }
+ let(:facebook_app_id) { "plain-text-facebook-app-id" }
+ let(:facebook_app_secret) { "plain-text-facebook-app-secret" }
+
+ context "when everything is OK" do
+ it { is_expected.to be_valid }
+
+ describe "enable_dataspace" do
+ context "when enable_dataspace is true" do
+ let(:enable_dataspace) { true }
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ describe "omniauth_settings" do
+ it "contains attributes as plain text" do
+ expect(subject.omniauth_settings_facebook_enabled).to be(true)
+ expect(subject.omniauth_settings_facebook_app_id).to eq(facebook_app_id)
+ expect(subject.omniauth_settings_facebook_app_secret).to eq(facebook_app_secret)
+ end
+
+ context "when all values are blank" do
+ let(:omniauth_settings) do
+ {
+ "omniauth_settings_facebook_enabled" => nil,
+ "omniauth_settings_facebook_app_id" => nil,
+ "omniauth_settings_facebook_app_secret" => nil
+ }
+ end
+
+ it "returns nil" do
+ expect(subject.encrypted_omniauth_settings).to be_nil
+ end
+ end
+ end
+
+ describe "encrypted_omniauth_settings" do
+ it "encrypts sensible attributes" do
+ encrypted_settings = subject.encrypted_omniauth_settings
+
+ expect(encrypted_settings["omniauth_settings_facebook_enabled"]).to be(true)
+ expect(
+ Decidim::AttributeEncryptor.decrypt(encrypted_settings["omniauth_settings_facebook_app_id"])
+ ).to eq(facebook_app_id)
+ expect(
+ Decidim::AttributeEncryptor.decrypt(encrypted_settings["omniauth_settings_facebook_app_secret"])
+ ).to eq(facebook_app_secret)
+ end
+ end
+
+ describe "#set_from" do
+ it "concatenates from_label and from_email" do
+ from = subject.set_from
+
+ expect(from).to eq("Decide Gotham ")
+ end
+
+ context "when from_label is empty" do
+ let(:from_label) { "" }
+
+ it "returns the email" do
+ from = subject.set_from
+
+ expect(from).to eq("decide@example.org")
+ end
+ end
+ end
+
+ describe "smtp_settings" do
+ it "handles SMTP password properly" do
+ expect(subject.smtp_settings).to eq(smtp_settings.except("password"))
+ expect(Decidim::AttributeEncryptor.decrypt(subject.encrypted_smtp_settings[:encrypted_password])).to eq(password)
+ end
+
+ context "when all values are blank" do
+ let(:smtp_settings) do
+ {
+ "address" => "",
+ "port" => "",
+ "user_name" => "",
+ "password" => "",
+ "from_email" => "",
+ "from_label" => ""
+ }
+ end
+
+ it "returns nil" do
+ expect(subject.encrypted_smtp_settings).to be_nil
+ end
+ end
+ end
+ end
+
+ describe "validations" do
+ describe "organization name presence" do
+ let(:organization) { create(:organization, default_locale: "en") }
+
+ before do
+ subject.id = organization.id
+ allow(subject).to receive(:current_organization).and_return(organization)
+ end
+
+ context "when name in default locale is present" do
+ before { subject.name = { en: "Gotham City" } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when name in default locale is blank" do
+ before { subject.name = { en: "" } }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error to the default locale name attribute" do
+ subject.valid?
+ expect(subject.errors[:name_en]).to include("cannot be blank")
+ end
+ end
+
+ context "when organization has different default locale" do
+ let(:organization) { create(:organization, default_locale: "es") }
+
+ before do
+ subject.default_locale = "es"
+ subject.name = { es: "" }
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error to the correct locale name attribute" do
+ subject.valid?
+ expect(subject.errors[:name_es]).to include("cannot be blank")
+ end
+ end
+
+ context "when current_organization is not set" do
+ before do
+ allow(subject).to receive(:current_organization).and_return(nil)
+ subject.send(:"name_#{Decidim.default_locale}=", "")
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "uses Decidim default locale" do
+ subject.valid?
+ expect(subject.errors[:"name_#{Decidim.default_locale}"]).to include("cannot be blank")
+ end
+ end
+ end
+
+ describe "organization uniqueness" do
+ let!(:existing_organization) do
+ create(
+ :organization,
+ name: { en: "Existing City", es: "Ciudad Existente" },
+ host: "existing.example.org"
+ )
+ end
+
+ context "when creating a new organization" do
+ context "when organization name already exists (case-insensitive)" do
+ before { subject.name_en = "EXISTING CITY" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error to the name attribute" do
+ subject.valid?
+ expect(subject.errors[:name_en]).to include("has already been taken")
+ end
+ end
+
+ context "when organization name already exists in different locale" do
+ before { subject.name_en = "Ciudad Existente" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:name_en]).to include("has already been taken")
+ end
+ end
+
+ context "when multiple locale names conflict" do
+ before do
+ subject.name_en = "Existing City"
+ subject.name_es = "Ciudad Existente"
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it "adds errors to both locale attributes" do
+ subject.valid?
+ expect(subject.errors[:name_en]).to include("has already been taken")
+ expect(subject.errors[:name_es]).to include("has already been taken")
+ end
+ end
+
+ context "when host already exists" do
+ before { subject.host = "existing.example.org" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:host]).to include("has already been taken")
+ end
+ end
+
+ context "when organization name is unique" do
+ before { subject.name_en = "Unique City" }
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when host is unique" do
+ before { subject.host = "unique.example.org" }
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context "when updating an existing organization" do
+ let(:organization_to_update) do
+ create(
+ :organization,
+ name: { en: "My City", es: "Mi Ciudad" },
+ host: "mycity.example.org"
+ )
+ end
+
+ before do
+ subject.id = organization_to_update.id
+ end
+
+ context "when keeping the same name" do
+ before { subject.name_en = "My City" }
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when keeping the same host" do
+ before { subject.host = "mycity.example.org" }
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when changing name to an existing one" do
+ before { subject.name_en = "Existing City" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:name_en]).to include("has already been taken")
+ end
+ end
+
+ context "when changing host to an existing one" do
+ before { subject.host = "existing.example.org" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:host]).to include("has already been taken")
+ end
+ end
+
+ context "when changing name to a unique one" do
+ before { subject.name_en = "Brand New City" }
+
+ it { is_expected.to be_valid }
+ end
+
+ context "when changing host to a unique one" do
+ before { subject.host = "other.example.org" }
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context "when name contains machine_translations" do
+ let!(:org_with_translations) do
+ create(
+ :organization,
+ name: {
+ :en => "City",
+ "machine_translations" => { fr: "Ville" }
+ }
+ )
+ end
+
+ context "when new name conflicts with machine translation" do
+ before { subject.name_en = "Ville" }
+
+ it { is_expected.not_to be_valid }
+
+ it "adds an error" do
+ subject.valid?
+ expect(subject.errors[:name_en]).to include("has already been taken")
+ end
+ end
+ end
+
+ context "when name value is a Hash (nested structure)" do
+ before do
+ allow(subject).to receive(:name).and_return({ en: { nested: "value" }, es: "Valid Name" })
+ end
+
+ it "skips Hash values during validation" do
+ expect { subject.valid? }.not_to raise_error
+ end
+ end
+ end
+ end
+
+ describe "#map_model" do
+ subject { described_class.from_model(organization) }
+
+ let(:organization) do
+ create(
+ :organization,
+ secondary_hosts: ["foobar.example.org", "foobaz.example.org"],
+ omniauth_settings: {
+ omniauth_settings_facebook_enabled: Decidim::AttributeEncryptor.encrypt(true),
+ omniauth_settings_facebook_app_id: Decidim::AttributeEncryptor.encrypt("foo")
+ },
+ file_upload_settings: {
+ allowed_file_extensions: {
+ default: %w(jpg jpeg),
+ admin: %w(jpg jpeg png),
+ image: %w(jpg jpeg png)
+ },
+ allowed_content_types: {
+ default: %w(image/*),
+ admin: %w(image/*)
+ },
+ maximum_file_size: {
+ default: 7.2,
+ avatar: 2.4
+ }
+ }
+ )
+ end
+
+ it "maps the organization attributes correctly" do
+ expect(subject.secondary_hosts).to eq(organization.secondary_hosts.join("\n"))
+ expect(subject.omniauth_settings).to eq(
+ {
+ "omniauth_settings_facebook_app_id" => "foo",
+ "omniauth_settings_facebook_enabled" => true
+ }
+ )
+ expect(subject.file_upload_settings.final).to eq(
+ {
+ allowed_content_types: { "admin" => %w(image/*), "default" => %w(image/*) },
+ allowed_file_extensions: { "admin" => %w(jpg jpeg png), "default" => %w(jpg jpeg), "image" => %w(jpg jpeg png) },
+ maximum_file_size: { "avatar" => 2.4, "default" => 7.2 }
+ }
+ )
+ end
+ end
+ end
+end
diff --git a/spec/system/proposals_index_spec.rb b/spec/system/proposals_index_spec.rb
index 40aa825..458057a 100644
--- a/spec/system/proposals_index_spec.rb
+++ b/spec/system/proposals_index_spec.rb
@@ -157,11 +157,10 @@
it "lists the proposals and the external proposals" do
visit_component
- # 5 cards
- expect(page).to have_css("a[class='card__list']", count: 5)
# 3 proposals
- expect(page).to have_css("[id^='proposals__proposal']", count: 3)
+ expect(page).to have_css("a[class='card__list']", count: 3)
# 2 external proposals
+ expect(page).to have_css("a[class='card__list card__list-external']", count: 2)
expect(page).to have_css("[id='JD-PROP-2025-09-1']", count: 1)
expect(page).to have_css("[id='JD-PROP-2025-09-20']", count: 1)
end
@@ -181,9 +180,9 @@
click_on "Next"
# proposals and external proposals on second page
- expect(page).to have_css("a[class='card__list']", count: 5)
expect(page).to have_css("[data-pages] [data-page][aria-current='page']", text: "2")
- expect(page).to have_css("[id^='proposals__proposal']", count: 3)
+ expect(page).to have_css("a[class='card__list']", count: 3)
+ expect(page).to have_css("a[class='card__list card__list-external']", count: 2)
expect(page).to have_css("[id='JD-PROP-2025-09-1']", count: 1)
expect(page).to have_css("[id='JD-PROP-2025-09-20']", count: 1)
end
@@ -197,11 +196,10 @@
it "returns the double amount of external proposals" do
visit_component
- # 7 cards
- expect(page).to have_css("a[class='card__list']", count: 7)
# 3 proposals
- expect(page).to have_css("[id^='proposals__proposal']", count: 3)
+ expect(page).to have_css("a[class='card__list']", count: 3)
# 4 external proposals
+ expect(page).to have_css("a[class='card__list card__list-external']", count: 4)
expect(page).to have_css("[id='JD-PROP-2025-09-1']", count: 2)
expect(page).to have_css("[id='JD-PROP-2025-09-20']", count: 2)
end
<%= external_proposal["children"].present? ? external_proposal["children"].count : 0 %>
+