diff --git a/app/controllers/forms/exit_pages_controller.rb b/app/controllers/forms/exit_pages_controller.rb
new file mode 100644
index 000000000..22f4f339f
--- /dev/null
+++ b/app/controllers/forms/exit_pages_controller.rb
@@ -0,0 +1,10 @@
+module Forms
+ class ExitPagesController < PageController
+ def show
+ return redirect_to form_page_path(@step.form_id, @step.form_slug, current_context.next_page_slug) unless current_context.can_visit?(@step.page_slug)
+
+ @back_link = form_page_path(@step.form_id, @step.form_slug, @step.page_slug)
+ @condition = @step.routing_conditions.first
+ end
+ end
+end
diff --git a/app/controllers/forms/page_controller.rb b/app/controllers/forms/page_controller.rb
index 7b88ea75a..b8627a598 100644
--- a/app/controllers/forms/page_controller.rb
+++ b/app/controllers/forms/page_controller.rb
@@ -72,6 +72,7 @@ def back_link(page_slug)
def redirect_post_save
return redirect_to review_file_page, success: t("banner.success.file_uploaded") if answered_file_question?
+ return redirect_to exit_page_path(form_id: @step.form_id, form_slug: @step.form_slug, page_slug: @step.page_slug) if @step.exit_page_condition_matches?
redirect_to next_page
end
diff --git a/app/models/step.rb b/app/models/step.rb
index 96a794738..615a19ab2 100644
--- a/app/models/step.rb
+++ b/app/models/step.rb
@@ -79,6 +79,10 @@ def end_page?
end
def next_page_slug_after_routing
+ if exit_page_condition_matches?
+ return nil
+ end
+
if first_condition_default?
return goto_condition_page_slug(routing_conditions.first)
end
@@ -106,6 +110,16 @@ def conditions_with_goto_errors
end
end
+ def has_exit_page_condition?
+ return false unless routing_conditions&.first.respond_to?(:exit_page_markdown)
+
+ routing_conditions.first.exit_page_markdown.is_a?(String)
+ end
+
+ def exit_page_condition_matches?
+ first_condition_matches? && has_exit_page_condition?
+ end
+
private
def goto_condition_page_slug(condition)
diff --git a/app/views/forms/exit_pages/show.html.erb b/app/views/forms/exit_pages/show.html.erb
new file mode 100644
index 000000000..83f90ab4d
--- /dev/null
+++ b/app/views/forms/exit_pages/show.html.erb
@@ -0,0 +1,13 @@
+<% set_page_title(form_title(form_name: @current_context.form.name, page_name: @condition.exit_page_heading, mode: @mode)) %>
+
+<% content_for :back_link do %>
+ <%= link_to "Back", @back_link, class: "govuk-back-link" %>
+<% end %>
+
+
+
+
<%= @condition.exit_page_heading %>
+ <%= HtmlMarkdownSanitizer.new.render_scrubbed_markdown(@condition.exit_page_markdown) %>
+ <%= render SupportDetailsComponent::View.new(@support_details) %>
+
+
diff --git a/config/routes.rb b/config/routes.rb
index a4a20f12f..b340a2687 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -27,6 +27,10 @@
answer_constraints = { answer_index: /\d+/ }
page_answer_defaults = { answer_index: 1 }
+ get "/:page_slug/exit" => "forms/exit_pages#show",
+ as: :exit_page,
+ constraints: page_constraints
+
get "/:page_slug/add-another-answer/change" => "forms/add_another_answer#show",
as: :change_add_another_answer,
constraints: page_constraints,
diff --git a/spec/features/fill_in_form_with_exit_page_spec.rb b/spec/features/fill_in_form_with_exit_page_spec.rb
new file mode 100644
index 000000000..0553e6c7c
--- /dev/null
+++ b/spec/features/fill_in_form_with_exit_page_spec.rb
@@ -0,0 +1,105 @@
+require "rails_helper"
+
+feature "Fill in and submit a form with an exit page", type: :feature do
+ let(:routing_conditions) { [DataStruct.new(routing_page_id: 1, check_page_id: 1, answer_value: "Option 1", goto_page_id: nil, exit_page_heading: "This is an exit_page", exit_page_markdown: "This is the contents", validation_errors: [])] }
+ let(:steps) { [(build :v2_question_page_step, :with_selections_settings, id: 1, routing_conditions:, question_text:)] }
+ let(:form) { build :v2_form_document, :live?, id: 1, name: "Fill in this form", steps:, start_page: 1 }
+ let(:question_text) { Faker::Lorem.question }
+ let(:reference) { Faker::Alphanumeric.alphanumeric(number: 8).upcase }
+
+ let(:req_headers) do
+ {
+ "X-API-Token" => Settings.forms_api.auth_key,
+ "Accept" => "application/json",
+ }
+ end
+
+ let(:post_headers) do
+ {
+ "X-API-Token" => Settings.forms_api.auth_key,
+ "Content-Type" => "application/json",
+ }
+ end
+
+ before do
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/api/v2/forms/1/live", req_headers, form.to_json, 200
+ end
+
+ allow(ReferenceNumberService).to receive(:generate).and_return(reference)
+ end
+
+ scenario "As a form filler" do
+ when_i_visit_the_form_start_page
+ then_i_should_see_the_first_question
+
+ when_i_choose_the_exit_option
+ and_i_click_on_continue
+ then_i_should_see_the_exit_page
+
+ when_i_click_back
+ when_i_dont_choose_the_exit_option
+ and_i_click_on_continue
+ then_i_should_see_the_check_your_answers_page
+
+ when_i_opt_out_of_email_confirmation
+ and_i_submit_my_form
+ then_my_form_should_be_submitted
+ and_i_should_receive_a_reference_number
+ end
+
+ def when_i_visit_the_form_start_page
+ visit form_path(mode: "form", form_id: 1, form_slug: "fill-in-this-form")
+ expect_page_to_have_no_axe_errors(page)
+ end
+
+ def then_i_should_see_the_first_question
+ expect(page.find("h1")).to have_text question_text
+ end
+
+ def when_i_choose_the_exit_option
+ choose "Option 1"
+ end
+
+ def when_i_dont_choose_the_exit_option
+ choose "Option 2"
+ end
+
+ def and_i_click_on_continue
+ click_button "Continue"
+ end
+
+ def then_i_should_see_the_check_your_answers_page
+ expect(page.find("h1")).to have_text "Check your answers before submitting your form"
+ expect(page).to have_text question_text
+ expect(page).to have_text "Option 2"
+ expect_page_to_have_no_axe_errors(page)
+ end
+
+ def when_i_click_back
+ click_on "Back"
+ end
+
+ def then_i_should_see_the_exit_page
+ expect(page.find("h1")).to have_text "This is an exit_page"
+ expect(page).to have_text "This is the contents"
+ expect_page_to_have_no_axe_errors(page)
+ end
+
+ def when_i_opt_out_of_email_confirmation
+ choose "No"
+ end
+
+ def and_i_submit_my_form
+ click_on "Submit"
+ end
+
+ def then_my_form_should_be_submitted
+ expect(page.find("h1")).to have_text "Your form has been submitted"
+ expect_page_to_have_no_axe_errors(page)
+ end
+
+ def and_i_should_receive_a_reference_number
+ expect(page).to have_text reference
+ end
+end
diff --git a/spec/models/step_spec.rb b/spec/models/step_spec.rb
index 34f5167d4..2eb5b2f92 100644
--- a/spec/models/step_spec.rb
+++ b/spec/models/step_spec.rb
@@ -420,4 +420,49 @@
end
end
end
+
+ describe "#has_exit_page_condition?" do
+ it "returns false when no routing conditions" do
+ expect(step.has_exit_page_condition?).to be false
+ end
+
+ it "returns false when first routing condition is not exit page" do
+ page.routing_conditions = [OpenStruct.new(answer_value: "Yes", goto_page_id: "5")]
+ expect(step.has_exit_page_condition?).to be false
+ end
+
+ it "returns false when first routing condition contains markdown exit_page_markdown" do
+ page.routing_conditions = [OpenStruct.new(exit_page_markdown: 12)]
+ expect(step.has_exit_page_condition?).to be false
+ end
+
+ it "returns true when first routing condition contains string markdown exit_page_markdown" do
+ page.routing_conditions = [OpenStruct.new(exit_page_markdown: "")]
+ expect(step.has_exit_page_condition?).to be true
+ end
+ end
+
+ describe "#exit_page_condition_matches?" do
+ let(:selection) { "Yes" }
+ let(:question) { instance_double(Question::Selection, selection:) }
+ let(:routing_conditions) { [OpenStruct.new(answer_value: "Yes", exit_page_markdown: "string")] }
+ let(:page) { build(:page, id: 2, position: 1, routing_conditions:) }
+
+ it "returns true when condition matches and condition is an exit page" do
+ expect(step.exit_page_condition_matches?).to be true
+ end
+
+ it "when condition matches but not an exit page it returns false" do
+ routing_conditions.first.exit_page_markdown = nil
+ expect(step.exit_page_condition_matches?).to be false
+ end
+
+ context "when condition doesn't match" do
+ let(:selection) { "No" }
+
+ it "returns false" do
+ expect(step.exit_page_condition_matches?).to be false
+ end
+ end
+ end
end
diff --git a/spec/requests/forms/page_controller_spec.rb b/spec/requests/forms/page_controller_spec.rb
index dba6da398..993093d92 100644
--- a/spec/requests/forms/page_controller_spec.rb
+++ b/spec/requests/forms/page_controller_spec.rb
@@ -651,6 +651,26 @@
end
end
end
+
+ context "when the page is a an exit question" do
+ let(:first_step_in_form) do
+ build :v2_question_page_step, :with_selections_settings,
+ id: 1,
+ next_step_id: 2,
+ routing_conditions: [DataStruct.new(id: 1, routing_page_id: 1, check_page_id: 1, goto_page_id: nil, answer_value: "Option 1", validation_errors: [], exit_page_markdown: "Exit page markdown", exit_page_heading: "exit page heading")],
+ is_optional: false
+ end
+
+ it "redirects to the exit page when exit page answer given" do
+ post save_form_page_path(mode:, form_id: 2, form_slug: form_data.form_slug, page_slug: 1, params: { question: { selection: "Option 1" }, changing_existing_answer: false })
+ expect(response).to redirect_to exit_page_path(mode:, form_id: 2, form_slug: form_data.form_slug, page_slug: 1)
+ end
+
+ it "redirects to the next step in the form when any other answer given" do
+ post save_form_page_path(mode:, form_id: 2, form_slug: form_data.form_slug, page_slug: 1, params: { question: { selection: "Option 2" }, changing_existing_answer: false })
+ expect(response).to redirect_to form_page_path(mode:, form_id: 2, form_slug: form_data.form_slug, page_slug: 2)
+ end
+ end
end
def log_lines
diff --git a/spec/views/forms/exit_pages/show.html.erb_spec.rb b/spec/views/forms/exit_pages/show.html.erb_spec.rb
new file mode 100644
index 000000000..08236329e
--- /dev/null
+++ b/spec/views/forms/exit_pages/show.html.erb_spec.rb
@@ -0,0 +1,38 @@
+require "rails_helper"
+
+describe "forms/exit_pages/show.html.erb" do
+ let(:form) { build :form, :with_support, id: 1, name: "exit page form" }
+ let(:mode) { OpenStruct.new(preview_draft?: false, preview_archived?: false, preview_live?: false) }
+ let(:condition) { OpenStruct.new({ exit_page_heading: "heading", exit_page_markdown: " * first line\n * second line\n" }) }
+ let(:support_details) { OpenStruct.new(email: form.support_email) }
+
+ before do
+ assign(:current_context, OpenStruct.new(form:))
+ assign(:mode, mode)
+ assign(:condition, condition)
+ assign(:back_link, "/back")
+ assign(:support_details, support_details)
+
+ render
+ end
+
+ it "has the correct title" do
+ expect(view.content_for(:title)).to eq "heading - exit page form"
+ end
+
+ it "has a back link" do
+ expect(view.content_for(:back_link)).to have_link("Back", href: "/back")
+ end
+
+ it "has the correct heading" do
+ expect(rendered).to have_css("h1", text: condition.exit_page_heading)
+ end
+
+ it "displays the markdown" do
+ expect(rendered).to have_css("li", text: "second line")
+ end
+
+ it "displays the help link" do
+ expect(rendered).to have_text(I18n.t("support_details.get_help_with_this_form"))
+ end
+end