diff --git a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb
index f34a63c227c75..466cf77f651b2 100644
--- a/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb
+++ b/decidim-meetings/app/views/decidim/meetings/meetings/_meeting_aside.html.erb
@@ -6,6 +6,12 @@
<% end %>
+<% if meeting.waitlist_enabled? && !meeting.has_available_slots? && meeting.can_be_joined_by?(current_user) %>
+
+ <%= cell "decidim/meetings/join_waitlist_button", meeting, hide_modal: local_assigns[:hide_modal] %>
+
+<% end %>
+
<% if (meeting.closed? && meeting.closing_visible?) || (registration.present? && registration.meeting.component.settings.registration_code_enabled) || (meeting.services.any?) %>
<% if meeting.closed? && meeting.closing_visible? %>
@@ -35,7 +41,7 @@
<% end %>
<% end %>
- <% if registration.present? && registration.meeting.component.settings.registration_code_enabled %>
+ <% if registration.present? && registration.meeting.component.settings.registration_code_enabled && registration.registered? %>
<%= render layout: "decidim/meetings/layouts/aside_block", locals: { emoji: "coupon-line" } do %>
<%= registration_code_help_text %>
<%= registration.code %>
diff --git a/decidim-meetings/config/locales/en.yml b/decidim-meetings/config/locales/en.yml
index 1b6e07e1f396f..4ab076bcbab2b 100644
--- a/decidim-meetings/config/locales/en.yml
+++ b/decidim-meetings/config/locales/en.yml
@@ -43,6 +43,7 @@ en:
title: Title
transparent: Transparent
type_of_meeting: Type
+ waitlist_enabled: Waitlist enabled
meeting_agenda:
title: Title
visible: Visible
@@ -231,6 +232,8 @@ en:
email_outro: You have received this notification because you are following the "%{resource_title}" meeting. You can unfollow it from the previous link.
email_subject: The "%{resource_title}" meeting was updated
notification_title: The %{resource_title} meeting was updated.
+ meeting_waitlist_added:
+ notification_title: You have been added to the waiting list for the %{resource_title} meeting.
registration_code_validated:
email_intro: Your registration code "%{registration_code}" for the "%{resource_title}" meeting has been validated.
email_outro: You have received this notification because your registration code for the "%{resource_title}" meeting has been validated.
@@ -431,6 +434,7 @@ en:
reserved_slots_help: Leave it to 0 if you do not have reserved slots.
reserved_slots_less_than: Must be less than or equal to %{count}
title: Registrations
+ waitlist_enabled_help: If enabled, the user will be able to join the waitlist when the available slots are full.
update:
invalid: There was a problem saving the registration settings.
success: Meeting registrations settings successfully saved.
@@ -592,11 +596,13 @@ en:
cancel: Cancel
confirm: Confirm
show:
+ add_to_waitlist: Join the waiting list
attendees: Attendees count
+ cancel_waitlist: Leave the waiting list
contributions: Contributions count
join: Register
+ join_waitlist: Join the waiting list
leave: Cancel your registration
- leave_confirmation: Are you sure you want to cancel your registration for this meeting?
link_available_soon: Link available soon
link_closed: The link to join the meeting will be available a few minutes before it starts
live_event: This meeting is happening right now
@@ -606,13 +612,18 @@ en:
organizations: Attending organizations
redirect_notice: This meeting is part of another space, so you have been moved to %{current_space_name}. If you prefer, you can go back to %{previous_space_name}.
registration_code_help_text: Your registration code
+ registration_confirmation: Are you sure you want to cancel your registration for this meeting?
registration_state:
validated: VALIDATED
validation_pending: VALIDATION PENDING
+ registration_title: Cancel your registration
remaining_slots:
one: "%{count} slot remaining"
other: "%{count} slots remaining"
visit_finished: View past meeting
+ waitlist_confirmation: Are you sure you want to cancel your waitlist registration for this meeting?
+ waitlist_info: You have joined the waiting list for this meeting. If a spot for this meeting becomes available, you will register automatically and receive a notification.
+ waitlist_title: Cancel your waitlist registration
withdraw_btn_hint: You can withdraw your meeting if you change your mind. The meeting is not deleted, it will appear in the list of withdrawn meetings.
withdraw_confirmation_html: Are you sure you want to withdraw this meeting?
This action cannot be cancelled!
withdraw_meeting: Withdraw
@@ -697,8 +708,15 @@ en:
invalid: There was a problem declining the invitation.
success: You have declined the invitation successfully.
destroy:
- invalid: There was a problem leaving this meeting.
- success: You have left the meeting successfully.
+ on_waiting_list:
+ invalid: There was a problem leaving the waiting list.
+ success: You have left the waiting list successfully.
+ registered:
+ invalid: There was a problem leaving this meeting.
+ success: You have left the meeting successfully.
+ waitlist:
+ invalid: There was a problem joining the waiting list.
+ success: You have joined the waiting list successfully.
type_of_meeting:
hybrid: Hybrid
in_person: In person
diff --git a/decidim-meetings/db/migrate/20250214110525_add_waitlist_enabled_to_decidim_meetings.rb b/decidim-meetings/db/migrate/20250214110525_add_waitlist_enabled_to_decidim_meetings.rb
new file mode 100644
index 0000000000000..c0707d4366d61
--- /dev/null
+++ b/decidim-meetings/db/migrate/20250214110525_add_waitlist_enabled_to_decidim_meetings.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddWaitlistEnabledToDecidimMeetings < ActiveRecord::Migration[7.0]
+ def change
+ add_column :decidim_meetings_meetings, :waitlist_enabled, :boolean, default: false, null: false
+ end
+end
diff --git a/decidim-meetings/db/migrate/20250214113208_add_status_to_registrations_to_decidim_meetings_registrations.rb b/decidim-meetings/db/migrate/20250214113208_add_status_to_registrations_to_decidim_meetings_registrations.rb
new file mode 100644
index 0000000000000..4bcce525e5e8c
--- /dev/null
+++ b/decidim-meetings/db/migrate/20250214113208_add_status_to_registrations_to_decidim_meetings_registrations.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class AddStatusToRegistrationsToDecidimMeetingsRegistrations < ActiveRecord::Migration[7.0]
+ def change
+ add_column :decidim_meetings_registrations, :status, :string, default: "registered", null: false
+ add_index :decidim_meetings_registrations, :status
+ end
+end
diff --git a/decidim-meetings/lib/decidim/meetings/engine.rb b/decidim-meetings/lib/decidim/meetings/engine.rb
index 3531cc8d9c56d..0e70e0940c9d5 100644
--- a/decidim-meetings/lib/decidim/meetings/engine.rb
+++ b/decidim-meetings/lib/decidim/meetings/engine.rb
@@ -26,6 +26,8 @@ class Engine < ::Rails::Engine
get :decline_invitation
get :join, action: :show
post :answer
+ get :join_waitlist, action: :show
+ post :join_waitlist
end
end
resources :versions, only: [:show]
@@ -71,6 +73,7 @@ class Engine < ::Rails::Engine
Decidim.icons.register(name: "bill-line", icon: "bill-line", category: "system", description: "", engine: :meetings)
Decidim.icons.register(name: "add-box-line", icon: "add-box-line", category: "system", description: "", engine: :meetings)
Decidim.icons.register(name: "calendar-close-line", icon: "calendar-close-line", category: "system", description: "", engine: :meetings)
+ Decidim.icons.register(name: "clockwise-line", icon: "clockwise-line", category: "system", description: "", engine: :meetings)
end
initializer "decidim_meetings.content_processors" do |_app|
diff --git a/decidim-meetings/lib/decidim/meetings/test/factories.rb b/decidim-meetings/lib/decidim/meetings/test/factories.rb
index 7ad7fbfbde41b..1ee2b74b9e113 100644
--- a/decidim-meetings/lib/decidim/meetings/test/factories.rb
+++ b/decidim-meetings/lib/decidim/meetings/test/factories.rb
@@ -44,6 +44,7 @@
registration_terms { generate_localized_description(:meeting_registration_terms, skip_injection:) }
registration_type { :on_this_platform }
type_of_meeting { :in_person }
+ waitlist_enabled { false }
component { build(:meeting_component) }
iframe_access_level { :all }
iframe_embed_type { :none }
diff --git a/decidim-meetings/spec/commands/admin/update_registrations_spec.rb b/decidim-meetings/spec/commands/admin/update_registrations_spec.rb
index 5858b5145a096..0567d50677732 100644
--- a/decidim-meetings/spec/commands/admin/update_registrations_spec.rb
+++ b/decidim-meetings/spec/commands/admin/update_registrations_spec.rb
@@ -28,6 +28,7 @@ module Decidim::Meetings
registration_form_enabled:,
available_slots:,
reserved_slots:,
+ waitlist_enabled: true,
customize_registration_email:,
registration_email_custom_content:,
registration_terms:,
@@ -57,6 +58,7 @@ module Decidim::Meetings
expect(meeting.customize_registration_email).to be true
expect(meeting.registration_email_custom_content).to eq(registration_email_custom_content)
expect(translated(meeting.registration_terms)).to eq "A legal text"
+ expect(meeting.waitlist_enabled).to be true
end
end
diff --git a/decidim-meetings/spec/commands/join_waitlist_spec.rb b/decidim-meetings/spec/commands/join_waitlist_spec.rb
new file mode 100644
index 0000000000000..b9c7d8fa008f1
--- /dev/null
+++ b/decidim-meetings/spec/commands/join_waitlist_spec.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim::Meetings
+ describe JoinWaitlist do
+ subject { described_class.new(meeting, form) }
+
+ let(:organization) { create(:organization) }
+ let(:participatory_process) { create(:participatory_process, organization: organization) }
+ let(:component) { create(:component, manifest_name: :meetings, participatory_space: participatory_process) }
+ let(:available_slots) { 2 }
+ let(:waitlist_enabled) { true }
+ let(:meeting) do
+ create(:meeting,
+ component: component,
+ waitlist_enabled: waitlist_enabled,
+ registrations_enabled: true,
+ available_slots: available_slots)
+ end
+
+ let(:user) { create(:user, :confirmed, organization: organization, notifications_sending_frequency: "none") }
+ let(:user_group) { create(:user_group) }
+ let(:form_params) { { user_group_id: user_group.id } }
+ let(:form) do
+ Decidim::Meetings::JoinMeetingForm.from_params(form_params).with_context(current_user: user)
+ end
+
+ let(:waitlist_notification) do
+ {
+ event: "decidim.events.meetings.meeting_waitlist_added",
+ event_class: MeetingRegistrationNotificationEvent,
+ resource: meeting,
+ affected_users: [user]
+ }
+ end
+
+ context "when all conditions are met" do
+ before do
+ create_list(:registration, available_slots, meeting: meeting, status: :registered)
+ end
+
+ it "broadcasts ok" do
+ expect { subject.call }.to broadcast(:ok)
+ end
+
+ it "creates a waitlist registration with correct attributes" do
+ expect { subject.call }.to change(Registration, :count).by(1)
+ last_registration = Registration.last
+ expect(last_registration.user).to eq(user)
+ expect(last_registration.meeting).to eq(meeting)
+ expect(last_registration.status).to eq("on_waiting_list")
+ end
+
+ it "publishes waitlist notification" do
+ expect(Decidim::EventsManager).to receive(:publish).with(waitlist_notification)
+ subject.call
+ end
+ end
+
+ context "when waitlist is disabled" do
+ let(:waitlist_enabled) { false }
+
+ before do
+ create_list(:registration, available_slots, meeting: meeting, status: :registered)
+ end
+
+ it "broadcasts invalid" do
+ expect { subject.call }.to broadcast(:invalid)
+ end
+ end
+
+ context "when the user is already registered" do
+ before do
+ create(:registration, meeting: meeting, user: user, status: :registered)
+ end
+
+ it "broadcasts invalid" do
+ expect { subject.call }.to broadcast(:invalid)
+ end
+ end
+
+ context "when meeting has available slots" do
+ it "broadcasts invalid" do
+ expect { subject.call }.to broadcast(:invalid)
+ end
+ end
+
+ context "when the form is invalid" do
+ before do
+ create_list(:registration, available_slots, meeting: meeting, status: :registered)
+ allow(form).to receive(:valid?).and_return(false)
+ end
+
+ it "broadcasts invalid_form" do
+ expect { subject.call }.to broadcast(:invalid_form)
+ end
+ end
+ end
+end
diff --git a/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb
new file mode 100644
index 0000000000000..554df129ec6fb
--- /dev/null
+++ b/decidim-meetings/spec/controllers/decidim/meetings/registrations_controller_spec.rb
@@ -0,0 +1,168 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+module Decidim::Meetings
+ describe RegistrationsController do
+ routes { Decidim::Meetings::Engine.routes }
+
+ let(:organization) { create(:organization) }
+ let(:user) { create(:user, :confirmed, organization:) }
+ let(:participatory_process) { create(:participatory_process, organization:) }
+ let(:component) { create(:meeting_component, participatory_space: participatory_process) }
+ let(:meeting) { create(:meeting, :published, component:, registrations_enabled: true, available_slots: 10) }
+
+ before do
+ request.env["decidim.current_organization"] = organization
+ request.env["decidim.current_participatory_space"] = participatory_process
+ request.env["decidim.current_component"] = component
+ end
+
+ describe "POST create" do
+ let(:params) { { meeting_id: meeting.id } }
+
+ context "when user is authenticated" do
+ before { sign_in user }
+
+ context "with available slots" do
+ it "creates registration and redirects" do
+ expect do
+ post :create, params: params
+ end.to change(Registration, :count).by(1)
+
+ expect(flash[:notice]).to eq(I18n.t("registrations.create.success", scope: "decidim.meetings"))
+ expect(response).to redirect_to(meeting_path(meeting))
+ end
+ end
+
+ context "when no available slots" do
+ let!(:registrations) { create_list(:registration, 10, meeting: meeting) }
+
+ it "shows error message" do
+ post :create, params: params
+
+ expect(flash[:alert]).to eq(I18n.t("registrations.create.invalid", scope: "decidim.meetings"))
+ expect(response).to redirect_to(meeting_path(meeting))
+ end
+ end
+ end
+
+ context "when user not authenticated" do
+ it "redirects to login" do
+ post :create, params: params
+ expect(response).to redirect_to("/users/sign_in")
+ end
+ end
+ end
+
+ describe "POST answer" do
+ let(:questionnaire) { create(:questionnaire, :with_questions, questionnaire_for: meeting) }
+ let(:question) { questionnaire.questions.first }
+ let(:params) do
+ {
+ meeting_id: meeting.id,
+ waitlist: waitlist,
+ questionnaire: {
+ responses: [
+ {
+ body: "Answer",
+ question_id: question.id
+ }
+ ],
+ tos_agreement: true
+ }
+ }
+ end
+ let(:waitlist) { false }
+
+ before do
+ sign_in user
+ meeting.update!(
+ registrations_enabled: true,
+ registration_form_enabled: true,
+ questionnaire: questionnaire
+ )
+ end
+
+ context "with valid params" do
+ context "when joining directly" do
+ it "answers questionnaire and redirects" do
+ expect do
+ post :answer, params: params
+ end.to change { meeting.registrations.count }.by(1)
+
+ expect(flash[:notice]).to eq(I18n.t("registrations.create.success", scope: "decidim.meetings"))
+ expect(response).to redirect_to(meeting_path(meeting))
+ end
+ end
+
+ context "when joining waitlist" do
+ let(:meeting) { create(:meeting, component:, available_slots: 10, waitlist_enabled: true) }
+ let!(:registrations) { create_list(:registration, 10, meeting: meeting) }
+ let(:waitlist) { true }
+
+ it "adds user to waitlist and redirects" do
+ expect do
+ post :answer, params: params
+ end.to change { meeting.registrations.where(status: :on_waiting_list).count }.by(1)
+
+ expect(flash[:notice]).to eq(I18n.t("registrations.waitlist.success", scope: "decidim.meetings"))
+ expect(response).to redirect_to(meeting_path(meeting))
+ end
+ end
+ end
+
+ context "with invalid params" do
+ let(:params) do
+ {
+ meeting_id: meeting.id,
+ waitlist: false,
+ questionnaire: { responses: [] }
+ }
+ end
+
+ it "shows error message" do
+ post :answer, params: params
+
+ expect(flash[:alert]).to eq(I18n.t("answer.invalid", scope: "decidim.forms.questionnaires"))
+ expect(response).to render_template("decidim/forms/questionnaires/show")
+ end
+ end
+ end
+
+ describe "POST join_waitlist" do
+ let(:meeting) { create(:meeting, component:, available_slots: 10, waitlist_enabled: true) }
+ let!(:registrations) { create_list(:registration, 10, meeting: meeting) }
+ let(:params) { { meeting_id: meeting.id } }
+
+ before { sign_in user }
+
+ context "when meeting has no available slots" do
+ it "adds user to waitlist" do
+ expect do
+ post :join_waitlist, params: params
+ end.to change(Registration.on_waiting_list, :count).by(1)
+
+ expect(flash[:notice]).to eq(I18n.t("registrations.waitlist.success", scope: "decidim.meetings"))
+ expect(response).to redirect_to(meeting_path(meeting))
+ end
+ end
+ end
+
+ describe "DELETE destroy" do
+ let!(:registration) { create(:registration, meeting:, user:) }
+ let(:params) { { meeting_id: meeting.id } }
+
+ before { sign_in user }
+
+ it "destroys registration" do
+ expect do
+ delete :destroy, params: params
+ end.to change(Registration, :count).by(-1)
+
+ expect(flash[:notice]).to match(/successfully/)
+ expect(response).to redirect_to(meeting_path(meeting))
+ end
+ end
+ end
+end
diff --git a/decidim-meetings/spec/system/meeting_registrations_spec.rb b/decidim-meetings/spec/system/meeting_registrations_spec.rb
index 6366ffcf5837f..fd3389986ac96 100644
--- a/decidim-meetings/spec/system/meeting_registrations_spec.rb
+++ b/decidim-meetings/spec/system/meeting_registrations_spec.rb
@@ -14,6 +14,7 @@
let(:registrations_enabled) { true }
let(:registration_form_enabled) { false }
let(:available_slots) { 20 }
+ let(:waitlist_enabled) { true }
let(:registration_terms) do
{
en: "A legal text",
@@ -30,6 +31,10 @@ def questionnaire_public_path
Decidim::EngineRouter.main_proxy(component).join_meeting_registration_path(meeting_id: meeting.id)
end
+ def questionnaire_waitlist_public_path
+ Decidim::EngineRouter.main_proxy(component).join_waitlist_meeting_registration_path(meeting_id: meeting.id)
+ end
+
def see_questionnaire_questions; end
before do
@@ -38,6 +43,7 @@ def see_questionnaire_questions; end
registrations_enabled:,
registration_form_enabled:,
available_slots:,
+ waitlist_enabled:,
registration_terms:
)
end
@@ -52,6 +58,12 @@ def see_questionnaire_questions; end
expect(page).to have_no_text("20 slots remaining")
end
+ it "the waitlist button is not visible" do
+ visit_meeting
+
+ expect(page).to have_no_content("Join the waiting list")
+ end
+
context "and registration form is also enabled" do
let(:registration_form_enabled) { true }
@@ -83,6 +95,14 @@ def see_questionnaire_questions; end
expect(page).to have_text("0 slots remaining")
end
+ context "and waitlist is enabled" do
+ it "the waitlist button is visible" do
+ visit_meeting
+
+ expect(page).to have_content("Join the waiting list")
+ end
+ end
+
context "and registration form is enabled" do
let(:registration_form_enabled) { true }
@@ -100,6 +120,15 @@ def see_questionnaire_questions; end
expect(page).to have_content("The form is closed and cannot be answered")
end
+
+ it "can answer the waitlist form" do
+ visit questionnaire_waitlist_public_path
+
+ expect(page).to have_i18n_content(questionnaire.title)
+ expect(page).to have_i18n_content(questionnaire.description, strip_tags: true)
+ expect(page).to have_i18n_content(question.body)
+ expect(page).to have_css(".form.answer-questionnaire")
+ end
end
end
diff --git a/decidim-meetings/spec/system/meeting_waiting_list_spec.rb b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb
new file mode 100644
index 0000000000000..fd522ec31a864
--- /dev/null
+++ b/decidim-meetings/spec/system/meeting_waiting_list_spec.rb
@@ -0,0 +1,257 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "Meeting waiting list" do
+ include_context "with a component"
+ let(:manifest_name) { "meetings" }
+
+ let!(:questionnaire) { create(:questionnaire) }
+ let!(:question) { create(:questionnaire_question, questionnaire:, position: 0) }
+ let!(:meeting) { create(:meeting, :published, component:, questionnaire:, waitlist_enabled:) }
+ let!(:user) { create(:user, :confirmed, organization:) }
+ let(:registrations_enabled) { true }
+ let(:registration_form_enabled) { false }
+ let(:available_slots) { 10 }
+ let(:waitlist_enabled) { true }
+ let(:registration_terms) do
+ {
+ en: "A legal text",
+ es: "Un texto legal",
+ ca: "Un text legal"
+ }
+ end
+
+ def visit_meeting
+ visit resource_locator(meeting).path
+ end
+
+ before do
+ stub_geocoding_coordinates([meeting.latitude, meeting.longitude])
+ meeting.update!(
+ registrations_enabled:,
+ registration_form_enabled:,
+ available_slots:,
+ registration_terms:
+ )
+ end
+
+ context "when the registration form is enabled" do
+ let(:registration_form_enabled) { true }
+
+ context "when the waitlist is enabled" do
+ context "when the meeting has available slots" do
+ before do
+ visit_meeting
+ login_as user, scope: :user
+ end
+
+ it "does not show the join waitlist button" do
+ expect(page).to have_no_content("Join the waiting list")
+ end
+ end
+
+ context "when the meeting has no available slots" do
+ before do
+ create_list(:registration, available_slots, meeting: meeting)
+ end
+
+ context "when the user is not logged in" do
+ before do
+ visit_meeting
+ end
+
+ it "shows the join waitlist button" do
+ expect(page).to have_content("Join the waiting list")
+ expect(page).to have_no_content("You have joined the waiting list for this meeting.")
+ end
+
+ it "shows the login modal when clicking the join waitlist button" do
+ click_on "Join the waiting list"
+ expect(page).to have_css("#loginModal", visible: :visible)
+ expect(page).to have_content("Please log in")
+ end
+ end
+
+ context "when the user is logged in" do
+ before do
+ login_as user, scope: :user
+ visit_meeting
+ end
+
+ it "shows the join waitlist button" do
+ expect(page).to have_content("Join the waiting list")
+ end
+
+ it "shows the join waitlist registration form when clicking the join waitlist button" do
+ click_on "Join the waiting list"
+ expect(page).to have_i18n_content(questionnaire.title)
+ expect(page).to have_i18n_content(questionnaire.description, strip_tags: true)
+ expect(page).to have_i18n_content(question.body)
+ expect(page).to have_css(".form.answer-questionnaire")
+ end
+
+ it "can join the waitlist" do
+ click_on "Join the waiting list"
+ fill_in question.body["en"], with: "My first answer"
+ check "questionnaire_tos_agreement"
+ accept_confirm do
+ click_on "Submit"
+ end
+ expect(page).to have_content("You have joined the waiting list successfully.")
+ end
+ end
+ end
+ end
+
+ context "when the waitlist is disabled" do
+ let(:waitlist_enabled) { false }
+
+ context "when the meeting has available slots" do
+ before do
+ visit_meeting
+ login_as user, scope: :user
+ end
+
+ it "does not show the join waitlist button" do
+ expect(page).to have_no_content("Join the waiting list")
+ expect(page).to have_no_content("You have joined the waiting list for this meeting.")
+ end
+ end
+
+ context "when the meeting has no available slots" do
+ before do
+ create_list(:registration, available_slots, meeting: meeting)
+ end
+
+ context "when the user is not logged in" do
+ before do
+ visit_meeting
+ end
+
+ it "does not show the join waitlist button" do
+ expect(page).to have_no_content("Join the waiting list")
+ expect(page).to have_no_content("You have joined the waiting list for this meeting.")
+ end
+ end
+
+ context "when the user is logged in" do
+ before do
+ login_as user, scope: :user
+ visit_meeting
+ end
+
+ it "does not show the join waitlist button" do
+ expect(page).to have_no_content("Join the waiting list")
+ expect(page).to have_no_content("You have joined the waiting list for this meeting.")
+ end
+ end
+ end
+ end
+ end
+
+ context "when the registration form is disabled" do
+ context "when the waitlist is enabled" do
+ context "when the meeting has available slots" do
+ before do
+ visit_meeting
+ login_as user, scope: :user
+ end
+
+ it "does not show the join waitlist button" do
+ expect(page).to have_no_content("Join the waiting list")
+ expect(page).to have_no_content("You have joined the waiting list for this meeting.")
+ end
+ end
+
+ context "when the meeting has no available slots" do
+ before do
+ create_list(:registration, available_slots, meeting: meeting)
+ login_as user, scope: :user
+ visit_meeting
+ end
+
+ it "shows the join waitlist button" do
+ expect(page).to have_content("Join the waiting list")
+ expect(page).to have_no_content("You have joined the waiting list for this meeting.")
+ end
+
+ it "can join the waitlist" do
+ click_on "Join the waiting list"
+
+ within "#meeting-waitlist-confirm-#{meeting.id}" do
+ expect(page).to have_content "Join the waiting list"
+ click_on "Confirm"
+ end
+
+ expect(page).to have_content("You have joined the waiting list successfully.")
+ expect(page).to have_content("You have joined the waiting list for this meeting.")
+ end
+ end
+ end
+
+ context "when the waitlist is disabled" do
+ let(:waitlist_enabled) { false }
+
+ context "when the meeting has available slots" do
+ before do
+ visit_meeting
+ login_as user, scope: :user
+ end
+
+ it "does not show the join waitlist button" do
+ expect(page).to have_no_content("Join the waiting list")
+ end
+ end
+
+ context "when the meeting has no available slots" do
+ before do
+ create_list(:registration, available_slots, meeting: meeting)
+ login_as user, scope: :user
+ visit_meeting
+ end
+
+ it "does not show the join waitlist button" do
+ expect(page).to have_no_content("Join the waiting list")
+ end
+ end
+ end
+ end
+
+ def leave_meeting(user)
+ login_as user, scope: :user
+ visit_meeting
+ click_on "Cancel your registration"
+ logout :user
+ end
+
+ context "when the meeting is full and a user cancels their registration" do
+ context "and there are users on the waiting list" do
+ let!(:registrations) { create_list(:registration, available_slots, meeting: meeting) }
+ let(:users_on_waitlist) { create_list(:user, 5, :confirmed, organization:) }
+ let(:first_waitlist_user) { users_on_waitlist.first }
+
+ let!(:waitlist_entries) do
+ users_on_waitlist.map.with_index do |user, index|
+ create(:registration, meeting: meeting, user: user, status: "on_waiting_list", created_at: Time.current - index.minutes)
+ end
+ end
+
+ let(:earliest_waitlist_entry) { waitlist_entries.min_by(&:created_at) }
+ let(:earliest_waitlist_user) { earliest_waitlist_entry.user }
+
+ before do
+ perform_enqueued_jobs { Decidim::Meetings::LeaveMeeting.call(meeting, registrations.first.user) }
+ login_as earliest_waitlist_user, scope: :user
+ end
+
+ it "displays the registration confirmation" do
+ visit_meeting
+ email = last_email
+ expect(page).to have_content("Your registration code")
+ expect(email.subject).to eq("Your meeting's registration has been confirmed")
+ expect(email.to).to eq([earliest_waitlist_user.email])
+ end
+ end
+ end
+end