From 1c7cfd13fd7a3800cf796de710a4dc2b04e7f956 Mon Sep 17 00:00:00 2001 From: stephanie rousset Date: Wed, 3 Dec 2025 09:30:42 +0100 Subject: [PATCH 1/7] feat: add proposals controller extends to fix proposals order in index --- config/application.rb | 1 + .../proposals/proposals_controller_extends.rb | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb diff --git a/config/application.rb b/config/application.rb index 112535d45b..16b8fd27f2 100644 --- a/config/application.rb +++ b/config/application.rb @@ -40,6 +40,7 @@ class Application < Rails::Application require "extends/controllers/decidim/participatory_processes/participatory_processes_controller_extends" require "extends/controllers/decidim/budgets/projects_controller_extends" require "extends/controllers/decidim/proposals/admin/proposal_states_controller_extends" + require "extends/controllers/decidim/proposals/proposals_controller_extends" # helpers require "extends/helpers/decidim/check_boxes_tree_helper_extends" require "extends/helpers/decidim/omniauth_helper_extends" diff --git a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb new file mode 100644 index 0000000000..91522728f9 --- /dev/null +++ b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "active_support/concern" + +module Decidim + module Proposals + module ProposalsControllerExtends + extend ActiveSupport::Concern + + included do + def index + if component_settings.participatory_texts_enabled? + @proposals = Decidim::Proposals::Proposal + .where(component: current_component) + .published + .not_hidden + .only_amendables + .includes(:category, :scope, :attachments, :coauthorships) + .order(position: :asc) + render "decidim/proposals/proposals/participatory_texts/participatory_text" + else + @proposals = search.result + @proposals = reorder(@proposals) + ids = @proposals.ids + @proposals = Decidim::Proposals::Proposal.where(id: ids) + .order(Arel.sql("position(decidim_proposals_proposals.id::text in '#{ids.join(",")}')")) + .page(params[:page]) + .per(per_page) + @proposals = @proposals.includes(:component, :coauthorships, :attachments) + + @voted_proposals = if current_user + ProposalVote.where( + author: current_user, + proposal: @proposals.pluck(:id) + ).pluck(:decidim_proposal_id) + else + [] + end + end + end + end + end + end +end + +Decidim::Proposals::ProposalsController.include(Decidim::Proposals::ProposalsControllerExtends) + From 9a8a885e465b2511adb942f7c2ff87a50ad8f678 Mon Sep 17 00:00:00 2001 From: stephanie rousset Date: Wed, 3 Dec 2025 09:31:39 +0100 Subject: [PATCH 2/7] test: add tests for proposal controller and system test for proposals index --- spec/controllers/proposals_controller_spec.rb | 76 ++++ spec/system/proposals_index_spec.rb | 409 ++++++++++++++++++ 2 files changed, 485 insertions(+) create mode 100644 spec/controllers/proposals_controller_spec.rb create mode 100644 spec/system/proposals_index_spec.rb diff --git a/spec/controllers/proposals_controller_spec.rb b/spec/controllers/proposals_controller_spec.rb new file mode 100644 index 0000000000..99a11805b7 --- /dev/null +++ b/spec/controllers/proposals_controller_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + module Proposals + describe ProposalsController do + routes { Decidim::Proposals::Engine.routes } + + let(:user) { create(:user, :confirmed, organization: component.organization) } + + let(:proposal_params) do + { + component_id: component.id + } + end + let(:params) { { proposal: proposal_params } } + + before do + request.env["decidim.current_organization"] = component.organization + request.env["decidim.current_participatory_space"] = component.participatory_space + request.env["decidim.current_component"] = component + stub_const("Decidim::Paginable::OPTIONS", [100]) + end + + describe "GET index" do + context "when participatory texts are disabled" do + let(:component) { create(:proposal_component, :with_geocoding_enabled) } + + it "sorts proposals by search defaults" do + get :index + expect(response).to have_http_status(:ok) + expect(subject).to render_template(:index) + expect(assigns(:proposals).order_values).to eq(["position(decidim_proposals_proposals.id::text in '')"]) + 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 + + 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 + end + end + end +end diff --git a/spec/system/proposals_index_spec.rb b/spec/system/proposals_index_spec.rb new file mode 100644 index 0000000000..d8bb0d4a5c --- /dev/null +++ b/spec/system/proposals_index_spec.rb @@ -0,0 +1,409 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "Proposals" do + include ActionView::Helpers::TextHelper + include_context "with a component" + let(:manifest_name) { "proposals" } + + let!(:category) { create(:category, participatory_space: participatory_process) } + let!(:scope) { create(:scope, organization:) } + let!(:user) { create(:user, :confirmed, organization:) } + let(:scoped_participatory_process) { create(:participatory_process, :with_steps, organization:, scope:) } + + let(:address) { "Some address" } + let(:latitude) { 40.1234 } + let(:longitude) { 2.1234 } + + let(:proposal_title) { translated(proposal.title) } + + before do + stub_geocoding(address, [latitude, longitude]) + end + + matcher :have_author do |name| + match { |node| node.has_selector?("[data-author]", text: name) } + match_when_negated { |node| node.has_no_selector?("[data-author]", text: name) } + end + + matcher :have_creation_date do |date| + match { |node| node.has_selector?(".author-data__extra", text: date) } + match_when_negated { |node| node.has_no_selector?(".author-data__extra", text: date) } + end + + context "when listing proposals in a participatory process" do + shared_examples_for "a random proposal ordering" do + let!(:lucky_proposal) { create(:proposal, component:) } + let!(:unlucky_proposal) { create(:proposal, component:) } + let!(:lucky_proposal_title) { translated(lucky_proposal.title) } + let!(:unlucky_proposal_title) { translated(unlucky_proposal.title) } + + it "lists the proposals ordered randomly by default" do + visit_component + + expect(page).to have_css("a", text: "Random") + expect(page).to have_css("[id^='proposals__proposal']", count: 2) + expect(page).to have_css("[id^='proposals__proposal']", text: lucky_proposal_title) + expect(page).to have_css("[id^='proposals__proposal']", text: unlucky_proposal_title) + expect(page).to have_author(lucky_proposal.creator_author.name) + end + end + + context "when maps are enabled" do + let(:component) { create(:proposal_component, :with_geocoding_enabled, participatory_space: participatory_process) } + + let!(:author_proposals) { create_list(:proposal, 2, :participant_author, :published, component:) } + let!(:group_proposals) { create_list(:proposal, 2, :user_group_author, :published, component:) } + let!(:official_proposals) { create_list(:proposal, 2, :official, :published, component:) } + + # We are providing a list of coordinates to make sure the points are scattered all over the map + # otherwise, there is a chance that markers can be clustered, which may result in a flaky spec. + before do + coordinates = [ + [-95.501705376541395, 95.10059236654689], + [-95.501705376541395, -95.10059236654689], + [95.10059236654689, -95.501705376541395], + [95.10059236654689, 95.10059236654689], + [142.15275006889419, -33.33377235135252], + [33.33377235135252, -142.15275006889419], + [-33.33377235135252, 142.15275006889419], + [-142.15275006889419, 33.33377235135252], + [-55.28745034772282, -35.587843900166945] + ] + Decidim::Proposals::Proposal.where(component:).geocoded.each_with_index do |proposal, index| + proposal.update!(latitude: coordinates[index][0], longitude: coordinates[index][1]) if coordinates[index] + end + + visit_component + end + + it "shows markers for selected proposals" do + expect(page).to have_css(".leaflet-marker-icon", count: 5) + within "#panel-dropdown-menu-origin" do + click_filter_item "Official" + end + expect(page).to have_css(".leaflet-marker-icon", count: 2) + + expect_no_js_errors + end + end + + it_behaves_like "accessible page" do + before { visit_component } + end + + it "lists all the proposals" do + create(:proposal_component, + manifest:, + participatory_space: participatory_process) + + create_list(:proposal, 3, component:) + + visit_component + expect(page).to have_css("[id^='proposals__proposal']", count: 3) + end + + describe "editable content" do + it_behaves_like "editable content for admins" do + let(:target_path) { main_component_path(component) } + end + end + + context "when comments have been moderated" do + let(:proposal) { create(:proposal, component:) } + let(:author) { create(:user, :confirmed, organization: component.organization) } + let!(:comments) { create_list(:comment, 3, commentable: proposal) } + let!(:moderation) { create(:moderation, reportable: comments.first, hidden_at: 1.day.ago) } + + it "displays unhidden comments count" do + visit_component + + within("#proposals__proposal_#{proposal.id}") do + within(".card__list-metadata") do + expect(page).to have_css("div", text: 2) + end + end + end + end + + describe "default ordering" do + it_behaves_like "a random proposal ordering" + end + + context "when voting phase is over" do + let!(:component) do + create(:proposal_component, + :with_votes_blocked, + manifest:, + participatory_space: participatory_process) + end + + let!(:most_voted_proposal) do + proposal = create(:proposal, component:) + create_list(:proposal_vote, 3, proposal:) + proposal + end + let!(:most_voted_proposal_title) { translated(most_voted_proposal.title) } + + let!(:less_voted_proposal) { create(:proposal, component:) } + let!(:less_voted_proposal_title) { translated(less_voted_proposal.title) } + + before { visit_component } + + it "lists the proposals ordered by votes by default" do + expect(page).to have_css("a", text: "Most voted") + expect(page).to have_css("[id^='proposals__proposal']:first-child", text: most_voted_proposal_title) + expect(page).to have_css("[id^='proposals__proposal']:last-child", text: less_voted_proposal_title) + end + end + + context "when voting is disabled" do + let!(:component) do + create(:proposal_component, + :with_votes_disabled, + :with_proposal_limit, + manifest:, + participatory_space: participatory_process) + end + + describe "order" do + it_behaves_like "a random proposal ordering" + end + + it "shows only links to full proposals" do + create_list(:proposal, 2, component:) + + visit_component + + expect(page).to have_css("[id^='proposals__proposal']", count: 2) + end + end + + context "when there are a lot of proposals" do + before do + create_list(:proposal, Decidim::Paginable::OPTIONS.first + 5, component:) + end + + it "paginates them" do + visit_component + + expect(page).to have_css("[id^='proposals__proposal']", count: Decidim::Paginable::OPTIONS.first) + + click_on "Next" + + expect(page).to have_css("[data-pages] [data-page][aria-current='page']", text: "2") + + expect(page).to have_css("[id^='proposals__proposal']", count: 5) + end + end + + shared_examples "ordering proposals by selected option" do |selected_option| + let(:first_proposal_title) { translated(first_proposal.title) } + let(:last_proposal_title) { translated(last_proposal.title) } + before do + visit_component + within ".order-by" do + expect(page).to have_css("div.order-by a", text: "Random") + page.find("a", text: "Random").click + click_on(selected_option) + end + end + + it "lists the proposals ordered by selected option" do + expect(page).to have_css("[id^='proposals__proposal']:first-child", text: first_proposal_title) + expect(page).to have_css("[id^='proposals__proposal']:last-child", text: last_proposal_title) + end + end + + context "when ordering by 'most_voted'" do + let!(:component) do + create(:proposal_component, + :with_votes_enabled, + manifest:, + participatory_space: participatory_process) + end + let!(:most_voted_proposal) { create(:proposal, component:) } + let!(:votes) { create_list(:proposal_vote, 3, proposal: most_voted_proposal) } + let!(:less_voted_proposal) { create(:proposal, component:) } + + it_behaves_like "ordering proposals by selected option", "Most voted" do + let(:first_proposal) { most_voted_proposal } + let(:last_proposal) { less_voted_proposal } + end + end + + context "when ordering by 'recent'" do + let!(:older_proposal) { create(:proposal, component:, created_at: 1.month.ago) } + let!(:recent_proposal) { create(:proposal, component:) } + + it_behaves_like "ordering proposals by selected option", "Recent" do + let(:first_proposal) { recent_proposal } + let(:last_proposal) { older_proposal } + end + end + + context "when ordering by 'most_followed'" do + let!(:most_followed_proposal) { create(:proposal, component:) } + let!(:follows) { create_list(:follow, 3, followable: most_followed_proposal) } + let!(:less_followed_proposal) { create(:proposal, component:) } + + it_behaves_like "ordering proposals by selected option", "Most followed" do + let(:first_proposal) { most_followed_proposal } + let(:last_proposal) { less_followed_proposal } + end + end + + context "when ordering by 'most_commented'" do + let!(:most_commented_proposal) { create(:proposal, component:, created_at: 1.month.ago) } + let!(:comments) { create_list(:comment, 3, commentable: most_commented_proposal) } + let!(:less_commented_proposal) { create(:proposal, component:) } + + it_behaves_like "ordering proposals by selected option", "Most commented" do + let(:first_proposal) { most_commented_proposal } + let(:last_proposal) { less_commented_proposal } + end + end + + context "when ordering by 'most_endorsed'" do + let!(:most_endorsed_proposal) { create(:proposal, component:, created_at: 1.month.ago) } + let!(:endorsements) do + 3.times.collect do + create(:endorsement, resource: most_endorsed_proposal, author: build(:user, organization:)) + end + end + let!(:less_endorsed_proposal) { create(:proposal, component:) } + + it_behaves_like "ordering proposals by selected option", "Most endorsed" do + let(:first_proposal) { most_endorsed_proposal } + let(:last_proposal) { less_endorsed_proposal } + end + end + + context "when ordering by 'with_more_authors'" do + let!(:most_authored_proposal) { create(:proposal, component:, created_at: 1.month.ago) } + let!(:coauthorships) { create_list(:coauthorship, 3, coauthorable: most_authored_proposal) } + let!(:less_authored_proposal) { create(:proposal, component:) } + + it_behaves_like "ordering proposals by selected option", "With more authors" do + let(:first_proposal) { most_authored_proposal } + let(:last_proposal) { less_authored_proposal } + end + end + + context "when searching proposals" do + let!(:proposals) do + [ + create(:proposal, title: "Lorem ipsum dolor sit amet", component:), + create(:proposal, title: "Donec vitae convallis augue", component:), + create(:proposal, title: "Pellentesque habitant morbi", component:) + ] + end + + before do + visit_component + end + + it "finds the correct proposal" do + within "form.new_filter" do + find("input[name='filter[search_text_cont]']", match: :first).set("lorem") + find("*[type=submit]").click + end + + expect(page).to have_content("Lorem ipsum dolor sit amet") + end + end + + context "when paginating" do + let!(:collection) { create_list(:proposal, collection_size, component:) } + let!(:resource_selector) { "[id^='proposals__proposal']" } + + it_behaves_like "a paginated resource" + end + + context "when component is not commentable" do + let!(:resources) { create_list(:proposal, 3, component:) } + + it_behaves_like "an uncommentable component" + end + end + + describe "viewing mode for proposals" do + let!(:proposal) { create(:proposal, :evaluating, component:) } + + context "when participants interact with the proposal view" do + it "provides an option for toggling between list and grid views" do + visit_component + expect(page).to have_css("use[href*='layout-grid-fill']") + expect(page).to have_css("use[href*='list-check']") + end + end + + context "when participants are viewing a grid of proposals" do + it "shows a grid of proposals with images" do + visit_component + + # Check that grid view is not the default + expect(page).to have_no_css(".card__grid-grid") + + # Switch to grid view + find("a[href*='view_mode=grid']").click + expect(page).to have_css(".card__grid-grid") + expect(page).to have_css(".card__grid-img img, .card__grid-img svg") + + # Revisit the component and check session storage + visit_component + expect(page).to have_css(".card__grid-grid") + end + end + + context "when participants are filtering proposals" do + let!(:evaluating_proposals) { create_list(:proposal, 3, :evaluating, component:) } + let!(:accepted_proposals) { create_list(:proposal, 5, :accepted, component:) } + + it "filters the proposals and keeps the filter when changing the view mode" do + visit_component + uncheck "Evaluating" + + expect(page).to have_css("[id^='proposals__proposal']", count: 5) + + find("a[href*='view_mode=grid']").click + + expect(page).to have_css(".card__grid-img svg#ri-proposal-placeholder-card-g", count: 5) + expect(page).to have_css("[id^='proposals__proposal']", count: 5) + end + end + + context "when participants are viewing a list of proposals" do + it "shows a list of proposals" do + visit_component + find("a[href*='view_mode=list']").click + expect(page).to have_css(".card__list-list") + end + end + + context "when proposals does not have attachments" do + it "shows a placeholder image" do + visit_component + find("a[href*='view_mode=grid']").click + expect(page).to have_css(".card__grid-img svg#ri-proposal-placeholder-card-g") + end + end + + context "when proposals have attachments" do + let!(:proposal) { create(:proposal, component:) } + let!(:attachment) { create(:attachment, attached_to: proposal) } + + before do + component.update!(settings: { attachments_allowed: true }) + end + + it "shows the proposal image" do + visit_component + + expect(page).to have_no_css(".card__grid-img img[src*='proposal_image_placeholder.svg']") + expect(page).to have_css(".card__grid-img img") + end + end + end +end From adc31316e734178bf33d4ce8619bfae904d31a19 Mon Sep 17 00:00:00 2001 From: stephanie rousset Date: Wed, 3 Dec 2025 09:33:00 +0100 Subject: [PATCH 3/7] style: refacto with rubocop --- .../proposals/proposals_controller_extends.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb index 91522728f9..51bb388731 100644 --- a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb +++ b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb @@ -11,12 +11,12 @@ module ProposalsControllerExtends def index if component_settings.participatory_texts_enabled? @proposals = Decidim::Proposals::Proposal - .where(component: current_component) - .published - .not_hidden - .only_amendables - .includes(:category, :scope, :attachments, :coauthorships) - .order(position: :asc) + .where(component: current_component) + .published + .not_hidden + .only_amendables + .includes(:category, :scope, :attachments, :coauthorships) + .order(position: :asc) render "decidim/proposals/proposals/participatory_texts/participatory_text" else @proposals = search.result @@ -44,4 +44,3 @@ def index end Decidim::Proposals::ProposalsController.include(Decidim::Proposals::ProposalsControllerExtends) - From dbc319e5b81057bff0c8c8c24d20019860405e60 Mon Sep 17 00:00:00 2001 From: stephanie rousset Date: Wed, 3 Dec 2025 14:30:51 +0100 Subject: [PATCH 4/7] test: update system test --- spec/system/proposals_index_spec.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/system/proposals_index_spec.rb b/spec/system/proposals_index_spec.rb index d8bb0d4a5c..c74dab49e7 100644 --- a/spec/system/proposals_index_spec.rb +++ b/spec/system/proposals_index_spec.rb @@ -83,9 +83,9 @@ within "#panel-dropdown-menu-origin" do click_filter_item "Official" end - expect(page).to have_css(".leaflet-marker-icon", count: 2) - - expect_no_js_errors + # make the page reload + visit current_path + "?" + URI.parse(current_url).query.to_s + expect(page).to have_css(".leaflet-marker-icon", count: 2, wait: 10) end end @@ -189,12 +189,15 @@ visit_component expect(page).to have_css("[id^='proposals__proposal']", count: Decidim::Paginable::OPTIONS.first) - + texts = page.all("[id^='proposals__proposal']").map(&:text) click_on "Next" expect(page).to have_css("[data-pages] [data-page][aria-current='page']", text: "2") expect(page).to have_css("[id^='proposals__proposal']", count: 5) + click_on "Prev" + # check elements on page one are still the same + expect(page.all("[id^='proposals__proposal']").map(&:text)).to eq(texts) end end From 9070bff497c2f2163e8e05cd29ffede8ffc728b2 Mon Sep 17 00:00:00 2001 From: stephanie rousset Date: Wed, 3 Dec 2025 14:41:36 +0100 Subject: [PATCH 5/7] test: refacto with rubocop --- spec/system/proposals_index_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/system/proposals_index_spec.rb b/spec/system/proposals_index_spec.rb index c74dab49e7..94b2a3bd70 100644 --- a/spec/system/proposals_index_spec.rb +++ b/spec/system/proposals_index_spec.rb @@ -84,7 +84,7 @@ click_filter_item "Official" end # make the page reload - visit current_path + "?" + URI.parse(current_url).query.to_s + visit "#{current_path}?#{URI.parse(current_url).query}" expect(page).to have_css(".leaflet-marker-icon", count: 2, wait: 10) end end From 451ad8f60abf5ba78bbcb5601e6aca95d40b3ff9 Mon Sep 17 00:00:00 2001 From: stephanie rousset Date: Tue, 6 Jan 2026 14:20:07 +0100 Subject: [PATCH 6/7] fix: update filter order in default_filter_params --- .../proposals/proposals_controller_extends.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb index 51bb388731..e760b213f9 100644 --- a/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb +++ b/lib/extends/controllers/decidim/proposals/proposals_controller_extends.rb @@ -38,6 +38,21 @@ def index end end end + + private + + def default_filter_params + { + activity: "all", + related_to: "", + search_text_cont: "", + type: "all", + with_any_category: nil, + with_any_origin: nil, + with_any_scope: nil, + with_any_state: default_states + } + end end end end From 55773f8d996162f151edd844e180781f5479bb08 Mon Sep 17 00:00:00 2001 From: stephanie rousset Date: Tue, 6 Jan 2026 14:20:59 +0100 Subject: [PATCH 7/7] test: add test to check the order of proposals is kept when using filter --- spec/system/proposals_index_spec.rb | 30 ++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/spec/system/proposals_index_spec.rb b/spec/system/proposals_index_spec.rb index 94b2a3bd70..c872f9c6c4 100644 --- a/spec/system/proposals_index_spec.rb +++ b/spec/system/proposals_index_spec.rb @@ -185,15 +185,15 @@ create_list(:proposal, Decidim::Paginable::OPTIONS.first + 5, component:) end - it "paginates them" do + it "paginates them and keeps the same order" do visit_component - + # 25 proposals on page one expect(page).to have_css("[id^='proposals__proposal']", count: Decidim::Paginable::OPTIONS.first) texts = page.all("[id^='proposals__proposal']").map(&:text) click_on "Next" expect(page).to have_css("[data-pages] [data-page][aria-current='page']", text: "2") - + # 5 proposals on page two expect(page).to have_css("[id^='proposals__proposal']", count: 5) click_on "Prev" # check elements on page one are still the same @@ -201,6 +201,30 @@ end end + context "when there is a lot of proposals and using a filter" do + before do + create_list(:proposal, Decidim::Paginable::OPTIONS.first - 5, :evaluating, component:) + create_list(:proposal, Decidim::Paginable::OPTIONS.first - 5, :accepted, component:) + create_list(:proposal, 10, :not_answered, component:) + end + + it "paginates proposals and keeps the same order in pages" do + visit_component + + expect(page).to have_css("[id^='proposals__proposal']", count: Decidim::Paginable::OPTIONS.first) + uncheck "Not answered" + sleep 5 + texts = page.all("[id^='proposals__proposal']").map(&:text) + click_on "Next" + expect(page).to have_css("[data-pages] [data-page][aria-current='page']", text: "2") + # we have 40 proposals without the not_answered, so 15 on page 2 + expect(page).to have_css("[id^='proposals__proposal']", count: 15) + click_on "Prev" + # check elements on page one are still in the same order + expect(page.all("[id^='proposals__proposal']").map(&:text)).to eq(texts) + end + end + shared_examples "ordering proposals by selected option" do |selected_option| let(:first_proposal_title) { translated(first_proposal.title) } let(:last_proposal_title) { translated(last_proposal.title) }