diff --git a/.github/workflows/ci_dataspace.yml b/.github/workflows/ci_dataspace.yml
index ba53310..1fe974b 100644
--- a/.github/workflows/ci_dataspace.yml
+++ b/.github/workflows/ci_dataspace.yml
@@ -20,7 +20,17 @@ concurrency:
cancel-in-progress: true
jobs:
- main:
+ lint:
+ name: Lint code
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: rokroskar/workflow-run-cleanup-action@v0.3.0
+ if: "github.ref != 'refs/heads/develop'"
+ env:
+ GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+ - uses: OpenSourcePolitics/lint-action@master
+ tests:
name: Tests
runs-on: ubuntu-latest
timeout-minutes: 30
diff --git a/Gemfile b/Gemfile
index 7b75364..344652e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,7 +4,7 @@ source "https://rubygems.org"
ruby RUBY_VERSION
-gem "decidim", "~> 0.29.1"
+gem "decidim", "~> 0.29.3"
gem "decidim-dataspace", path: "."
gem "bootsnap", "~> 1.4"
@@ -14,7 +14,7 @@ gem "uri", ">= 1.0.4"
group :development, :test do
gem "byebug", "~> 11.0", platform: :mri
- gem "decidim-dev", "~> 0.29.1"
+ gem "decidim-dev", "~> 0.29.3"
end
group :development do
diff --git a/Gemfile.lock b/Gemfile.lock
index e989858..2372c81 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -819,9 +819,9 @@ PLATFORMS
DEPENDENCIES
bootsnap (~> 1.4)
byebug (~> 11.0)
- decidim (~> 0.29.1)
+ decidim (~> 0.29.3)
decidim-dataspace!
- decidim-dev (~> 0.29.1)
+ decidim-dev (~> 0.29.3)
faker (~> 3.2)
letter_opener_web (~> 2.0)
listen (~> 3.1)
diff --git a/app/controllers/decidim/dataspace/api/v1/base_controller.rb b/app/controllers/decidim/dataspace/api/v1/base_controller.rb
index ea97216..ca941ba 100644
--- a/app/controllers/decidim/dataspace/api/v1/base_controller.rb
+++ b/app/controllers/decidim/dataspace/api/v1/base_controller.rb
@@ -2,8 +2,9 @@
module Decidim
module Dataspace
- class Api::V1::BaseController < ActionController::API
+ class Api::V1::BaseController < Decidim::Api::ApplicationController
# skip_before_action :verify_authenticity_token
+ before_action :verify_dataspace_enabled
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActiveRecord::RecordInvalid, with: :validation_error
@@ -21,6 +22,14 @@ def validation_error(exception)
def resource_not_found(resource)
render json: { error: "#{resource} not found" }, status: :not_found
end
+
+ def verify_dataspace_enabled
+ render json: { error: "Dataspace is not enabled for this organization" }, status: :forbidden unless dataspace_enabled?
+ end
+
+ def dataspace_enabled?
+ current_organization.enable_dataspace == true
+ end
end
end
end
diff --git a/app/views/decidim/admin/components/_settings_fields.html.erb b/app/views/decidim/admin/components/_settings_fields.html.erb
index 2fc5009..b55db5c 100644
--- a/app/views/decidim/admin/components/_settings_fields.html.erb
+++ b/app/views/decidim/admin/components/_settings_fields.html.erb
@@ -1,4 +1,7 @@
<% manifest.settings(settings_name).attributes.each do |field_name, settings_attribute| %>
+ <% dataspace_related = [:add_integration, :integration_url, :preferred_locale] %>
+ <% next if dataspace_related.include?(field_name) && current_organization.enable_dataspace == false %>
+
<%= settings_attribute_input(
form,
diff --git a/app/views/decidim/system/organizations/edit.html.erb b/app/views/decidim/system/organizations/edit.html.erb
new file mode 100644
index 0000000..cc699d2
--- /dev/null
+++ b/app/views/decidim/system/organizations/edit.html.erb
@@ -0,0 +1,53 @@
+<% add_decidim_page_title(t(".title")) %>
+
+<% provide :title do %>
+
<%= t ".title" %>
+<% end %>
+
+<%= decidim_form_for(@form, url: organization_path(@organization)) do |f| %>
+
+
+
+ <% if @organization&.users&.first&.invitation_pending? %>
+ <%= link_to t(".resend_invitation"),
+ resend_invitation_organization_path(@organization),
+ method: :post,
+ class: "button button__sm md:button__lg button__transparent-secondary",
+ data: { confirm: t(".confirm_resend_invitation") } %>
+ <% end %>
+ <%= f.submit t("decidim.system.actions.save"), class: "button button__sm md:button__lg button__primary" %>
+
+<% end %>
diff --git a/app/views/decidim/system/organizations/new.html.erb b/app/views/decidim/system/organizations/new.html.erb
new file mode 100644
index 0000000..3fbaecf
--- /dev/null
+++ b/app/views/decidim/system/organizations/new.html.erb
@@ -0,0 +1,72 @@
+<% add_decidim_page_title(t(".title")) %>
+
+<% provide :title do %>
+
<%= t ".title" %>
+<% end %>
+
+<%= decidim_form_for(@form) do |f| %>
+
+
+
+ <%= f.submit t("decidim.system.models.organization.actions.save_and_invite"), class: "button button__sm md:button__lg button__primary" %>
+
+<% end %>
diff --git a/db/migrate/20260129143027_add_enable_dataspace_to_organizations.rb b/db/migrate/20260129143027_add_enable_dataspace_to_organizations.rb
new file mode 100644
index 0000000..97ece8e
--- /dev/null
+++ b/db/migrate/20260129143027_add_enable_dataspace_to_organizations.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddEnableDataspaceToOrganizations < ActiveRecord::Migration[7.0]
+ def up
+ add_column :decidim_organizations, :enable_dataspace, :boolean, default: false, null: false
+ end
+
+ def down
+ remove_column :decidim_organizations, :enable_dataspace
+ end
+end
diff --git a/lib/decidim/dataspace/engine.rb b/lib/decidim/dataspace/engine.rb
index b0e0177..221ad09 100644
--- a/lib/decidim/dataspace/engine.rb
+++ b/lib/decidim/dataspace/engine.rb
@@ -44,6 +44,9 @@ class Engine < ::Rails::Engine
require "extends/controllers/decidim/proposals/proposals_controller_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"
end
end
diff --git a/lib/extends/commands/decidim/system/create_organization_extends.rb b/lib/extends/commands/decidim/system/create_organization_extends.rb
new file mode 100644
index 0000000..93d169d
--- /dev/null
+++ b/lib/extends/commands/decidim/system/create_organization_extends.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module CreateOrganizationExtends
+ extend ActiveSupport::Concern
+
+ included do
+ def create_organization
+ Decidim::Organization.create!(
+ name: { form.default_locale => form.name },
+ host: form.host,
+ secondary_hosts: form.clean_secondary_hosts,
+ reference_prefix: form.reference_prefix,
+ available_locales: form.available_locales,
+ available_authorizations: form.clean_available_authorizations,
+ external_domain_allowlist: ["decidim.org", "github.com"],
+ users_registration_mode: form.users_registration_mode,
+ force_users_to_authenticate_before_access_organization: form.force_users_to_authenticate_before_access_organization,
+ badges_enabled: true,
+ user_groups_enabled: true,
+ default_locale: form.default_locale,
+ omniauth_settings: form.encrypted_omniauth_settings,
+ smtp_settings: form.encrypted_smtp_settings,
+ send_welcome_notification: true,
+ file_upload_settings: form.file_upload_settings.final,
+ colors: default_colors,
+ content_security_policy: form.content_security_policy,
+ enable_dataspace: form.enable_dataspace
+ )
+ end
+ end
+end
+
+Decidim::System::CreateOrganization.include(CreateOrganizationExtends)
diff --git a/lib/extends/commands/decidim/system/update_organization_extends.rb b/lib/extends/commands/decidim/system/update_organization_extends.rb
new file mode 100644
index 0000000..a46f1de
--- /dev/null
+++ b/lib/extends/commands/decidim/system/update_organization_extends.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module UpdateOrganizationExtends
+ extend ActiveSupport::Concern
+
+ included do
+ def save_organization
+ organization.name = form.name
+ organization.host = form.host
+ organization.secondary_hosts = form.clean_secondary_hosts
+ organization.force_users_to_authenticate_before_access_organization = form.force_users_to_authenticate_before_access_organization
+ organization.available_authorizations = form.clean_available_authorizations
+ organization.users_registration_mode = form.users_registration_mode
+ organization.omniauth_settings = form.encrypted_omniauth_settings
+ organization.smtp_settings = form.encrypted_smtp_settings
+ organization.file_upload_settings = form.file_upload_settings.final
+ organization.content_security_policy = form.content_security_policy
+ organization.enable_dataspace = form.enable_dataspace
+
+ organization.save!
+ end
+ end
+end
+
+Decidim::System::UpdateOrganization.include(UpdateOrganizationExtends)
diff --git a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb
index 292b783..c8bc70e 100644
--- a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb
+++ b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb
@@ -7,6 +7,8 @@ module ProposalsControllerExtends
extend ActiveSupport::Concern
included do
+ before_action :dataspace_enabled, only: :external_proposal
+
def index
if component_settings.participatory_texts_enabled?
@proposals = Decidim::Proposals::Proposal.where(component: current_component)
@@ -17,8 +19,7 @@ def index
.order(position: :asc)
render "decidim/proposals/proposals/participatory_texts/participatory_text"
else
- if component_settings.add_integration && component_settings.integration_url.present? && data.present?
-
+ if verify_dataspace? && data.present?
external_proposals = data["contributions"]
@authors = data["authors"]
proposals = search.result
@@ -58,6 +59,14 @@ def external_proposal
private
+ def verify_dataspace?
+ current_organization.enable_dataspace == true && component_settings.add_integration && component_settings.integration_url.present?
+ end
+
+ def dataspace_enabled
+ redirect_to(root_url) && return unless verify_dataspace?
+ end
+
def voted_proposals
if current_user
Decidim::Proposals::ProposalVote.where(
diff --git a/lib/extends/forms/decidim/system/base_organization_form_extends.rb b/lib/extends/forms/decidim/system/base_organization_form_extends.rb
new file mode 100644
index 0000000..85a451e
--- /dev/null
+++ b/lib/extends/forms/decidim/system/base_organization_form_extends.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module BaseOrganizationFormExtends
+ extend ActiveSupport::Concern
+
+ included do
+ attribute :enable_dataspace, Decidim::AttributeObject::TypeMap::Boolean
+ end
+end
+
+Decidim::System::BaseOrganizationForm.include(BaseOrganizationFormExtends)
diff --git a/lib/extends/lib/decidim/core_extends.rb b/lib/extends/lib/decidim/core_extends.rb
index c2d4319..039ca11 100644
--- a/lib/extends/lib/decidim/core_extends.rb
+++ b/lib/extends/lib/decidim/core_extends.rb
@@ -12,11 +12,8 @@ module CoreExtends
lambda do |resource, component|
ref = ""
- if resource.is_a?(Decidim::HasComponent) && component.present?
- # It is a component resource
- ref = component.participatory_space.organization.reference_prefix
- elsif resource.is_a?(Decidim::Comments::Comment) && component.present?
- # It is a comment resource
+ if (resource.is_a?(Decidim::HasComponent) || resource.is_a?(Decidim::Comments::Comment)) && component.present?
+ # It is a component resource or a comment resource
ref = component.participatory_space.organization.reference_prefix
elsif resource.is_a?(Decidim::Participable)
# It is a participatory space
diff --git a/spec/controllers/decidim/dataspace/api/v1/authors_controller_spec.rb b/spec/controllers/decidim/dataspace/api/v1/authors_controller_spec.rb
index b74311b..7e455c0 100644
--- a/spec/controllers/decidim/dataspace/api/v1/authors_controller_spec.rb
+++ b/spec/controllers/decidim/dataspace/api/v1/authors_controller_spec.rb
@@ -6,88 +6,129 @@
routes { Decidim::Dataspace::Engine.routes }
describe "index" do
- context "when there are proposals and authors" do
- let(:component) { create(:proposal_component) }
- let!(:proposal) { create(:proposal, :participant_author, component:) }
- let!(:proposal_two) { create(:proposal, :official_meeting, component:) }
- let!(:proposal_three) { create(:proposal, :official, component:) }
+ context "when dataspace is disabled" do
+ let!(:component) { create(:proposal_component) }
- it "is a success and returns json" do
+ before do
+ request.env["decidim.current_organization"] = component.organization
get :index
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
end
- it "returns all authors" do
- get :index
- expect(response.parsed_body.size).to eq(3)
+ it "returns a forbidden status" do
+ expect(response).to have_http_status(:forbidden)
end
end
- context "when there are no proposals and so no authors" do
- it "is a not_found and returns json without authors" do
- get :index
- expect(response).to have_http_status(:not_found)
- expect(response.content_type).to include("application/json")
- expect(response.parsed_body).to eq({ "error" => "Authors not found" })
+ context "when dataspace is enabled" do
+ let!(:component) { create(:proposal_component) }
+
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ component.organization.enable_dataspace = true
+ component.organization.save!
+ end
+
+ context "and there are proposals and authors" do
+ let!(:proposal) { create(:proposal, :participant_author, component:) }
+ let!(:proposal_two) { create(:proposal, :official_meeting, component:) }
+ let!(:proposal_three) { create(:proposal, :official, component:) }
+
+ it "is a success, and it returns json with 3 authors" do
+ get :index
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ # we have 3 different authors
+ expect(response.parsed_body.size).to eq(3)
+ end
+ end
+
+ context "and there are no proposals and so no authors" do
+ it "is a not_found and returns json without authors" do
+ get :index
+ expect(response).to have_http_status(:not_found)
+ expect(response.content_type).to include("application/json")
+ expect(response.parsed_body).to eq({ "error" => "Authors not found" })
+ end
end
end
end
describe "show" do
- context "when author exists" do
+ context "when dataspace is disabled" do
let(:component) { create(:proposal_component) }
let!(:proposal) { create(:proposal, :participant_author, component:) }
- it "is a success and returns json" do
+ before do
+ request.env["decidim.current_organization"] = component.organization
get :show, params: { reference: proposal.authors.first.name }
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
end
- context "and author is a user" do
- it "returns the author" do
- author = proposal.authors.first
- get :show, params: { reference: author.name }
- expect(response.parsed_body).to eq({ "reference" => author.name,
- "name" => author.name,
- "source" => author.personal_url })
- end
+ it "returns a forbidden status" do
+ expect(response).to have_http_status(:forbidden)
end
+ end
- context "and author is a official" do
- let!(:proposal) { create(:proposal, :official, component:) }
+ context "when dataspace is enabled" do
+ let(:component) { create(:proposal_component) }
+ let!(:proposal) { create(:proposal, :participant_author, component:) }
- it "returns the author" do
- author = proposal.authors.first
- get :show, params: { reference: author.reference_prefix }
- expect(response.parsed_body).to eq({ "reference" => author.reference_prefix,
- "name" => author.name["en"],
- "source" => author.official_url })
- end
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ component.organization.enable_dataspace = true
+ component.organization.save!
end
- context "and author is a meeting" do
- let!(:proposal) { create(:proposal, :official_meeting, component:) }
+ context "and author exists" do
+ it "is a success and returns json" do
+ get :show, params: { reference: proposal.authors.first.name }
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ end
- it "returns the author" do
- author = proposal.authors.first
- get :show, params: { reference: author.reference }
- expect(response.parsed_body).to eq({ "reference" => author.reference,
- "name" => author.title["en"],
- "source" => Decidim::ResourceLocatorPresenter.new(author).url })
+ context "and author is a user" do
+ it "returns the author" do
+ author = proposal.authors.first
+ get :show, params: { reference: author.name }
+ expect(response.parsed_body).to eq({ "reference" => author.name,
+ "name" => author.name,
+ "source" => author.personal_url })
+ end
+ end
+
+ context "and author is a official" do
+ let!(:proposal) { create(:proposal, :official, component:) }
+
+ it "returns the author" do
+ author = proposal.authors.first
+ get :show, params: { reference: author.reference_prefix }
+ expect(response.parsed_body).to eq({ "reference" => author.reference_prefix,
+ "name" => author.name["en"],
+ "source" => author.official_url })
+ end
+ end
+
+ context "and author is a meeting" do
+ let!(:proposal) { create(:proposal, :official_meeting, component:) }
+
+ it "returns the author" do
+ author = proposal.authors.first
+ get :show, params: { reference: author.reference }
+ expect(response.parsed_body).to eq({ "reference" => author.reference,
+ "name" => author.title["en"],
+ "source" => Decidim::ResourceLocatorPresenter.new(author).url })
+ end
end
end
- end
- context "when author does not exist" do
- it "is a not_found and returns json message" do
- get :show, params: { reference: "XXX" }
- expect(response).to have_http_status(:not_found)
- expect(response.content_type).to include("application/json")
- expect(response.parsed_body).to eq({ "error" => "Author not found" })
+ context "when author does not exist" do
+ it "is a not_found and returns json message" do
+ get :show, params: { reference: "XXX" }
+ expect(response).to have_http_status(:not_found)
+ expect(response.content_type).to include("application/json")
+ expect(response.parsed_body).to eq({ "error" => "Author not found" })
+ end
end
end
end
diff --git a/spec/controllers/decidim/dataspace/api/v1/containers_controller_spec.rb b/spec/controllers/decidim/dataspace/api/v1/containers_controller_spec.rb
index fb4f4dd..1d76233 100644
--- a/spec/controllers/decidim/dataspace/api/v1/containers_controller_spec.rb
+++ b/spec/controllers/decidim/dataspace/api/v1/containers_controller_spec.rb
@@ -6,67 +6,112 @@
routes { Decidim::Dataspace::Engine.routes }
describe "index" do
- context "when there are proposals and containers" do
- let(:component) { create(:proposal_component) }
- let!(:proposal) { create(:proposal, component:) }
- let!(:proposal_two) { create(:proposal, component:) }
+ context "when dataspace is disabled" do
+ let!(:component) { create(:proposal_component) }
before do
+ request.env["decidim.current_organization"] = component.organization
get :index
end
- it "is a success and returns json" do
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
+ it "returns a forbidden status" do
+ expect(response).to have_http_status(:forbidden)
end
+ end
+
+ context "when dataspace is enabled" do
+ let!(:component) { create(:proposal_component) }
- it "returns all containers" do
- # proposals are created from participatory_process
- expect(response.parsed_body.size).to eq(1)
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ component.organization.enable_dataspace = true
+ component.organization.save!
end
- end
- context "when there are no proposals and so no containers" do
- it "is a not_found and returns json without authors" do
- get :index
- expect(response).to have_http_status(:not_found)
- expect(response.content_type).to include("application/json")
- expect(response.parsed_body).to eq({ "error" => "Containers not found" })
+ context "and there are proposals and containers" do
+ let!(:proposal) { create(:proposal, component:) }
+ let!(:proposal_two) { create(:proposal, component:) }
+
+ before do
+ get :index
+ end
+
+ it "is a success and returns json" do
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ end
+
+ it "returns all containers" do
+ # proposals are created from participatory_process
+ expect(response.parsed_body.size).to eq(1)
+ end
+ end
+
+ context "and there are no proposals and so no containers" do
+ it "is a not_found and returns json without authors" do
+ get :index
+ expect(response).to have_http_status(:not_found)
+ expect(response.content_type).to include("application/json")
+ expect(response.parsed_body).to eq({ "error" => "Containers not found" })
+ end
end
end
end
describe "show" do
- context "when container exists" do
+ context "when dataspace is disabled" do
let(:component) { create(:proposal_component) }
- let(:proposal) { create(:proposal, component:) }
- let!(:container) { proposal.component.participatory_space_type.constantize.find(proposal.component.participatory_space_id) }
+ let!(:proposal) { create(:proposal, :participant_author, component:) }
before do
- get :show, params: { reference: container.reference }
+ request.env["decidim.current_organization"] = component.organization
+ get :show, params: { reference: proposal.authors.first.name }
end
- it "is a success and returns json" do
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
+ it "returns a forbidden status" do
+ expect(response).to have_http_status(:forbidden)
end
+ end
+
+ context "when dataspace is enabled" do
+ let!(:component) { create(:proposal_component) }
- it "returns the container" do
- expect(response.parsed_body["reference"]).to eq(container.reference)
- expect(response.parsed_body["source"]).to eq(Decidim::ResourceLocatorPresenter.new(container).url)
- expect(response.parsed_body["name"]).to eq(container.title["en"])
- expect(response.parsed_body["description"]).to eq(container.description["en"])
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ component.organization.enable_dataspace = true
+ component.organization.save!
+ end
+
+ context "and container exists" do
+ let(:proposal) { create(:proposal, component:) }
+ let!(:container) { proposal.component.participatory_space_type.constantize.find(proposal.component.participatory_space_id) }
+
+ before do
+ get :show, params: { reference: container.reference }
+ end
+
+ it "is a success and returns json" do
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ end
+
+ it "returns the container" do
+ expect(response.parsed_body["reference"]).to eq(container.reference)
+ expect(response.parsed_body["source"]).to eq(Decidim::ResourceLocatorPresenter.new(container).url)
+ expect(response.parsed_body["name"]).to eq(container.title["en"])
+ expect(response.parsed_body["description"]).to eq(container.description["en"])
+ end
end
- end
- context "when container does not exist" do
- it "is a not_found and returns json message" do
- get :show, params: { reference: "XXX" }
- expect(response).to have_http_status(:not_found)
- expect(response.content_type).to include("application/json")
- expect(response.parsed_body).to eq({ "error" => "Container not found" })
+ context "when container does not exist" do
+ it "is a not_found and returns json message" do
+ get :show, params: { reference: "XXX" }
+ expect(response).to have_http_status(:not_found)
+ expect(response.content_type).to include("application/json")
+ expect(response.parsed_body).to eq({ "error" => "Container not found" })
+ end
end
end
end
diff --git a/spec/controllers/decidim/dataspace/api/v1/contributions_controller_spec.rb b/spec/controllers/decidim/dataspace/api/v1/contributions_controller_spec.rb
index 5eb2b01..fbeffc6 100644
--- a/spec/controllers/decidim/dataspace/api/v1/contributions_controller_spec.rb
+++ b/spec/controllers/decidim/dataspace/api/v1/contributions_controller_spec.rb
@@ -6,128 +6,173 @@
routes { Decidim::Dataspace::Engine.routes }
describe "index" do
- context "when there are contributions with comments" do
- let(:component) { create(:proposal_component) }
- let!(:proposal) { create(:proposal, component:) }
- let!(:comment_one) { create(:comment, commentable: proposal) }
- let!(:comment_two) { create(:comment, commentable: proposal) }
- let!(:proposal_two) { create(:proposal, component:) }
- let!(:proposal_three) { create(:proposal, component:) }
+ context "when dataspace is disabled" do
+ let!(:component) { create(:proposal_component) }
- context "and with_comments is false" do
- it "is a success and returns json with only proposals as contributions" do
- get :index
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
- # only proposals are rendered
- expect(response.parsed_body.size).to eq(3)
- end
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ get :index
end
- context "and with_comments is true" do
- it "is a success and returns json with proposals and comments as contributions" do
- get :index, params: { with_comments: "true" }
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
- # proposals + comments are rendered
- expect(response.parsed_body.size).to eq(5)
- end
+ it "returns a forbidden status" do
+ expect(response).to have_http_status(:forbidden)
end
end
- context "when there are no contributions" do
- it "is a not_found and returns json without authors" do
- get :index
- expect(response).to have_http_status(:not_found)
- expect(response.content_type).to include("application/json")
- expect(response.parsed_body).to eq({ "error" => "Contributions not found" })
+ context "when dataspace is enabled" do
+ let!(:component) { create(:proposal_component) }
+
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ component.organization.enable_dataspace = true
+ component.organization.save!
end
- end
- context "when there is a container param" do
- let(:component_two) { create(:proposal_component) }
- let!(:proposal_four) { create(:proposal, :participant_author, component: component_two) }
+ context "and there are contributions with comments" do
+ let!(:proposal) { create(:proposal, component:) }
+ let!(:comment_one) { create(:comment, commentable: proposal) }
+ let!(:comment_two) { create(:comment, commentable: proposal) }
+ let!(:proposal_two) { create(:proposal, component:) }
+ let!(:proposal_three) { create(:proposal, component:) }
+
+ context "and with_comments is false" do
+ it "is a success and returns json with only proposals as contributions" do
+ get :index
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ # only proposals are rendered
+ expect(response.parsed_body.size).to eq(3)
+ end
+ end
- before do
- get :index, params: { container: component_two.participatory_space.reference }
+ context "and with_comments is true" do
+ it "is a success and returns json with proposals and comments as contributions" do
+ get :index, params: { with_comments: "true" }
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ # proposals + comments are rendered
+ expect(response.parsed_body.size).to eq(5)
+ end
+ end
end
- it "is a success and returns json with filtered proposals as contributions" do
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
- # only proposal_four is rendered
- expect(response.parsed_body.size).to eq(1)
- expect(response.parsed_body.first["reference"]).to eq(proposal_four.reference)
+ context "when there are no contributions" do
+ it "is a not_found and returns json without authors" do
+ get :index
+ expect(response).to have_http_status(:not_found)
+ expect(response.content_type).to include("application/json")
+ expect(response.parsed_body).to eq({ "error" => "Contributions not found" })
+ end
end
- end
- end
- describe "show" do
- let(:component) { create(:proposal_component) }
- let!(:proposal) { create(:proposal, component:) }
- let!(:comment_one) { create(:comment, commentable: proposal) }
- let!(:comment_two) { create(:comment, commentable: proposal) }
+ context "when there is a container param" do
+ let(:component_two) { create(:proposal_component) }
+ let!(:proposal_four) { create(:proposal, :participant_author, component: component_two) }
- context "when contribution exists" do
- context "and with comments is false" do
before do
- get :show, params: { reference: proposal.reference }
+ get :index, params: { container: component_two.participatory_space.reference }
end
- it "is a success and returns json" do
+ it "is a success and returns json with filtered proposals as contributions" do
expect(response).to have_http_status(:ok)
expect(response.content_type).to include("application/json")
expect { response.parsed_body }.not_to raise_error
+ # only proposal_four is rendered
+ expect(response.parsed_body.size).to eq(1)
+ expect(response.parsed_body.first["reference"]).to eq(proposal_four.reference)
end
+ end
+ end
+ end
- it "returns the contribution with no detailed comments" do
- expect(response.parsed_body["reference"]).to eq(proposal.reference)
- expect(response.parsed_body["source"]).to eq(Decidim::ResourceLocatorPresenter.new(proposal).url)
- expect(response.parsed_body["container"]).to eq(proposal.component.participatory_space_type.constantize.find(proposal.component.participatory_space_id).reference)
- expect(response.parsed_body["title"]).to eq(proposal.title["en"])
- expect(response.parsed_body["content"]).to eq(proposal.body["en"])
- # 2 comments without details
- expect(response.parsed_body["children"].size).to eq(2)
- expect(response.parsed_body["children"].first.class).to eq(String)
- expect(response.parsed_body["children"].first).to eq(comment_one.reference)
- end
+ describe "show" do
+ context "when dataspace is disabled" do
+ let(:component) { create(:proposal_component) }
+ let!(:proposal) { create(:proposal, component:) }
+
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ get :show, params: { reference: proposal.authors.first.name }
end
- context "and with comments is true" do
- before do
- get :show, params: { reference: proposal.reference, with_comments: "true" }
- end
+ it "returns a forbidden status" do
+ expect(response).to have_http_status(:forbidden)
+ end
+ end
- it "is a success and returns json" do
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
+ context "when dataspace is enabled" do
+ let!(:component) { create(:proposal_component) }
+
+ before do
+ request.env["decidim.current_organization"] = component.organization
+ component.organization.enable_dataspace = true
+ component.organization.save!
+ end
+
+ context "and contribution exists" do
+ let!(:proposal) { create(:proposal, component:) }
+ let!(:comment_one) { create(:comment, commentable: proposal) }
+ let!(:comment_two) { create(:comment, commentable: proposal) }
+
+ context "and with comments is false" do
+ before do
+ get :show, params: { reference: proposal.reference }
+ end
+
+ it "is a success and returns json" do
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ end
+
+ it "returns the contribution with no detailed comments" do
+ expect(response.parsed_body["reference"]).to eq(proposal.reference)
+ expect(response.parsed_body["source"]).to eq(Decidim::ResourceLocatorPresenter.new(proposal).url)
+ expect(response.parsed_body["container"]).to eq(proposal.component.participatory_space_type.constantize.find(proposal.component.participatory_space_id).reference)
+ expect(response.parsed_body["title"]).to eq(proposal.title["en"])
+ expect(response.parsed_body["content"]).to eq(proposal.body["en"])
+ # 2 comments without details
+ expect(response.parsed_body["children"].size).to eq(2)
+ expect(response.parsed_body["children"].first.class).to eq(String)
+ expect(response.parsed_body["children"].first).to eq(comment_one.reference)
+ end
end
- it "returns the contribution with detailed comments" do
- expect(response.parsed_body["reference"]).to eq(proposal.reference)
- expect(response.parsed_body["source"]).to eq(Decidim::ResourceLocatorPresenter.new(proposal).url)
- expect(response.parsed_body["container"]).to eq(proposal.component.participatory_space_type.constantize.find(proposal.component.participatory_space_id).reference)
- expect(response.parsed_body["title"]).to eq(proposal.title["en"])
- expect(response.parsed_body["content"]).to eq(proposal.body["en"])
- # 2 comments with details
- expect(response.parsed_body["children"].size).to eq(2)
- expect(response.parsed_body["children"].first.class).to eq(Hash)
- expect(response.parsed_body["children"].first["reference"]).to eq(comment_one.reference)
- expect(response.parsed_body["children"].first["content"]).to eq(comment_one.body["en"])
+ context "and with comments is true" do
+ before do
+ get :show, params: { reference: proposal.reference, with_comments: "true" }
+ end
+
+ it "is a success and returns json" do
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ end
+
+ it "returns the contribution with detailed comments" do
+ expect(response.parsed_body["reference"]).to eq(proposal.reference)
+ expect(response.parsed_body["source"]).to eq(Decidim::ResourceLocatorPresenter.new(proposal).url)
+ expect(response.parsed_body["container"]).to eq(proposal.component.participatory_space_type.constantize.find(proposal.component.participatory_space_id).reference)
+ expect(response.parsed_body["title"]).to eq(proposal.title["en"])
+ expect(response.parsed_body["content"]).to eq(proposal.body["en"])
+ # 2 comments with details
+ expect(response.parsed_body["children"].size).to eq(2)
+ expect(response.parsed_body["children"].first.class).to eq(Hash)
+ expect(response.parsed_body["children"].first["reference"]).to eq(comment_one.reference)
+ expect(response.parsed_body["children"].first["content"]).to eq(comment_one.body["en"])
+ end
end
end
- end
- context "when contribution does not exist" do
- it "is a not_found and returns json message" do
- get :show, params: { reference: "XXX" }
- expect(response).to have_http_status(:not_found)
- expect(response.content_type).to include("application/json")
- expect(response.parsed_body).to eq({ "error" => "Contribution not found" })
+ context "and contribution does not exist" do
+ it "is a not_found and returns json message" do
+ get :show, params: { reference: "XXX" }
+ expect(response).to have_http_status(:not_found)
+ expect(response.content_type).to include("application/json")
+ expect(response.parsed_body).to eq({ "error" => "Contribution not found" })
+ end
end
end
end
diff --git a/spec/controllers/decidim/dataspace/api/v1/data_controller_spec.rb b/spec/controllers/decidim/dataspace/api/v1/data_controller_spec.rb
index 02bfadb..d855168 100644
--- a/spec/controllers/decidim/dataspace/api/v1/data_controller_spec.rb
+++ b/spec/controllers/decidim/dataspace/api/v1/data_controller_spec.rb
@@ -11,43 +11,62 @@
let!(:proposal_three) { create(:proposal, :official, component:) }
describe "index" do
- context "when there is no container param" do
+ context "when dataspace is disabled" do
before do
+ request.env["decidim.current_organization"] = component.organization
get :index
end
- it "is successful and returns json" do
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
- end
-
- it "returns all data" do
- expect(response.parsed_body["contributions"].size).to eq(3)
- expect(response.parsed_body["authors"].size).to eq(3)
- expect(response.parsed_body["containers"].size).to eq(1)
+ it "returns a forbidden status" do
+ expect(response).to have_http_status(:forbidden)
end
end
- context "when there is a container param" do
- let(:component_two) { create(:proposal_component) }
- let!(:proposal_four) { create(:proposal, :participant_author, component: component_two) }
-
+ context "when dataspace is enabled" do
before do
- get :index, params: { container: component_two.participatory_space.reference }
+ request.env["decidim.current_organization"] = component.organization
+ component.organization.enable_dataspace = true
+ component.organization.save!
end
- it "is successful and returns json" do
- expect(response).to have_http_status(:ok)
- expect(response.content_type).to include("application/json")
- expect { response.parsed_body }.not_to raise_error
+ context "and there is no container param" do
+ before do
+ get :index
+ end
+
+ it "is successful and returns json" do
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ end
+
+ it "returns all data" do
+ expect(response.parsed_body["contributions"].size).to eq(3)
+ expect(response.parsed_body["authors"].size).to eq(3)
+ expect(response.parsed_body["containers"].size).to eq(1)
+ end
end
- it "returns data with filtered contributions" do
- expect(response.parsed_body["contributions"].size).to eq(1)
- expect(response.parsed_body["contributions"].first["reference"]).to eq(proposal_four.reference)
- expect(response.parsed_body["authors"].size).to eq(4)
- expect(response.parsed_body["containers"].size).to eq(2)
+ context "and there is a container param" do
+ let(:component_two) { create(:proposal_component) }
+ let!(:proposal_four) { create(:proposal, :participant_author, component: component_two) }
+
+ before do
+ get :index, params: { container: component_two.participatory_space.reference }
+ end
+
+ it "is successful and returns json" do
+ expect(response).to have_http_status(:ok)
+ expect(response.content_type).to include("application/json")
+ expect { response.parsed_body }.not_to raise_error
+ end
+
+ it "returns data with filtered contributions" do
+ expect(response.parsed_body["contributions"].size).to eq(1)
+ expect(response.parsed_body["contributions"].first["reference"]).to eq(proposal_four.reference)
+ expect(response.parsed_body["authors"].size).to eq(4)
+ expect(response.parsed_body["containers"].size).to eq(2)
+ end
end
end
end
diff --git a/spec/controllers/decidim/proposals/proposals_controller_spec.rb b/spec/controllers/decidim/proposals/proposals_controller_spec.rb
index 4f6076e..608b4f3 100644
--- a/spec/controllers/decidim/proposals/proposals_controller_spec.rb
+++ b/spec/controllers/decidim/proposals/proposals_controller_spec.rb
@@ -7,8 +7,8 @@ module Proposals
describe ProposalsController do
routes { Decidim::Proposals::Engine.routes }
+ let(:component) { create(:proposal_component, :with_geocoding_enabled) }
let(:user) { create(:user, :confirmed, organization: component.organization) }
-
let(:proposal_params) do
{
component_id: component.id
@@ -24,167 +24,184 @@ module Proposals
end
describe "GET index" do
+ context "when participatory texts are enabled" do
+ let(:component) { create(:proposal_component, :with_participatory_texts_enabled) }
+
+ it "sorts proposals by position" do
+ get :index
+ expect(response).to have_http_status(:ok)
+ expect(subject).to render_template(:participatory_text)
+ expect(assigns(:proposals).order_values.first.expr.name).to eq("position")
+ end
+
+ context "when emendations exist" do
+ let!(:amendable) { create(:proposal, component:) }
+ let!(:emendation) { create(:proposal, component:) }
+ let!(:amendment) { create(:amendment, amendable:, emendation:, state: "accepted") }
+
+ it "does not include emendations" do
+ get :index
+ expect(response).to have_http_status(:ok)
+ emendations = assigns(:proposals).select(&:emendation?)
+ expect(emendations).to be_empty
+ end
+ end
+ end
+
context "when participatory texts are disabled" do
- let(:component) { create(:proposal_component, :with_geocoding_enabled) }
+ context "and dataspace is disabled" do
+ let!(:geocoded_proposals) { create_list(:proposal, 10, component:, latitude: 1.1, longitude: 2.2) }
+ let!(:proposals) { create_list(:proposal, 2, component:, latitude: nil, longitude: nil) }
- context "and there are no externals proposals" do
- it "sorts proposals by search defaults" do
+ before do
get :index
+ end
+
+ it "sorts proposals by search defaults" do
expect(response).to have_http_status(:ok)
expect(subject).to render_template(:index)
+ expect(assigns(:proposals).size).to eq(12)
expect(assigns(:proposals).order_values).to eq [Decidim::Proposals::Proposal.arel_table[Decidim::Proposals::Proposal.primary_key] * Arel.sql("RANDOM()")]
expect(assigns(:proposals).order_values.map(&:to_sql)).to eq ["\"decidim_proposals_proposals\".\"id\" * RANDOM()"]
end
+
+ it "sets two different collections" do
+ expect(assigns(:proposals)).to match_array(geocoded_proposals + proposals)
+ end
end
- context "and there are externals proposals" do
- let(:component) { create(:proposal_component) }
+ context "and dataspace is enabled" do
let!(:proposals) { create_list(:proposal, 2, component:) }
- let(:contrib_one) do
- { "reference": "JD-PROP-2025-09-1",
- "source": "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
- "container": "JD-PART-2025-09-1",
- "locale": "en",
- "title": "Test one",
- "content": "Debitis repellat provident",
- "authors": ["JD-MEET-2025-09-6"],
- "created_at": "2025-09-11T10:20:21.222Z",
- "updated_at": "2025-09-11T10:21:56.604Z",
- "deleted_at": nil }
- end
- let(:contrib_two) do
- { "reference": "JD-PROP-2025-09-20",
- "source": "http://localhost:3000/assemblies/smile-trivial/f/25/proposals/20",
- "container": "JD-ASSE-2025-09-1",
- "locale": "en",
- "title": "Test two",
- "content": "Non et vel",
- "authors": ["JD-MEET-2025-09-23"],
- "created_at": "2025-09-11T10:43:23.743Z",
- "updated_at": "2025-09-11T10:43:27.147Z",
- "deleted_at": nil }
- end
- let(:container_one) do
- {
- "reference": "JD-PART-2025-09-1",
- "source": "http://localhost:3000/processes/satisfaction-hope",
- "name": "Cupiditate natus dignissimos saepe ut.",
- "description": "
Voluptas recusandae est. Nesciunt excepturi corrupti. Qui natus eligendi.
",
- "metadata": {},
- "created_at": "2025-09-11T10:14:58.111Z",
- "updated_at": "2025-09-11T10:14:58.126Z",
- "deleted_at": nil
- }
- end
- let(:container_two) do
- {
- "reference": "JD-ASSE-2025-09-1",
- "source": "http://localhost:3000/assemblies/smile-trivial",
- "name": "Molestiae aut corporis quas et.",
- "description": "
Ratione autem repellendus. Error voluptatem ipsam. Ut dicta velit.
",
- "metadata": {},
- "created_at": "2025-09-11T10:38:07.682Z",
- "updated_at": "2025-09-11T10:38:07.682Z",
- "deleted_at": nil
- }
- end
- let(:author_one) do
- {
- "reference": "JD-MEET-2025-09-6",
- "name": "Animi voluptatum.",
- "source": "http://localhost:3000/processes/satisfaction-hope/f/5/meetings/6"
- }
- end
- let(:author_two) do
- {
- "reference": "JD-MEET-2025-09-23",
- "name": "Et natus.",
- "source": "http://localhost:3000/assemblies/smile-trivial/f/23/meetings/23"
- }
- end
- let(:json) do
- {
- "containers" => [container_one, container_two],
- "contributions" => [contrib_one, contrib_two],
- "authors" => [author_one, author_two]
- }
+ before do
+ component.organization.enable_dataspace = true
+ component.organization.save!
end
- context "and there is one url in integration url" do
- before do
- component.update!(settings: { add_integration: true, integration_url: "http://example.org", preferred_locale: "en" })
- allow(GetDataFromApi).to receive(:data).and_return(json)
- end
-
- it "sorts proposals by search defaults and define external_proposals and other variables" do
+ context "and there are no external proposals" do
+ it "returns proposals" do
get :index
expect(response).to have_http_status(:ok)
expect(subject).to render_template(:index)
+ expect(assigns(:proposals).size).to eq(2)
expect(assigns(:proposals).order_values).to eq [Decidim::Proposals::Proposal.arel_table[Decidim::Proposals::Proposal.primary_key] * Arel.sql("RANDOM()")]
expect(assigns(:proposals).order_values.map(&:to_sql)).to eq ["\"decidim_proposals_proposals\".\"id\" * RANDOM()"]
- expect(assigns(:authors).count).to eq 2
- expect(assigns(:authors).first[:reference]).to eq "JD-MEET-2025-09-6"
- expect(assigns(:authors).last[:reference]).to eq "JD-MEET-2025-09-23"
- expect(assigns(:total_count)).to eq 4
- expect(assigns(:current_page)).to eq 1
- expect(assigns(:total_pages)).to eq 1
- expect(assigns(:proposals).count).to eq 2
- expect(assigns(:external_proposals).count).to eq 2
- expect(assigns(:external_proposals).first[:reference]).to eq "JD-PROP-2025-09-1"
- expect(assigns(:external_proposals).last[:reference]).to eq "JD-PROP-2025-09-20"
end
end
- context "and there are 2 urls in integration_url" do
- before do
- component.update!(settings: { add_integration: true, integration_url: "http://example.org, http://example.org,", preferred_locale: "en" })
- allow(GetDataFromApi).to receive(:data).and_return(json)
+ context "and there are externals proposals" do
+ let(:component) { create(:proposal_component) }
+ let!(:proposals) { create_list(:proposal, 2, component:) }
+ let(:contrib_one) do
+ { "reference": "JD-PROP-2025-09-1",
+ "source": "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
+ "container": "JD-PART-2025-09-1",
+ "locale": "en",
+ "title": "Test one",
+ "content": "Debitis repellat provident",
+ "authors": ["JD-MEET-2025-09-6"],
+ "created_at": "2025-09-11T10:20:21.222Z",
+ "updated_at": "2025-09-11T10:21:56.604Z",
+ "deleted_at": nil }
end
-
- it "returns 4 external proposals and 4 authors" do
- get :index
- expect(response).to have_http_status(:ok)
- expect(subject).to render_template(:index)
- expect(assigns(:external_proposals).count).to eq 4
- expect(assigns(:authors).count).to eq 4
+ let(:contrib_two) do
+ { "reference": "JD-PROP-2025-09-20",
+ "source": "http://localhost:3000/assemblies/smile-trivial/f/25/proposals/20",
+ "container": "JD-ASSE-2025-09-1",
+ "locale": "en",
+ "title": "Test two",
+ "content": "Non et vel",
+ "authors": ["JD-MEET-2025-09-23"],
+ "created_at": "2025-09-11T10:43:23.743Z",
+ "updated_at": "2025-09-11T10:43:27.147Z",
+ "deleted_at": nil }
+ end
+ let(:container_one) do
+ {
+ "reference": "JD-PART-2025-09-1",
+ "source": "http://localhost:3000/processes/satisfaction-hope",
+ "name": "Cupiditate natus dignissimos saepe ut.",
+ "description": "
Voluptas recusandae est. Nesciunt excepturi corrupti. Qui natus eligendi.
",
+ "metadata": {},
+ "created_at": "2025-09-11T10:14:58.111Z",
+ "updated_at": "2025-09-11T10:14:58.126Z",
+ "deleted_at": nil
+ }
+ end
+ let(:container_two) do
+ {
+ "reference": "JD-ASSE-2025-09-1",
+ "source": "http://localhost:3000/assemblies/smile-trivial",
+ "name": "Molestiae aut corporis quas et.",
+ "description": "
Ratione autem repellendus. Error voluptatem ipsam. Ut dicta velit.
",
+ "metadata": {},
+ "created_at": "2025-09-11T10:38:07.682Z",
+ "updated_at": "2025-09-11T10:38:07.682Z",
+ "deleted_at": nil
+ }
+ end
+ let(:author_one) do
+ {
+ "reference": "JD-MEET-2025-09-6",
+ "name": "Animi voluptatum.",
+ "source": "http://localhost:3000/processes/satisfaction-hope/f/5/meetings/6"
+ }
+ end
+ let(:author_two) do
+ {
+ "reference": "JD-MEET-2025-09-23",
+ "name": "Et natus.",
+ "source": "http://localhost:3000/assemblies/smile-trivial/f/23/meetings/23"
+ }
end
- end
- end
-
- it "sets two different collections" do
- geocoded_proposals = create_list(:proposal, 10, component:, latitude: 1.1, longitude: 2.2)
- non_geocoded_proposals = create_list(:proposal, 2, component:, latitude: nil, longitude: nil)
-
- get :index
- expect(response).to have_http_status(:ok)
- expect(subject).to render_template(:index)
- expect(assigns(:proposals).count).to eq 12
- expect(assigns(:proposals)).to match_array(geocoded_proposals + non_geocoded_proposals)
- end
- end
+ let(:json) do
+ {
+ "containers" => [container_one, container_two],
+ "contributions" => [contrib_one, contrib_two],
+ "authors" => [author_one, author_two]
+ }
+ end
- context "when participatory texts are enabled" do
- let(:component) { create(:proposal_component, :with_participatory_texts_enabled) }
+ context "and there is one url in integration url" do
+ before do
+ component.update!(settings: { add_integration: true, integration_url: "http://example.org", preferred_locale: "en" })
+ allow(GetDataFromApi).to receive(:data).and_return(json)
+ end
- it "sorts proposals by position" do
- get :index
- expect(response).to have_http_status(:ok)
- expect(subject).to render_template(:participatory_text)
- expect(assigns(:proposals).order_values.first.expr.name).to eq("position")
- end
+ it "sorts proposals by search defaults and define external_proposals and other variables" do
+ get :index
+ expect(response).to have_http_status(:ok)
+ expect(subject).to render_template(:index)
+ expect(assigns(:proposals).order_values).to eq [Decidim::Proposals::Proposal.arel_table[Decidim::Proposals::Proposal.primary_key] * Arel.sql("RANDOM()")]
+ expect(assigns(:proposals).order_values.map(&:to_sql)).to eq ["\"decidim_proposals_proposals\".\"id\" * RANDOM()"]
+ expect(assigns(:authors).count).to eq 2
+ expect(assigns(:authors).first[:reference]).to eq "JD-MEET-2025-09-6"
+ expect(assigns(:authors).last[:reference]).to eq "JD-MEET-2025-09-23"
+ expect(assigns(:total_count)).to eq 4
+ expect(assigns(:current_page)).to eq 1
+ expect(assigns(:total_pages)).to eq 1
+ expect(assigns(:proposals).count).to eq 2
+ expect(assigns(:external_proposals).count).to eq 2
+ expect(assigns(:external_proposals).first[:reference]).to eq "JD-PROP-2025-09-1"
+ expect(assigns(:external_proposals).last[:reference]).to eq "JD-PROP-2025-09-20"
+ end
+ end
- context "when emendations exist" do
- let!(:amendable) { create(:proposal, component:) }
- let!(:emendation) { create(:proposal, component:) }
- let!(:amendment) { create(:amendment, amendable:, emendation:, state: "accepted") }
+ context "and there are 2 urls in integration_url" do
+ before do
+ component.update!(settings: { add_integration: true, integration_url: "http://example.org, http://example.org,", preferred_locale: "en" })
+ allow(GetDataFromApi).to receive(:data).and_return(json)
+ end
- it "does not include emendations" do
- get :index
- expect(response).to have_http_status(:ok)
- emendations = assigns(:proposals).select(&:emendation?)
- expect(emendations).to be_empty
+ it "returns 4 external proposals and 4 authors" do
+ get :index
+ expect(response).to have_http_status(:ok)
+ expect(subject).to render_template(:index)
+ expect(assigns(:external_proposals).count).to eq 4
+ expect(assigns(:authors).count).to eq 4
+ end
+ end
end
end
end
@@ -279,14 +296,29 @@ module Proposals
allow(GetDataFromApi).to receive(:authors).and_return(authors)
end
- it "displays external_proposal view and sets variables" do
- get :external_proposal, params: { reference: "JD-PROP-2025-09-1", param: :reference, url: "http://example.org" }
- expect(response).to have_http_status(:ok)
- expect(subject).to render_template(:external_proposal)
- expect(assigns(:external_proposal)).to eq json_contrib
- expect(assigns(:comments)).to eq json_contrib["children"]
- expect(assigns(:parent_comments)).to eq(json_contrib["children"].select { |comment| comment["parent"] == json_contrib["reference"] })
- expect(assigns(:authors)).to eq "Et natus."
+ context "when dataspace is disabled" do
+ it "redirects to proposals index" do
+ get :external_proposal, params: { reference: "JD-PROP-2025-09-1", param: :reference, url: "http://example.org" }
+ expect(response).to have_http_status(:redirect)
+ expect(response).to redirect_to("/proposals")
+ end
+ end
+
+ context "when dataspace is enabled" do
+ before do
+ component.organization.enable_dataspace = true
+ component.organization.save!
+ end
+
+ it "displays external_proposal view and sets variables" do
+ get :external_proposal, params: { reference: "JD-PROP-2025-09-1", param: :reference, url: "http://example.org" }
+ expect(response).to have_http_status(:ok)
+ expect(subject).to render_template(:external_proposal)
+ expect(assigns(:external_proposal)).to eq json_contrib
+ expect(assigns(:comments)).to eq json_contrib["children"]
+ expect(assigns(:parent_comments)).to eq(json_contrib["children"].select { |comment| comment["parent"] == json_contrib["reference"] })
+ expect(assigns(:authors)).to eq "Et natus."
+ end
end
end
diff --git a/spec/factories.rb b/spec/factories.rb
index b607669..933a026 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -2,3 +2,85 @@
require "decidim/dataspace/test/factories"
require "decidim/proposals/test/factories"
+
+FactoryBot.modify do
+ factory :organization, class: "Decidim::Organization" do
+ transient do
+ skip_injection { false }
+ create_static_pages { true }
+ end
+
+ # we do not want machine translation here
+ name do
+ Decidim.available_locales.index_with { |_locale| Faker::Company.unique.name }
+ end
+
+ reference_prefix { Faker::Name.suffix }
+ time_zone { "UTC" }
+ twitter_handler { Faker::Hipster.word }
+ facebook_handler { Faker::Hipster.word }
+ instagram_handler { Faker::Hipster.word }
+ youtube_handler { Faker::Hipster.word }
+ github_handler { Faker::Hipster.word }
+ sequence(:host) { |n| "#{n}.lvh.me" }
+ description { generate_localized_description(:organization_description, skip_injection:) }
+ favicon { Decidim::Dev.test_file("icon.png", "image/png") }
+ default_locale { Decidim.default_locale }
+ available_locales { Decidim.available_locales }
+ users_registration_mode { :enabled }
+ official_img_footer { Decidim::Dev.test_file("avatar.jpg", "image/jpeg") }
+ official_url { Faker::Internet.url }
+ highlighted_content_banner_enabled { false }
+ enable_omnipresent_banner { false }
+ badges_enabled { true }
+ user_groups_enabled { true }
+ enable_dataspace { false }
+ send_welcome_notification { true }
+ comments_max_length { 1000 }
+ admin_terms_of_service_body { generate_localized_description(:admin_terms_of_service_body, skip_injection:) }
+ force_users_to_authenticate_before_access_organization { false }
+ machine_translation_display_priority { "original" }
+ external_domain_allowlist { ["example.org", "twitter.com", "facebook.com", "youtube.com", "github.com", "mytesturl.me"] }
+ smtp_settings do
+ {
+ "from" => "test@example.org",
+ "user_name" => "test",
+ "encrypted_password" => Decidim::AttributeEncryptor.encrypt("demo"),
+ "port" => "25",
+ "address" => "smtp.example.org"
+ }
+ end
+ file_upload_settings { Decidim::OrganizationSettings.default(:upload) }
+ enable_participatory_space_filters { true }
+ content_security_policy do
+ {
+ "default-src" => "localhost:* #{host}:*",
+ "script-src" => "localhost:* #{host}:*",
+ "style-src" => "localhost:* #{host}:*",
+ "img-src" => "localhost:* #{host}:*",
+ "font-src" => "localhost:* #{host}:*",
+ "connect-src" => "localhost:* #{host}:*",
+ "frame-src" => "localhost:* #{host}:* www.example.org",
+ "media-src" => "localhost:* #{host}:*"
+ }
+ end
+ colors do
+ {
+ primary: "#e02d2d",
+ secondary: "#155abf",
+ tertiary: "#ebc34b"
+ }
+ end
+
+ trait :secure_context do
+ host { "localhost" }
+ end
+
+ after(:create) do |organization, evaluator|
+ if evaluator.create_static_pages
+ tos_page = Decidim::StaticPage.find_by(slug: "terms-of-service", organization:)
+ create(:static_page, :tos, organization:, skip_injection: evaluator.skip_injection) if tos_page.nil?
+ end
+ end
+ end
+end
diff --git a/spec/system/admin_adds_integrations_on_proposals_spec.rb b/spec/system/admin_adds_integrations_on_proposals_spec.rb
index 2ab14b4..359d230 100644
--- a/spec/system/admin_adds_integrations_on_proposals_spec.rb
+++ b/spec/system/admin_adds_integrations_on_proposals_spec.rb
@@ -11,7 +11,7 @@
let!(:component) { create(:proposal_component, participatory_space: participatory_process) }
let(:admin) { create(:user, :admin, :confirmed, organization:) }
- context "when editing the proposals component" do
+ context "when dataspace is disabled" do
before do
switch_to_host(organization.host)
login_as admin, scope: :user
@@ -21,37 +21,61 @@
end
end
- context "and adding valid urls" do
- it "can adds multiple integrations and updates component" do
- # check add integration displays 2 divs
- check I18n.t("decidim.components.proposals.settings.global.add_integration")
- expect(page).to have_css("div.integration_url_container")
- expect(page).to have_css("div.preferred_locale_container")
- # provide valid urls, no error message displayed
- fill_in "component[settings][integration_url]", with: "http://example.com, http://localhost:3000"
- expect(page).to have_no_css("p.url_input_error")
- select("fr", from: "component[settings][preferred_locale]")
- # update component succesfully
- click_link_or_button "Update"
- expect(page).to have_content("The component was updated successfully.")
- end
+ it "doesn't display the fields related to it" do
+ expect(page).to have_no_css("div.add_integration_container")
+ expect(page).to have_no_css("div.integration_url_container")
+ expect(page).to have_no_css("div.preferred_locale_container")
+ end
+ end
+
+ context "when dataspace is enabled" do
+ before do
+ component.organization.enable_dataspace = true
+ component.organization.save!
end
- context "and adding invalid url" do
- it "gets an error message" do
- check I18n.t("decidim.components.proposals.settings.global.add_integration")
- # provide invalid url
- fill_in "component[settings][integration_url]", with: "http://localhost:3000, example.com"
- # error message displayed when input looses focus
- find_by_id("component_settings_preferred_locale").click
- sleep(1)
- expect(page).to have_css("p.url_input_error")
- # providing a good url removes the error
- fill_in "component[settings][integration_url]", with: "http://localhost:3000, http://example.com"
- # error message removed when input looses focus
- find_by_id("component_settings_preferred_locale").click
- sleep(1)
- expect(page).to have_no_css("p.url_input_error")
+ context "when editing the proposals component" do
+ before do
+ switch_to_host(organization.host)
+ login_as admin, scope: :user
+ visit decidim_admin_participatory_processes.components_path(participatory_process)
+ within ".component-#{component.id}" do
+ find("a[title='Configure']").click
+ end
+ end
+
+ context "and adding valid urls" do
+ it "can adds multiple integrations and updates component" do
+ # check add integration displays 2 divs
+ check I18n.t("decidim.components.proposals.settings.global.add_integration")
+ expect(page).to have_css("div.integration_url_container")
+ expect(page).to have_css("div.preferred_locale_container")
+ # provide valid urls, no error message displayed
+ fill_in "component[settings][integration_url]", with: "http://example.com, http://localhost:3000"
+ expect(page).to have_no_css("p.url_input_error")
+ select("fr", from: "component[settings][preferred_locale]")
+ # update component succesfully
+ click_link_or_button "Update"
+ expect(page).to have_content("The component was updated successfully.")
+ end
+ end
+
+ context "and adding invalid url" do
+ it "gets an error message" do
+ check I18n.t("decidim.components.proposals.settings.global.add_integration")
+ # provide invalid url
+ fill_in "component[settings][integration_url]", with: "http://localhost:3000, example.com"
+ # error message displayed when input looses focus
+ find_by_id("component_settings_preferred_locale").click
+ sleep(1)
+ expect(page).to have_css("p.url_input_error")
+ # providing a good url removes the error
+ fill_in "component[settings][integration_url]", with: "http://localhost:3000, http://example.com"
+ # error message removed when input looses focus
+ find_by_id("component_settings_preferred_locale").click
+ sleep(1)
+ expect(page).to have_no_css("p.url_input_error")
+ end
end
end
end
diff --git a/spec/system/external_proposal_spec.rb b/spec/system/external_proposal_spec.rb
index fd3869a..3ad60dc 100644
--- a/spec/system/external_proposal_spec.rb
+++ b/spec/system/external_proposal_spec.rb
@@ -9,6 +9,7 @@
let!(:manifest_name) { "proposals" }
let(:participatory_process) { create(:participatory_process, organization:) }
let!(:component) { create(:proposal_component, participatory_space: participatory_process) }
+ let!(:proposals) { create_list(:proposal, 3, component:) }
let(:authors) do
[
@@ -35,125 +36,141 @@
]
end
- before do
- component.update!(settings: { add_integration: true, integration_url: "http://localhost:3000", preferred_locale: "en" })
- allow(GetDataFromApi).to receive(:contribution).and_return(json_contrib)
- allow(GetDataFromApi).to receive(:authors).and_return(authors)
- visit_external_proposal
+ context "when dataspace is disabled" do
+ before do
+ component.update!(settings: { add_integration: true, integration_url: "http://localhost:3000", preferred_locale: "en" })
+ visit_external_proposal
+ end
+
+ it "redirects to proposals index page" do
+ expect(page).to have_css("h1#proposals-count", text: "Proposals")
+ expect(page).to have_css("h2", text: "3 proposals")
+ end
end
- context "when the external proposal has no comments" do
- let(:json_contrib) do
- {
- "reference" => "JD-PROP-2025-09-1",
- "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
- "container" => "JD-PART-2025-09-1",
- "locale" => "en",
- "title" => "Quia sapiente.",
- "content" => "Debitis repellat provident. Earum dolorem eaque. Aut quia officiis.",
- "authors" => [
- "Aldo Davis"
- ],
- "parent" => nil,
- "children" => nil,
- "metadata" => {
- "state" => {
- "withdrawn" => false,
- "emendation" => false,
- "state" => "accepted"
- }
- },
- "created_at" => "2025-09-11T10:20:21.222Z",
- "updated_at" => "2025-09-11T10:21:56.604Z",
- "deleted_at" => nil
- }
+ context "when dataspace is enabled" do
+ before do
+ component.organization.enable_dataspace = true
+ component.organization.save!
+ component.update!(settings: { add_integration: true, integration_url: "http://localhost:3000", preferred_locale: "en" })
+ allow(GetDataFromApi).to receive(:contribution).and_return(json_contrib)
+ allow(GetDataFromApi).to receive(:authors).and_return(authors)
+ visit_external_proposal
end
- it "displays only external proposal" do
- expect(page).to have_css("h1", text: "Quia sapiente.")
- expect(page).to have_css("p.author_name", text: "Aldo Davis")
- expect(page).to have_css("div.rich-text-display", text: json_contrib["content"])
- # check status is displayed
- expect(page).to have_content("Accepted")
- # check there is no comments
- expect(page).to have_no_css("div#comments")
+ context "and the external proposal has no comments" do
+ let(:json_contrib) do
+ {
+ "reference" => "JD-PROP-2025-09-1",
+ "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
+ "container" => "JD-PART-2025-09-1",
+ "locale" => "en",
+ "title" => "Quia sapiente.",
+ "content" => "Debitis repellat provident. Earum dolorem eaque. Aut quia officiis.",
+ "authors" => [
+ "Aldo Davis"
+ ],
+ "parent" => nil,
+ "children" => nil,
+ "metadata" => {
+ "state" => {
+ "withdrawn" => false,
+ "emendation" => false,
+ "state" => "accepted"
+ }
+ },
+ "created_at" => "2025-09-11T10:20:21.222Z",
+ "updated_at" => "2025-09-11T10:21:56.604Z",
+ "deleted_at" => nil
+ }
+ end
+
+ it "displays only external proposal" do
+ expect(page).to have_css("h1", text: "Quia sapiente.")
+ expect(page).to have_css("p.author_name", text: "Aldo Davis")
+ expect(page).to have_css("div.rich-text-display", text: json_contrib["content"])
+ # check status is displayed
+ expect(page).to have_content("Accepted")
+ # check there is no comments
+ expect(page).to have_no_css("div#comments")
+ end
end
- end
- context "when the external proposal has comments" do
- let(:json_contrib) do
- {
- "reference" => "JD-PROP-2025-09-1",
- "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
- "container" => "JD-PART-2025-09-1",
- "locale" => "en",
- "title" => "Quia sapiente.",
- "content" => "Debitis repellat provident. Earum dolorem eaque. Aut quia officiis.",
- "authors" => [
- "Aldo Davis"
- ],
- "parent" => nil,
- "children" => [
- {
- "reference" => "JD-PROP-2025-09-1-249",
- "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
- "container" => "JD-PART-2025-09-1",
- "locale" => "en",
- "title" => nil,
- "content" => "Cumque hic quia veniam et dolores aliquam commodi laudantium omnis expedita enim natus et beatae quidem dolores architecto repudiandae rem a corporis impedit rerum fugit neque eos dicta deserunt consequatur numquam magnam voluptate inventore omnis aut porro nemo voluptas sit quia saepe aut provident accusantium voluptatem illum nam quaerat molestiae.",
- "authors" => "Kautzer-Mayer",
- "parent" => "JD-PROP-2025-09-1",
- "children" => [
- "JD-PROP-2025-09-1-250"
- ],
- "metadata" => {
- "depth" => 0
+ context "and the external proposal has comments" do
+ let(:json_contrib) do
+ {
+ "reference" => "JD-PROP-2025-09-1",
+ "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
+ "container" => "JD-PART-2025-09-1",
+ "locale" => "en",
+ "title" => "Quia sapiente.",
+ "content" => "Debitis repellat provident. Earum dolorem eaque. Aut quia officiis.",
+ "authors" => [
+ "Aldo Davis"
+ ],
+ "parent" => nil,
+ "children" => [
+ {
+ "reference" => "JD-PROP-2025-09-1-249",
+ "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
+ "container" => "JD-PART-2025-09-1",
+ "locale" => "en",
+ "title" => nil,
+ "content" => "Cumque hic quia veniam et dolores aliquam commodi laudantium omnis expedita enim natus et beatae quidem dolores architecto repudiandae rem a corporis impedit rerum fugit neque eos dicta deserunt consequatur numquam magnam voluptate inventore omnis aut porro nemo voluptas sit quia saepe aut provident accusantium voluptatem illum nam quaerat molestiae.",
+ "authors" => "Kautzer-Mayer",
+ "parent" => "JD-PROP-2025-09-1",
+ "children" => [
+ "JD-PROP-2025-09-1-250"
+ ],
+ "metadata" => {
+ "depth" => 0
+ },
+ "created_at" => "2025-09-11T10:20:23.609Z",
+ "updated_at" => "2025-09-11T10:20:23.609Z",
+ "deleted_at" => nil
},
- "created_at" => "2025-09-11T10:20:23.609Z",
- "updated_at" => "2025-09-11T10:20:23.609Z",
- "deleted_at" => nil
+ {
+ "reference" => "JD-PROP-2025-09-1-250",
+ "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
+ "container" => "JD-PART-2025-09-1",
+ "locale" => "en",
+ "title" => nil,
+ "content" => "Voluptatem illum sit eius eligendi omnis dolore qui alias et occaecati eos ipsum blanditiis unde fugit minus est quia excepturi eos ut nam iste molestias cupiditate et vel repellat quidem qui non est porro commodi quia mollitia reiciendis odit rem voluptas tempora autem et sequi quos provident accusantium fugiat accusamus.",
+ "authors" => "Aldo Davis",
+ "parent" => "JD-PROP-2025-09-1-249",
+ "children" => nil,
+ "metadata" => {
+ "depth" => 1
+ },
+ "created_at" => "2025-09-11T10:20:24.655Z",
+ "updated_at" => "2025-09-11T10:20:24.655Z",
+ "deleted_at" => nil
+ }
+ ],
+ "metadata" => {
+ "state" => {
+ "withdrawn" => false,
+ "emendation" => false,
+ "state" => nil
+ }
},
- {
- "reference" => "JD-PROP-2025-09-1-250",
- "source" => "http://localhost:3000/processes/satisfaction-hope/f/7/proposals/1",
- "container" => "JD-PART-2025-09-1",
- "locale" => "en",
- "title" => nil,
- "content" => "Voluptatem illum sit eius eligendi omnis dolore qui alias et occaecati eos ipsum blanditiis unde fugit minus est quia excepturi eos ut nam iste molestias cupiditate et vel repellat quidem qui non est porro commodi quia mollitia reiciendis odit rem voluptas tempora autem et sequi quos provident accusantium fugiat accusamus.",
- "authors" => "Aldo Davis",
- "parent" => "JD-PROP-2025-09-1-249",
- "children" => nil,
- "metadata" => {
- "depth" => 1
- },
- "created_at" => "2025-09-11T10:20:24.655Z",
- "updated_at" => "2025-09-11T10:20:24.655Z",
- "deleted_at" => nil
- }
- ],
- "metadata" => {
- "state" => {
- "withdrawn" => false,
- "emendation" => false,
- "state" => nil
- }
- },
- "created_at" => "2025-09-11T10:20:21.222Z",
- "updated_at" => "2025-09-11T10:21:56.604Z",
- "deleted_at" => nil
- }
- end
+ "created_at" => "2025-09-11T10:20:21.222Z",
+ "updated_at" => "2025-09-11T10:21:56.604Z",
+ "deleted_at" => nil
+ }
+ end
- it "displays external proposal with its comments" do
- expect(page).to have_css("h1", text: "Quia sapiente.")
- expect(page).to have_css("p.author_name", text: "Aldo Davis")
- expect(page).to have_css("div.rich-text-display", text: json_contrib["content"])
- # check comments are displayed
- expect(page).to have_css("div#comments")
- expect(page).to have_css("span.comments-count", text: "2 comments")
- within "div.comment-thread" do
- expect(page).to have_css("div#comment_JD-PROP-2025-09-1-249")
- expect(page).to have_css("div#accordion-JD-PROP-2025-09-1-249")
+ it "displays external proposal with its comments" do
+ expect(page).to have_css("h1", text: "Quia sapiente.")
+ expect(page).to have_css("p.author_name", text: "Aldo Davis")
+ expect(page).to have_css("div.rich-text-display", text: json_contrib["content"])
+ # check comments are displayed
+ expect(page).to have_css("div#comments")
+ expect(page).to have_css("span.comments-count", text: "2 comments")
+ within "div.comment-thread" do
+ expect(page).to have_css("div#comment_JD-PROP-2025-09-1-249")
+ expect(page).to have_css("div#accordion-JD-PROP-2025-09-1-249")
+ end
end
end
end
diff --git a/spec/system/proposals_index_spec.rb b/spec/system/proposals_index_spec.rb
index 7236c38..40aa825 100644
--- a/spec/system/proposals_index_spec.rb
+++ b/spec/system/proposals_index_spec.rb
@@ -32,7 +32,7 @@
end
end
- context "when there is no external proposals" do
+ context "and there is no external proposals" do
it "lists all the proposals" do
create(:proposal_component,
manifest:,
@@ -125,60 +125,86 @@
allow(GetDataFromApi).to receive(:data).and_return(json)
end
- context "and there is one url in integration_url" do
- before do
- component.update!(settings: { add_integration: true, integration_url: "http://example.org", preferred_locale: "en" })
+ context "and dataspace is disabled" do
+ context "and there is one url in integration_url" do
+ before do
+ component.update!(settings: { add_integration: true, integration_url: "http://example.org", preferred_locale: "en" })
+ end
+
+ it "lists only the proposals" do
+ visit_component
+ # 3 cards
+ expect(page).to have_css("a[class='card__list']", count: 3)
+ # 3 proposals
+ expect(page).to have_css("[id^='proposals__proposal']", count: 3)
+ # no external proposals
+ expect(page).to have_no_css("[id='JD-PROP-2025-09-1']")
+ expect(page).to have_no_css("[id='JD-PROP-2025-09-20']")
+ end
end
+ end
- it "lists the proposals ordered randomly by default and the external proposals at the end" 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)
- # 2 external proposals
- 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)
+ context "and dataspace is enabled" do
+ before do
+ component.organization.enable_dataspace = true
+ component.organization.save!
end
- context "and there are a lot of proposals" do
+ context "and there is one url in integration_url" do
before do
- # Decidim::Paginable::OPTIONS.first is 25
- create_list(:proposal, Decidim::Paginable::OPTIONS.first, component:)
- # we have already create 3 proposals, so we will have a total of 28 proposals
+ component.update!(settings: { add_integration: true, integration_url: "http://example.org", preferred_locale: "en" })
end
- it "paginates them with proposals first and external proposals at the end" do
+ it "lists the proposals and the external proposals" do
visit_component
- # only proposals on first page
- expect(page).to have_css("a[class='card__list']", count: Decidim::Paginable::OPTIONS.first)
- expect(page).to have_css("[id^='proposals__proposal']", count: Decidim::Paginable::OPTIONS.first)
-
- click_on "Next"
- # proposals and external proposals on second page
+ # 5 cards
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")
+ # 3 proposals
expect(page).to have_css("[id^='proposals__proposal']", count: 3)
+ # 2 external proposals
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
- end
- end
- context "and there are 2 urls in integration_url" do
- before do
- component.update!(settings: { add_integration: true, integration_url: "http://example.org, http://example.org", preferred_locale: "en" })
+ context "and there are a lot of proposals" do
+ before do
+ # Decidim::Paginable::OPTIONS.first is 25
+ create_list(:proposal, Decidim::Paginable::OPTIONS.first, component:)
+ # we have already create 3 proposals, so we will have a total of 28 proposals
+ end
+
+ it "paginates them with proposals first and external proposals at the end" do
+ visit_component
+ # only proposals on first page
+ expect(page).to have_css("a[class='card__list']", count: Decidim::Paginable::OPTIONS.first)
+ expect(page).to have_css("[id^='proposals__proposal']", count: Decidim::Paginable::OPTIONS.first)
+
+ 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("[id='JD-PROP-2025-09-1']", count: 1)
+ expect(page).to have_css("[id='JD-PROP-2025-09-20']", count: 1)
+ end
+ end
end
- 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)
- # 4 external proposals
- 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)
+ context "and there are 2 urls in integration_url" do
+ before do
+ component.update!(settings: { add_integration: true, integration_url: "http://example.org, http://example.org", preferred_locale: "en" })
+ end
+
+ 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)
+ # 4 external proposals
+ 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
end
end
end