From d678b309cb52611f01f6f027d664b676040400ef Mon Sep 17 00:00:00 2001 From: Samuel Culley Date: Tue, 27 Oct 2026 11:44:50 +0000 Subject: [PATCH 1/2] Add RoutesSectionComponent Add a component for a routes section on the edit question page, which links to the question's routes. Selection questions with only one option can have conditional routes, so the section mentions exiting the form based on the answer. Other questions can have unconditional routes, which let people skip later questions, so the section isn't shown on the last question in the form. If the question already has routes, the section says so, including on the last question. Co-Authored-By: Claude Opus 5.5 --- .../routes_section_component/view.html.erb | 3 + .../routes_section_component/view.rb | 35 ++++++ config/locales/en.yml | 5 + .../routes_section_component_preview.rb | 24 ++++ .../routes_section_component/view_spec.rb | 119 ++++++++++++++++++ 5 files changed, 186 insertions(+) create mode 100644 app/components/routes_section_component/view.html.erb create mode 100644 app/components/routes_section_component/view.rb create mode 100644 spec/components/routes_section_component/routes_section_component_preview.rb create mode 100644 spec/components/routes_section_component/view_spec.rb diff --git a/app/components/routes_section_component/view.html.erb b/app/components/routes_section_component/view.html.erb new file mode 100644 index 000000000..db788650c --- /dev/null +++ b/app/components/routes_section_component/view.html.erb @@ -0,0 +1,3 @@ +

<%= t("pages.routes_section.heading") %>

+

<%= t("pages.routes_section.#{message_key}", routes_path: routes_path(form_id: @page.form_id, anchor: @page.page_position_id)) %>

