diff --git a/engines/bops_preapps/spec/system/assessment_workflow_spec.rb b/engines/bops_preapps/spec/system/assessment_workflow_spec.rb new file mode 100644 index 0000000000..5dd617c53d --- /dev/null +++ b/engines/bops_preapps/spec/system/assessment_workflow_spec.rb @@ -0,0 +1,443 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Pre-application assessment workflow", type: :system do + let(:local_authority) { create(:local_authority, :default) } + let(:assessor) { create(:user, :assessor, local_authority:, name: "Alice Smith") } + let(:reviewer) { create(:user, :reviewer, local_authority:, name: "Bob Jones") } + let(:case_record) { build(:case_record, user: assessor, local_authority:) } + + let!(:requirement) { create(:local_authority_requirement, local_authority:, category: "drawings", description: "Floor plans") } + let!(:application_type) { create(:application_type, :prior_approval, local_authority:, requirements: [requirement]) } + let!(:policy_area) { create(:local_authority_policy_area, local_authority:, description: "Design") } + let!(:policy_reference) { create(:local_authority_policy_reference, local_authority:, code: "LP1", description: "Local design policy") } + + let(:planning_application) do + create(:planning_application, :pre_application, :in_assessment, + local_authority:, + case_record:, + recommended_application_type: application_type) + end + + let(:reference) { planning_application.reference } + + describe "end-to-end assessment workflow" do + it "completes all assessment tasks in sequence with correct status transitions and icons" do + sign_in(assessor) + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:sidebar) + expect(page).to have_content("Assessment") + + within :sidebar do + expect(page).to have_content("Check application") + expect(page).to have_content("Additional services") + expect(page).to have_content("Assessment summaries") + expect(page).to have_content("Complete assessment") + end + + assessment_tasks.each do |t| + expect(t).to be_not_started + end + + within :sidebar do + expect(page).to have_css("svg[aria-label='Not started']", minimum: 10) + end + + within :sidebar do + click_link "Check application details" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/check-application/check-application-details") + expect(page).to have_selector("h1", text: "Check application details") + expect(page).to have_selector(:active_sidebar_task, "Check application details") + + within_fieldset("Does the description match the development or use in the plans?") { choose "Yes" } + within_fieldset("Are the plans consistent with each other?") { choose "Yes" } + within_fieldset("Are the proposal details consistent with the plans?") { choose "Yes" } + within_fieldset("Is the site map correct?") { choose "Yes" } + + click_button "Save and mark as complete" + + expect(task("Check application details").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Check application details") + + within :sidebar do + click_link "Check consultees consulted" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/check-application/check-consultees-consulted") + expect(page).to have_selector("h1", text: "Check consultees consulted") + expect(page).to have_selector(:active_sidebar_task, "Check consultees consulted") + + click_button "Save and mark as complete" + + expect(task("Check consultees consulted").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Check consultees consulted") + + within :sidebar do + click_link "Check site history" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/check-application/check-site-history") + expect(page).to have_selector("h1", text: "Check site history") + expect(page).to have_selector(:active_sidebar_task, "Check site history") + + click_button "Save and mark as complete" + + expect(task("Check site history").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Check site history") + + within :sidebar do + click_link "Site visit" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/additional-services/site-visit") + expect(page).to have_selector("h1", text: "Site visit") + expect(page).to have_selector(:active_sidebar_task, "Site visit") + + expect(page).to have_content("No site visits have been recorded yet") + + click_button "Save and mark as complete" + + expect(task("Site visit").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Site visit") + + within :sidebar do + click_link "Meeting" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/additional-services/meeting") + expect(page).to have_selector("h1", text: "Meeting") + expect(page).to have_selector(:active_sidebar_task, "Meeting") + + click_button "Save and mark as complete" + + expect(task("Meeting").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Meeting") + + within :sidebar do + click_link "Site description" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/assessment-summaries/site-description") + expect(page).to have_selector("h1", text: "Site description") + expect(page).to have_selector(:active_sidebar_task, "Site description") + + fill_in "Description of the site", with: "A detached house with garden." + + click_button "Save changes" + + expect(page).to have_content("Site description was successfully updated") + expect(task("Site description").reload).to be_in_progress + expect(page).to have_selector(:in_progress_sidebar_task, "Site description") + + click_button "Save and mark as complete" + + expect(task("Site description").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Site description") + + within :sidebar do + click_link "Planning considerations and advice" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/assessment-summaries/planning-considerations-and-advice") + expect(page).to have_selector("h1", text: "Planning considerations and advice") + expect(page).to have_selector(:active_sidebar_task, "Planning considerations and advice") + + click_button "Save and mark as complete" + + expect(task("Planning considerations and advice").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Planning considerations and advice") + + within :sidebar do + click_link "Suggest heads of terms" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/assessment-summaries/suggest-heads-of-terms") + expect(page).to have_selector("h1", text: "Suggest heads of terms") + expect(page).to have_selector(:active_sidebar_task, "Suggest heads of terms") + + click_button "Save and mark as complete" + + expect(task("Suggest heads of terms").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Suggest heads of terms") + + within :sidebar do + click_link "Summary of advice" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/assessment-summaries/summary-of-advice") + expect(page).to have_selector("h1", text: "Summary of advice") + expect(page).to have_selector(:active_sidebar_task, "Summary of advice") + + choose "Likely to be supported (recommended based on considerations)" + fill_in "Enter summary of planning considerations and advice. This should summarise any changes the applicant needs to make before they make an application.", with: "The proposal is acceptable." + + click_button "Save and mark as complete" + + expect(page).to have_content("Summary of advice successfully updated") + expect(task("Summary of advice").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Summary of advice") + + within :sidebar do + click_link "Choose application type" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/complete-assessment/choose-application-type") + expect(page).to have_selector("h1", text: "Choose application type") + expect(page).to have_selector(:active_sidebar_task, "Choose application type") + + expect(page).to have_content("What application type would the applicant need to apply for next?") + + select "Prior Approval - Larger extension to a house", from: "What application type would the applicant need to apply for next?" + click_button "Save and mark as complete" + + expect(page).to have_content("Recommended application type was successfully chosen") + expect(task("Choose application type").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Choose application type") + + within :sidebar do + click_link "Check and add requirements" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/complete-assessment/check-and-add-requirements") + expect(page).to have_selector("h1", text: "Check and add requirements") + expect(page).to have_selector(:active_sidebar_task, "Check and add requirements") + + click_button "Save and mark as complete" + + expect(page).to have_content("Requirements were successfully saved") + expect(task("Check and add requirements").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Check and add requirements") + + within :sidebar do + click_link "Review and submit pre-application" + end + + expect(page).to have_selector("h1", text: "Pre-application report") + + click_button "Confirm and submit recommendation" + + expect(page).to have_content("Pre-application report submitted for review") + expect(task("Review and submit pre-application").reload).to be_completed + end + + it "shows in progress status when task is partially completed" do + sign_in(assessor) + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:sidebar) + + within :sidebar do + click_link "Site description" + end + + fill_in "Description of the site", with: "Some text" + click_button "Save changes" + + expect(page).to have_content("Site description was successfully updated") + expect(task("Site description").reload).to be_in_progress + expect(page).to have_selector(:in_progress_sidebar_task, "Site description") + end + + it "navigates correctly between all assessment task sections" do + sign_in(assessor) + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:sidebar) + + tasks = [ + {name: "Check application details", path: "check-application/check-application-details"}, + {name: "Check consultees consulted", path: "check-application/check-consultees-consulted"}, + {name: "Check site history", path: "check-application/check-site-history"}, + {name: "Site visit", path: "additional-services/site-visit"}, + {name: "Meeting", path: "additional-services/meeting"}, + {name: "Site description", path: "assessment-summaries/site-description"}, + {name: "Planning considerations and advice", path: "assessment-summaries/planning-considerations-and-advice"}, + {name: "Suggest heads of terms", path: "assessment-summaries/suggest-heads-of-terms"}, + {name: "Summary of advice", path: "assessment-summaries/summary-of-advice"}, + {name: "Choose application type", path: "complete-assessment/choose-application-type"}, + {name: "Check and add requirements", path: "complete-assessment/check-and-add-requirements"} + ] + + tasks.each do |t| + within :sidebar do + click_link t[:name] + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-assess/#{t[:path]}") + expect(page).to have_selector(:active_sidebar_task, t[:name]) + end + end + + it "hides buttons when application is determined" do + planning_application.update!(status: "determined", determined_at: Time.current) + + sign_in(assessor) + visit "/preapps/#{reference}/check-and-assess/assessment-summaries/site-description" + + expect(page).not_to have_button("Save and mark as complete") + expect(page).not_to have_button("Save changes") + end + + it "maintains sidebar scroll position across navigation", js: true do + sign_in(assessor) + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:sidebar) + + within :sidebar do + click_link "Summary of advice" + end + + expect(page).to have_css("nav.bops-sidebar[data-controller='sidebar-scroll']") + + initial_scroll = page.evaluate_script("document.querySelector('nav.bops-sidebar').scrollTop") + + within :sidebar do + click_link "Check application details" + end + + final_scroll = page.evaluate_script("document.querySelector('nav.bops-sidebar').scrollTop") + expect(final_scroll).to eq(initial_scroll) + end + end + + describe "review and submit workflow with reviewer" do + before do + planning_application.case_record.update!(user: assessor) + create(:assessment_detail, planning_application:, category: :summary_of_work, entry: "Test summary") + create(:assessment_detail, planning_application:, category: :site_description, entry: "Test site") + create(:assessment_detail, planning_application:, category: :consultation_summary, entry: "Test consultation") + planning_application.create_consideration_set! if planning_application.consideration_set.nil? + end + + it "handles the full review workflow with action_required status" do + sign_in(assessor) + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:sidebar) + + within :sidebar do + click_link "Review and submit pre-application" + end + + expect(task("Review and submit pre-application")).to be_not_started + + click_button "Confirm and submit recommendation" + + expect(page).to have_content("Pre-application report submitted for review") + expect(task("Review and submit pre-application").reload).to be_completed + + sign_out(assessor) + sign_in(reviewer) + + visit "/reports/planning_applications/#{reference}?origin=review_and_submit_pre_application" + + within_fieldset "Do you agree with the advice?" do + choose "No (return the case for assessment)" + fill_in "Reviewer comment", with: "Needs more detail on considerations" + end + + click_button "Confirm and submit pre-application report" + + expect(page).to have_content("Pre-application report has been sent back to the case officer for amendments") + expect(task("Review and submit pre-application").reload).to be_action_required + + sign_out(reviewer) + sign_in(assessor) + + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:action_required_sidebar_task, "Review and submit pre-application") + + within :sidebar do + click_link "Review and submit pre-application" + end + + fill_in "Assessor comment", with: "Added the requested details" + click_button "Confirm and submit recommendation" + + expect(page).to have_content("Pre-application report submitted for review") + expect(task("Review and submit pre-application").reload).to be_completed + + sign_out(assessor) + sign_in(reviewer) + + visit "/reports/planning_applications/#{reference}?origin=review_and_submit_pre_application" + + within_fieldset "Do you agree with the advice?" do + choose "Yes" + end + + click_button "Confirm and submit pre-application report" + + expect(page).to have_content("Pre-application report has been sent to the applicant") + expect(task("Review and submit pre-application").reload).to be_completed + end + end + + describe "site visit recording" do + it "allows adding a site visit with date and comments" do + sign_in(assessor) + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:sidebar) + + within :sidebar do + click_link "Site visit" + end + + expect(task("Site visit")).to be_not_started + + expect(page).to have_content("No site visits have been recorded yet") + + within "#new-site-visit-form" do + click_button "Add site visit" + end + + yesterday = Date.yesterday + within "#new-site-visit-form" do + fill_in "Day", with: yesterday.day + fill_in "Month", with: yesterday.month + fill_in "Year", with: yesterday.year + fill_in "Comment", with: "Inspected front and rear of property" + click_button "Add site visit" + end + + click_button "Save changes" + + expect(task("Site visit").reload).to be_in_progress + expect(page).to have_selector(:in_progress_sidebar_task, "Site visit") + + expect(page).not_to have_content("No site visits have been recorded yet") + + within("#site-visit-history") do + expect(page).to have_content("Inspected front and rear of property") + end + end + end + + describe "check application details with issues" do + it "shows request links when selecting No for checks" do + sign_in(assessor) + visit "/planning_applications/#{reference}/assessment/tasks" + + expect(page).to have_selector(:sidebar) + + within :sidebar do + click_link "Check application details" + end + + within_fieldset("Does the description match the development or use in the plans?") { choose "No" } + + expect(page).to have_link("Request a change to the description") + + within_fieldset("Are the plans consistent with each other?") { choose "No" } + + expect(page).to have_link("Request a new document") + end + end +end diff --git a/engines/bops_preapps/spec/system/consultation_workflow_spec.rb b/engines/bops_preapps/spec/system/consultation_workflow_spec.rb new file mode 100644 index 0000000000..ad2033b2b0 --- /dev/null +++ b/engines/bops_preapps/spec/system/consultation_workflow_spec.rb @@ -0,0 +1,274 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Pre-application consultation workflow", type: :system do + let(:local_authority) { create(:local_authority, :default) } + let(:user) { create(:user, local_authority:, name: "Alice Smith") } + + let(:planning_application) do + create(:planning_application, :pre_application, :not_started, local_authority:) + end + + let(:reference) { planning_application.reference } + + before do + sign_in(user) + end + + describe "end-to-end consultation workflow" do + it "completes all consultation tasks in sequence with correct status transitions and icons" do + visit "/preapps/#{reference}/consultees/determine-consultation-requirement" + + expect(page).to have_selector(:sidebar) + expect(page).to have_content("Consultation") + + expect(task("Determine consultation requirement")).to be_not_started + expect(task("Add and assign consultees")).to be_hidden + expect(task("Send emails to consultees")).to be_hidden + expect(task("View consultee responses")).to be_hidden + + within :sidebar do + expect(page).to have_link("Determine consultation requirement") + expect(page).not_to have_link("Add and assign consultees") + expect(page).not_to have_link("Send emails to consultees") + expect(page).not_to have_link("View consultee responses") + end + + expect(page).to have_selector(:not_started_sidebar_task, "Determine consultation requirement") + expect(page).to have_selector("h1", text: "Determine consultation requirement") + expect(page).to have_selector(:active_sidebar_task, "Determine consultation requirement") + + expect(page).to have_content("Is consultation required?") + expect(page).to have_field("Yes") + expect(page).to have_field("No") + + choose "Yes" + click_button "Save and mark as complete" + + expect(page).to have_content("Consultation requirement was successfully updated") + expect(task("Determine consultation requirement").reload).to be_completed + expect(planning_application.reload.consultation_required).to be true + expect(page).to have_selector(:completed_sidebar_task, "Determine consultation requirement") + + expect(task("Add and assign consultees").reload).not_to be_hidden + expect(task("Send emails to consultees").reload).not_to be_hidden + expect(task("View consultee responses").reload).not_to be_hidden + + within :sidebar do + expect(page).to have_link("Add and assign consultees") + expect(page).to have_link("Send emails to consultees") + expect(page).to have_link("View consultee responses") + end + + expect(page).to have_selector(:not_started_sidebar_task, "Add and assign consultees") + expect(page).to have_selector(:not_started_sidebar_task, "Send emails to consultees") + expect(page).to have_selector(:not_started_sidebar_task, "View consultee responses") + + within :sidebar do + click_link "Add and assign consultees" + end + + expect(page).to have_current_path("/preapps/#{reference}/consultees/add-and-assign-consultees") + expect(page).to have_selector("h1", text: "Add and assign consultees") + expect(page).to have_selector(:active_sidebar_task, "Add and assign consultees") + + expect(page).to have_content("Select constraints that require consultation") + expect(page).to have_content("Assign consultees to each constraint") + + click_button "Save changes" + + expect(page).to have_content("Consultee assignments were successfully saved") + expect(task("Add and assign consultees").reload).to be_in_progress + expect(page).to have_selector(:in_progress_sidebar_task, "Add and assign consultees") + + click_button "Save and mark as complete" + + expect(page).to have_content("Consultee assignments were successfully saved") + expect(task("Add and assign consultees").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Add and assign consultees") + + within :sidebar do + click_link "Send emails to consultees" + end + + expect(page).to have_current_path("/preapps/#{reference}/consultees/send-emails-to-consultees") + expect(page).to have_selector("h1", text: "Send emails to consultees") + expect(page).to have_selector(:active_sidebar_task, "Send emails to consultees") + + task("Send emails to consultees").complete! + + within :sidebar do + click_link "View consultee responses" + end + + expect(page).to have_current_path("/preapps/#{reference}/consultees/view-consultee-responses") + expect(page).to have_selector("h1", text: "View consultee responses") + expect(page).to have_selector(:active_sidebar_task, "View consultee responses") + + expect(page).to have_content("No consultees have been added yet") + + click_button "Save and mark as complete" + + expect(page).to have_content("Consultee responses were successfully reviewed") + expect(task("View consultee responses").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "View consultee responses") + + [ + task("Determine consultation requirement"), + task("Add and assign consultees"), + task("Send emails to consultees"), + task("View consultee responses") + ].each do |t| + expect(t.reload).to be_completed + end + end + + it "hides consultation tasks when consultation is not required" do + visit "/preapps/#{reference}/consultees/determine-consultation-requirement" + + choose "No" + click_button "Save and mark as complete" + + expect(page).to have_content("Consultation requirement was successfully updated") + expect(planning_application.reload.consultation_required).to be false + + expect(task("Add and assign consultees").reload).to be_hidden + expect(task("Send emails to consultees").reload).to be_hidden + expect(task("View consultee responses").reload).to be_hidden + + within :sidebar do + expect(page).not_to have_link("Add and assign consultees") + expect(page).not_to have_link("Send emails to consultees") + expect(page).not_to have_link("View consultee responses") + end + end + + it "navigates correctly between all consultation tasks" do + planning_application.update!(consultation_required: true) + consultees_section.tasks.update_all(hidden: false) + + tasks = [ + {name: "Determine consultation requirement", path: "determine-consultation-requirement"}, + {name: "Add and assign consultees", path: "add-and-assign-consultees"}, + {name: "Send emails to consultees", path: "send-emails-to-consultees"}, + {name: "View consultee responses", path: "view-consultee-responses"} + ] + + visit "/preapps/#{reference}/consultees/determine-consultation-requirement" + + tasks.each do |t| + within :sidebar do + click_link t[:name] + end + + expect(page).to have_current_path("/preapps/#{reference}/consultees/#{t[:path]}") + expect(page).to have_selector(:active_sidebar_task, t[:name]) + end + end + + it "hides buttons when application is determined" do + planning_application.update!(status: "determined", determined_at: Time.current, consultation_required: true) + consultees_section.tasks.update_all(hidden: false) + + visit "/preapps/#{reference}/consultees/determine-consultation-requirement" + + expect(page).not_to have_button("Save and mark as complete") + expect(page).not_to have_button("Save changes") + + visit "/preapps/#{reference}/consultees/add-and-assign-consultees" + + expect(page).not_to have_button("Save and mark as complete") + expect(page).not_to have_button("Save changes") + end + + it "shows warning when changing consultation requirement with existing consultees" do + planning_application.update!(consultation_required: true) + consultation = planning_application.consultation || planning_application.create_consultation! + create(:consultee, consultation:, name: "Test Consultee") + + visit "/preapps/#{reference}/consultees/determine-consultation-requirement" + + expect(page).to have_selector(".govuk-warning-text", text: "Changing this answer to \"No\" will remove all consultees") + end + + it "maintains sidebar scroll position across navigation", js: true do + planning_application.update!(consultation_required: true) + consultees_section.tasks.update_all(hidden: false) + + visit "/preapps/#{reference}/consultees/determine-consultation-requirement" + + within :sidebar do + click_link "View consultee responses" + end + + expect(page).to have_css("nav.bops-sidebar[data-controller='sidebar-scroll']") + + initial_scroll = page.evaluate_script("document.querySelector('nav.bops-sidebar').scrollTop") + + within :sidebar do + click_link "Determine consultation requirement" + end + + final_scroll = page.evaluate_script("document.querySelector('nav.bops-sidebar').scrollTop") + expect(final_scroll).to eq(initial_scroll) + end + end + + describe "consultee response handling" do + let(:consultation) { planning_application.consultation || planning_application.create_consultation! } + let!(:consultee_approved) do + consultee = create(:consultee, :consulted, consultation:, name: "Thames Water", status: :responded) + create(:consultee_response, consultee:, summary_tag: :approved, response: "No objection", email: consultee.email_address) + consultee + end + let!(:consultee_objected) do + consultee = create(:consultee, :consulted, consultation:, name: "Natural England", status: :responded) + create(:consultee_response, consultee:, summary_tag: :objected, response: "We object", email: consultee.email_address) + consultee + end + + before do + planning_application.update!(consultation_required: true) + consultees_section.tasks.update_all(hidden: false) + end + + it "displays consultee responses with correct status tags" do + visit "/preapps/#{reference}/consultees/view-consultee-responses" + + expect(page).to have_content("Response summary") + expect(page).to have_content("Total consultees") + + expect(page).to have_link("All (2)") + expect(page).to have_link("No objection (1)") + expect(page).to have_link("Objection (1)") + + within("#consultee-tab-all") do + within(".consultee-panel", text: "Thames Water") do + expect(page).to have_content("No objection") + end + within(".consultee-panel", text: "Natural England") do + expect(page).to have_content("Objection") + end + end + end + + it "filters consultees by response type when clicking tabs" do + visit "/preapps/#{reference}/consultees/view-consultee-responses" + + click_link "No objection (1)" + + within("#consultee-tab-approved") do + expect(page).to have_content("Thames Water") + expect(page).not_to have_content("Natural England") + end + + click_link "Objection (1)" + + within("#consultee-tab-objected") do + expect(page).to have_content("Natural England") + expect(page).not_to have_content("Thames Water") + end + end + end +end diff --git a/engines/bops_preapps/spec/system/validation_workflow_spec.rb b/engines/bops_preapps/spec/system/validation_workflow_spec.rb new file mode 100644 index 0000000000..cc0f4b5087 --- /dev/null +++ b/engines/bops_preapps/spec/system/validation_workflow_spec.rb @@ -0,0 +1,304 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Pre-application validation workflow", type: :system do + let(:local_authority) { create(:local_authority, :default) } + let(:api_user) { create(:api_user, :planx, local_authority:) } + let(:user) { create(:user, local_authority:, name: "Alice Smith") } + + let(:boundary_geojson) do + { + type: "Feature", + properties: {}, + geometry: { + type: "Polygon", + coordinates: [[[-0.054597, 51.537331], [-0.054588, 51.537287], [-0.054453, 51.537313], [-0.054597, 51.537331]]] + } + } + end + + let(:proposal_details) do + [{"question" => "Planning Pre-Application Advice Services", "responses" => [{"value" => "Householder (£100)"}], "metadata" => {}}] + end + + let(:planning_application) do + create(:planning_application, :pre_application, :not_started, :with_constraints, + local_authority:, + api_user:, + boundary_geojson:, + proposal_details:) + end + + let(:reference) { planning_application.reference } + + before do + sign_in(user) + end + + describe "end-to-end validation workflow" do + it "completes all validation tasks in sequence with correct status transitions and icons" do + visit "/planning_applications/#{reference}/validation/tasks" + + expect(page).to have_selector(:sidebar) + expect(page).to have_content("Validation") + + within :sidebar do + expect(page).to have_content("Check, tag, and confirm documents") + expect(page).to have_content("Check application details") + expect(page).to have_content("Other validation issues") + expect(page).to have_content("Review") + end + + validation_tasks.each do |t| + expect(t).to be_not_started + end + + within :sidebar do + expect(page).to have_css("svg[aria-label='Not started']", minimum: 7) + end + + within :sidebar do + click_link "Review documents" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/check-tag-and-confirm-documents/review-documents") + expect(page).to have_selector("h1", text: "Review documents") + expect(page).to have_selector(:active_sidebar_task, "Review documents") + + expect(page).to have_content("There are no active documents") + + click_button "Save and mark as complete" + + expect(page).to have_content("Successfully updated document review") + expect(task("Review documents").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Review documents") + + within :sidebar do + click_link "Check red line boundary" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/check-application-details/check-red-line-boundary") + expect(page).to have_content("Check the digital red line boundary") + expect(page).to have_selector(:active_sidebar_task, "Check red line boundary") + + expect(page).to have_field("Yes") + expect(page).to have_field("No") + + choose "Yes" + click_button "Save and mark as complete" + + expect(page).to have_content("Red line boundary check was successfully saved") + expect(task("Check red line boundary").reload).to be_completed + expect(planning_application.reload.valid_red_line_boundary).to be true + expect(page).to have_selector(:completed_sidebar_task, "Check red line boundary") + + within :sidebar do + click_link "Check constraints" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/check-application-details/check-constraints") + expect(page).to have_content("Check constraints") + expect(page).to have_selector(:active_sidebar_task, "Check constraints") + + within(".identified-constraints-table") do + expect(page).to have_text("Conservation area") + expect(page).to have_text("Listed building outline") + end + + click_button "Save and mark as complete" + + expect(page).to have_content("Constraints were successfully marked as reviewed") + expect(task("Check constraints").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Check constraints") + + within :sidebar do + click_link "Check description" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/check-application-details/check-description") + expect(page).to have_selector("h1", text: "Check description") + expect(page).to have_selector(:active_sidebar_task, "Check description") + + expect(page).to have_content("Does the description match the development or use in the plans?") + + choose "Yes" + click_button "Save and mark as complete" + + expect(page).to have_content("Description check was successfully saved") + expect(task("Check description").reload).to be_completed + expect(planning_application.reload.valid_description).to be true + expect(page).to have_selector(:completed_sidebar_task, "Check description") + + within :sidebar do + click_link "Add reporting details" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/check-application-details/add-reporting-details") + expect(page).to have_selector("h1", text: "Add reporting details") + expect(page).to have_selector(:active_sidebar_task, "Add reporting details") + + expect(page).to have_content("Is the local planning authority the owner of this land?") + + page.first(:radio_button, "No").choose + + click_button "Save and mark as complete" + + expect(page).to have_content("Reporting details were successfully saved") + expect(task("Add reporting details").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Add reporting details") + + within :sidebar do + click_link "Check fee" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/check-application-details/check-fee") + expect(page).to have_content("Check the application fee") + expect(page).to have_selector(:active_sidebar_task, "Check fee") + + expect(page).to have_content("Payment information") + expect(page).to have_content("Fee calculation") + expect(page).to have_content("Householder") + expect(page).to have_content("£100") + + choose "Yes" + click_button "Save and mark as complete" + + expect(page).to have_content("Fee check was successfully saved") + expect(task("Check fee").reload).to be_completed + expect(planning_application.reload.valid_fee).to be true + expect(page).to have_selector(:completed_sidebar_task, "Check fee") + + within :sidebar do + click_link "Other validation requests" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/other-validation-issues/other-validation-requests") + expect(page).to have_selector("h1", text: "Other validation requests") + expect(page).to have_selector(:active_sidebar_task, "Other validation requests") + + expect(page).to have_content("No other validation requests have been added") + + click_button "Save and mark as complete" + + expect(page).to have_content("Other validation requests was successfully saved") + expect(task("Other validation requests").reload).to be_completed + expect(page).to have_selector(:completed_sidebar_task, "Other validation requests") + + within :sidebar do + click_link "Review validation requests" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/review/review-validation-requests") + expect(page).to have_selector("h1", text: "Review validation requests") + expect(page).to have_selector(:active_sidebar_task, "Review validation requests") + + expect(page).to have_content("There are no active validation requests") + + within :sidebar do + click_link "Send validation decision" + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/review/send-validation-decision") + expect(page).to have_selector("h1", text: "Send validation decision") + expect(page).to have_selector(:active_sidebar_task, "Send validation decision") + + expect(page).to have_content("The application has not been marked as valid or invalid yet") + expect(page).to have_button("Mark the application as valid") + + click_button "Mark the application as valid" + + expect(page).to have_content("The application is marked as valid") + expect(task("Send validation decision").reload).to be_completed + expect(planning_application.reload).to be_valid + expect(page).to have_selector(:completed_sidebar_task, "Send validation decision") + end + + it "shows in progress status when task is partially completed" do + visit "/planning_applications/#{reference}/validation/tasks" + + within :sidebar do + click_link "Check constraints" + end + + click_button "Save changes" + + expect(page).to have_content("Constraints were successfully marked as reviewed") + expect(task("Check constraints").reload).to be_in_progress + expect(page).to have_selector(:in_progress_sidebar_task, "Check constraints") + end + + it "handles validation request flow with status transitions" do + visit "/planning_applications/#{reference}/validation/tasks" + + within :sidebar do + click_link "Check description" + end + + choose "No" + click_button "Save and mark as complete" + + expect(page).to have_current_path("/planning_applications/#{reference}/validation/validation_requests/new?type=description_change") + + expect(task("Check description").reload).to be_in_progress + expect(planning_application.reload.valid_description).to be false + + visit "/planning_applications/#{reference}/validation/tasks" + + expect(page).to have_selector(:in_progress_sidebar_task, "Check description") + end + + it "navigates correctly between all validation task sections" do + visit "/planning_applications/#{reference}/validation/tasks" + + sections = [ + {name: "Review documents", path: "check-tag-and-confirm-documents/review-documents"}, + {name: "Check red line boundary", path: "check-application-details/check-red-line-boundary"}, + {name: "Check constraints", path: "check-application-details/check-constraints"}, + {name: "Check description", path: "check-application-details/check-description"}, + {name: "Add reporting details", path: "check-application-details/add-reporting-details"}, + {name: "Check fee", path: "check-application-details/check-fee"}, + {name: "Other validation requests", path: "other-validation-issues/other-validation-requests"}, + {name: "Review validation requests", path: "review/review-validation-requests"}, + {name: "Send validation decision", path: "review/send-validation-decision"} + ] + + sections.each do |section| + within :sidebar do + click_link section[:name] + end + + expect(page).to have_current_path("/preapps/#{reference}/check-and-validate/#{section[:path]}") + expect(page).to have_selector(:active_sidebar_task, section[:name]) + end + end + + it "hides buttons when application is determined" do + planning_application.update!(status: "determined", determined_at: Time.current) + + visit "/preapps/#{reference}/check-and-validate/check-application-details/check-description" + + expect(page).not_to have_button("Save and mark as complete") + expect(page).not_to have_button("Save changes") + end + + it "maintains sidebar scroll position across navigation", js: true do + visit "/planning_applications/#{reference}/validation/tasks" + + within :sidebar do + click_link "Send validation decision" + end + + expect(page).to have_css("nav.bops-sidebar[data-controller='sidebar-scroll']") + + initial_scroll = page.evaluate_script("document.querySelector('nav.bops-sidebar').scrollTop") + + within :sidebar do + click_link "Review documents" + end + + final_scroll = page.evaluate_script("document.querySelector('nav.bops-sidebar').scrollTop") + expect(final_scroll).to eq(initial_scroll) + end + end +end diff --git a/spec/support/preapp_task_helpers.rb b/spec/support/preapp_task_helpers.rb new file mode 100644 index 0000000000..bd3e517841 --- /dev/null +++ b/spec/support/preapp_task_helpers.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +module PreappTaskHelpers + extend ActiveSupport::Concern + + class TaskDefinitions + WORKFLOW_FILE = Rails.root.join("config/task_workflows/pre_application.yml") + + Task = Struct.new(:name, :slug_path, :section, :hidden, keyword_init: true) do + def visible? + !hidden + end + end + + class << self + def all_tasks + @all_tasks ||= flatten_workflow + end + + def find_by_name(name) + all_tasks.find { |t| t.name == name } + end + + def visible_tasks_for_section(section_name) + all_tasks.select { |t| t.section == section_name && t.visible? } + end + + private + + def workflow + @workflow ||= YAML.load_file(WORKFLOW_FILE) + end + + def flatten_workflow + workflow.flat_map do |section| + collect_tasks(section["tasks"], section["name"].parameterize, section["section"]) + end + end + + def collect_tasks(nodes, parent_slug, section_name) + return [] unless nodes + + nodes.flat_map do |node| + slug = node["name"].parameterize + full_slug = [parent_slug, slug].compact.join("/") + + if node["tasks"] + collect_tasks(node["tasks"], full_slug, section_name) + else + Task.new( + name: node["name"], + slug_path: full_slug, + section: section_name, + hidden: node["hidden"] || node["status_hidden"] + ) + end + end + end + end + end + + # Usage: task("Site description"), task("Add and assign consultees"), etc. + def task(name) + task_cache[name] + end + + def task_cache + @task_cache ||= Hash.new(&method(:find_task)) + end + + def find_task(tasks, name) + if (definition = TaskDefinitions.find_by_name(name)) + tasks[name] = planning_application.case_record.find_task_by_slug_path!(definition.slug_path) + else + raise "Unknown task: #{name}. Available: #{available_tasks}" + end + end + + def available_tasks + TaskDefinitions.all_tasks.map(&:name).join(", ") + end + + def assessment_tasks + TaskDefinitions.visible_tasks_for_section("Assessment").map { |t| task(t.name) } + end + + def validation_tasks + TaskDefinitions.visible_tasks_for_section("Validation").map { |t| task(t.name) } + end + + def consultees_section + @consultees_section ||= planning_application.case_record.find_task_by_slug_path!("consultees") + end +end + +RSpec.configure do |config| + config.include PreappTaskHelpers, type: :system +end diff --git a/spec/support/sidebar_selectors.rb b/spec/support/sidebar_selectors.rb new file mode 100644 index 0000000000..3e02ddd5e1 --- /dev/null +++ b/spec/support/sidebar_selectors.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +# Custom Capybara selectors for sidebar navigation in workflow specs +# These provide cleaner, more semantic ways to interact with the sidebar + +Capybara.add_selector(:sidebar) do + css { "nav.bops-sidebar" } +end + +Capybara.add_selector(:active_sidebar_task) do + xpath { |name| ".//li[contains(@class, 'bops-sidebar__task--active')][.//a[normalize-space()='#{name}']]" } +end + +Capybara.add_selector(:completed_sidebar_task) do + xpath { |name| ".//li[contains(@class, 'bops-sidebar__task')][.//svg[@aria-label='Completed']][.//a[normalize-space()='#{name}']]" } +end + +Capybara.add_selector(:in_progress_sidebar_task) do + xpath { |name| ".//li[contains(@class, 'bops-sidebar__task')][.//svg[@aria-label='In progress']][.//a[normalize-space()='#{name}']]" } +end + +Capybara.add_selector(:not_started_sidebar_task) do + xpath { |name| ".//li[contains(@class, 'bops-sidebar__task')][.//svg[@aria-label='Not started']][.//a[normalize-space()='#{name}']]" } +end + +Capybara.add_selector(:action_required_sidebar_task) do + xpath { |name| ".//li[contains(@class, 'bops-sidebar__task')][.//svg[@aria-label='Action required']][.//a[normalize-space()='#{name}']]" } +end