+
diff --git a/app/components/routes_section_component/view.rb b/app/components/routes_section_component/view.rb new file mode 100644 index 000000000..1542e2daa --- /dev/null +++ b/app/components/routes_section_component/view.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module RoutesSectionComponent + class View < ApplicationComponent + include Rails.application.routes.url_helpers + + def initialize(page:, draft_question:) + super() + @page = page + @draft_question = draft_question + end + + def render? + message_key.present? + end + + def message_key + @message_key ||= if @page.routing_conditions.any? + "has_routes_html" + elsif selection_question_with_one_option_only? + "conditional_routes_html" + elsif @page.has_next_page? + "skip_questions_html" + end + end + + private + + def selection_question_with_one_option_only? + @draft_question.answer_type == "selection" && + @draft_question.answer_settings[:only_one_option] == "true" && + @draft_question.answer_settings[:selection_options].length <= 10 + end + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 5f13ac5f5..0e1ed0b7d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2070,6 +2070,11 @@ en: skip_later: If you need to, you can make them skip one or more questions that only people on route 1 need to answer. will_continue_to: People who select any other answer will continue to question %{next_question_number} and through the rest of the form. will_continue_to_end_of_form: People who select any other answer will continue to the end of the form. + routes_section: + conditional_routes_html: If you need people to skip some questions or exit the form based on their answer to this question, edit question routes. + has_routes_html: 'This question has routes: Edit question routes' + heading: Routes + skip_questions_html: If you need people to skip some questions after this one, edit question routes. submit_save: Save question payment_link_input: body_html: | diff --git a/spec/components/routes_section_component/routes_section_component_preview.rb b/spec/components/routes_section_component/routes_section_component_preview.rb new file mode 100644 index 000000000..89e8c4f6c --- /dev/null +++ b/spec/components/routes_section_component/routes_section_component_preview.rb @@ -0,0 +1,24 @@ +class RoutesSectionComponent::RoutesSectionComponentPreview < ViewComponent::Preview + include FactoryBot::Syntax::Methods + + def with_selection_question_with_one_option_only + page = build(:page, id: 1, form_id: 1, position: 1, routing_conditions: []) + draft_question = build(:selection_draft_question, only_one_option: "true") + render(RoutesSectionComponent::View.new(page:, draft_question:)) + end + + def with_question_that_is_not_selection_with_one_option_only + page = build(:page, id: 1, form_id: 1, position: 1, routing_conditions: []) + draft_question = build(:draft_question, answer_type: "email") + page.define_singleton_method(:has_next_page?) { true } + + render(RoutesSectionComponent::View.new(page:, draft_question:)) + end + + def with_routes + condition = build(:condition, id: 1, routing_page_id: 1, check_page_id: 1, answer_value: "Option 1", skip_to_end: true) + page = build(:page, id: 1, form_id: 1, position: 1, routing_conditions: [condition]) + draft_question = build(:selection_draft_question, only_one_option: "true") + render(RoutesSectionComponent::View.new(page:, draft_question:)) + end +end diff --git a/spec/components/routes_section_component/view_spec.rb b/spec/components/routes_section_component/view_spec.rb new file mode 100644 index 000000000..8e3e3b834 --- /dev/null +++ b/spec/components/routes_section_component/view_spec.rb @@ -0,0 +1,119 @@ +require "rails_helper" + +RSpec.describe RoutesSectionComponent::View, type: :component do + include Rails.application.routes.url_helpers + + let(:question_page) { create :page } + let(:form) { create :form, pages: [question_page, create(:page)] } + let(:draft_question) { build :draft_question, answer_type: "email" } + let(:question_routes_path) { routes_path(form_id: form.id, anchor: question_page.page_position_id) } + + before do + form + render_inline(described_class.new(page: question_page, draft_question:)) + end + + context "when the question has no routes" do + context "when the question is a selection question with one option only" do + let(:draft_question) { build :selection_draft_question, only_one_option: "true" } + + it "contains the routes heading" do + expect(page).to have_css("h2", text: I18n.t("pages.routes_section.heading")) + end + + it "contains a link to the question's routes" do + expect(page).to have_link("edit question routes", href: question_routes_path) + end + end + + context "when the question is a selection question with one option only and more than 10 options" do + let(:draft_question) { build :selection_draft_question, only_one_option: "true", selection_options: (1..11).map { |i| { name: "Option #{i}", value: "option #{i}" } } } + + it "contains the text about skipping questions" do + expect(page).to have_text("If you need people to skip some questions after this one, edit question routes.") + end + + context "when the question is the last question in the form" do + let(:form) { create :form, pages: [question_page] } + + it "does not render" do + expect(page).not_to have_css("h2", text: I18n.t("pages.routes_section.heading")) + end + end + end + + context "when the question is a selection question with multiple options" do + let(:draft_question) { build :selection_draft_question, only_one_option: "false" } + + it "contains the routes heading" do + expect(page).to have_css("h2", text: I18n.t("pages.routes_section.heading")) + end + + it "contains the text about skipping questions" do + expect(page).to have_text("If you need people to skip some questions after this one, edit question routes.") + end + + it "contains a link to the question's routes" do + expect(page).to have_link("edit question routes", href: question_routes_path) + end + end + + context "when the question is not a selection question" do + it "contains the routes heading" do + expect(page).to have_css("h2", text: I18n.t("pages.routes_section.heading")) + end + + it "contains the text about skipping questions" do + expect(page).to have_text("If you need people to skip some questions after this one, edit question routes.") + end + + it "contains a link to the question's routes" do + expect(page).to have_link("edit question routes", href: question_routes_path) + end + end + + context "when the question is not a selection question and is the last question in the form" do + let(:form) { create :form, pages: [question_page] } + + it "does not render" do + expect(page).not_to have_css("h2", text: I18n.t("pages.routes_section.heading")) + end + end + end + + context "when the question has routes" do + let(:other_page) { create :page } + let(:form) do + create(:form, pages: [question_page, other_page]).tap do + create :condition, routing_page: question_page, check_page: question_page, answer_value: "Option 1", goto_page: other_page + question_page.reload + end + end + let(:draft_question) { build :selection_draft_question, only_one_option: "true" } + + it "contains the routes heading" do + expect(page).to have_css("h2", text: I18n.t("pages.routes_section.heading")) + end + + it "contains the text saying the question has routes" do + expect(page).to have_text("This question has routes: Edit question routes") + end + + it "contains a link to the question's routes" do + expect(page).to have_link("Edit question routes", href: question_routes_path) + end + + context "when the question is the last question in the form" do + let(:form) do + create(:form, pages: [question_page]).tap do + create :condition, routing_page: question_page, check_page: question_page, answer_value: "Option 1", skip_to_end: true + question_page.reload + end + end + + it "contains the text saying the question has routes" do + expect(page).to have_text("This question has routes: Edit question routes") + end + end + end +end From 585dcc36d50ff26794c21b4cef4009afd7a78b8c Mon Sep 17 00:00:00 2001 From: Samuel Culley Date: Tue, 27 Oct 2026 11:45:00 +0000 Subject: [PATCH 2/2] Add routes section to edit question page Co-Authored-By: Claude Opus 5.5 --- app/views/pages/_form.html.erb | 2 ++ spec/views/pages/edit.html.erb_spec.rb | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/app/views/pages/_form.html.erb b/app/views/pages/_form.html.erb index bb4b64612..0e5383f22 100644 --- a/app/views/pages/_form.html.erb +++ b/app/views/pages/_form.html.erb @@ -52,6 +52,8 @@

<%= t("pages.answer_settings") %>

<%= render PageSettingsSummaryComponent::View.new(draft_question:, errors: question_input.errors) %> + <%= render RoutesSectionComponent::View.new(page: page_object, draft_question:) unless is_new_page %> +
  • <%= f.govuk_submit t('pages.submit_save'), class: "govuk-!-margin-bottom-3", name: "submit_type", value: "save" %> diff --git a/spec/views/pages/edit.html.erb_spec.rb b/spec/views/pages/edit.html.erb_spec.rb index f3bf777a3..c7b67f665 100644 --- a/spec/views/pages/edit.html.erb_spec.rb +++ b/spec/views/pages/edit.html.erb_spec.rb @@ -67,4 +67,16 @@ expect(rendered).not_to have_text(I18n.t("helpers.hint.pages_question_input.is_optional_options.true")) end end + + describe "routes section" do + let(:form) { create :form, pages: [page, create(:page)] } + + it "contains the routes section" do + expect(rendered).to have_css("h2", text: I18n.t("pages.routes_section.heading")) + end + + it "contains a link to the question's routes" do + expect(rendered).to have_link("edit question routes", href: routes_path(form_id: form.id, anchor: page.page_position_id)) + end + end end