Skip to content

Commit 5780eb6

Browse files
authored
Merge pull request #1807 from alphagov/show-none-of-the-above-question-for-autocomplete
Show none of the above question on a separate page for long list selection questions
2 parents 548920e + 736eeb6 commit 5780eb6

File tree

20 files changed

+574
-61
lines changed

20 files changed

+574
-61
lines changed

app/components/check_your_answers_component/view.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def step_to_row(step)
2929
def none_of_the_above_answer_row(step)
3030
question_name = step.question.none_of_the_above_question_text
3131
answer = step.question.none_of_the_above_answer
32-
row(question_name:, answer:, change_link: change_link(step))
32+
row(question_name:, answer:, change_link: none_of_the_above_change_link(step))
3333
end
3434

3535
def row(question_name:, answer:, change_link:)
@@ -55,5 +55,13 @@ def change_link(step)
5555
form_change_answer_path(mode: @mode, form_id: @form.id, form_slug: @form.form_slug, page_slug: step.id)
5656
end
5757
end
58+
59+
def none_of_the_above_change_link(step)
60+
if step.autocomplete_selection_question?
61+
change_selection_none_of_the_above_path(mode: @mode, form_id: @form.id, form_slug: @form.form_slug, page_slug: step.id)
62+
else
63+
form_change_answer_path(mode: @mode, form_id: @form.id, form_slug: @form.form_slug, page_slug: step.id)
64+
end
65+
end
5866
end
5967
end

app/controllers/forms/page_controller.rb

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,22 @@ def set_request_logging_attributes
1010

1111
def show
1212
return redirect_to form_page_path(@form.id, @form.form_slug, current_context.next_page_slug) unless current_context.can_visit?(@step.id)
13-
return redirect_to review_file_page if answered_file_question?
13+
return redirect_to review_file_page if @step.answered_file_question?
1414

15-
back_link(@step.id)
1615
setup_instance_vars_for_view
1716
end
1817

1918
def save
2019
page_params = params.fetch(:question, {}).permit(*@step.params)
21-
@step.update!(page_params)
20+
@step.assign_question_attributes(page_params)
2221

23-
if current_context.save_step(@step)
24-
current_context.clear_submission_details if is_first_page?
22+
current_context.clear_submission_details if is_first_page?
23+
24+
validation_context = @step.autocomplete_selection_question? ? :skip_none_of_the_above_question_validation : nil
25+
if current_context.save_step(@step, context: validation_context)
26+
# Redirect before logging when the question has multiple pages so that we don't send multiple form started
27+
# metrics to CloudWatch if this is the first question.
28+
return redirect_to selection_none_of_the_above_page if redirect_to_none_of_the_above_page?
2529

2630
unless mode.preview?
2731
LogEventService.new(current_context, @step, request, changing_existing_answer, page_params).log_page_save
@@ -56,46 +60,61 @@ def answer_index
5660
end
5761

5862
def setup_instance_vars_for_view
59-
@is_question = true
60-
@question_edit_link = "#{Settings.forms_admin.base_url}/forms/#{@form.id}/pages-by-external-id/#{@step.id}/edit-question"
63+
@question_edit_link = question_edit_link
6164
@save_url = save_url
65+
@back_link = back_link(@step.id)
66+
end
67+
68+
def question_edit_link
69+
"#{Settings.forms_admin.base_url}/forms/#{@form.id}/pages-by-external-id/#{@step.id}/edit-question"
70+
end
71+
72+
def save_url
73+
save_form_page_path(@form.id, @form.form_slug, @step.id, changing_existing_answer: @changing_existing_answer, answer_index:)
6274
end
6375

6476
def changing_existing_answer
6577
@changing_existing_answer = ActiveModel::Type::Boolean.new.cast(params[:changing_existing_answer])
6678
end
6779

6880
def back_link(page_slug)
81+
return check_your_answers_path(form_id: current_context.form.id) if changing_existing_answer
82+
6983
previous_step = current_context.previous_step(page_slug)
84+
return nil unless previous_step
7085

71-
if changing_existing_answer
72-
@back_link = check_your_answers_path(form_id: current_context.form.id)
73-
elsif previous_step
74-
@back_link = previous_step.repeatable? ? add_another_answer_path(form_id: current_context.form.id, form_slug: current_context.form.form_slug, page_slug: previous_step.id) : form_page_path(@form.id, @form.form_slug, previous_step.page_id)
86+
if previous_step.repeatable?
87+
add_another_answer_path(form_id: current_context.form.id, form_slug: current_context.form.form_slug, page_slug: previous_step.id)
88+
else
89+
form_page_path(@form.id, @form.form_slug, previous_step.page_id)
7590
end
7691
end
7792

7893
def redirect_post_save
79-
return redirect_to review_file_page, success: t("banner.success.file_uploaded") if answered_file_question?
94+
return redirect_to review_file_page, success: t("banner.success.file_uploaded") if @step.answered_file_question?
8095
return redirect_to exit_page_path(form_id: @form.id, form_slug: @form.form_slug, page_slug: @step.id) if @step.exit_page_condition_matches?
8196

8297
redirect_to next_page
8398
end
8499

85100
def redirect_if_not_answered_file_question
86-
unless @step.question.is_a?(Question::File) && @step.question.file_uploaded?
101+
unless @step.answered_file_question?
87102
redirect_to form_page_path(@form.id, @form.form_slug, @step.id)
88103
end
89104
end
90105

91-
def answered_file_question?
92-
@step.question.is_a?(Question::File) && @step.question.file_uploaded?
106+
def redirect_to_none_of_the_above_page?
107+
@step.autocomplete_selection_question? && @step.question.show_none_of_the_above_question?
93108
end
94109

95110
def review_file_page
96111
review_file_path(form_id: @form.id, form_slug: @form.form_slug, page_slug: @step.id, changing_existing_answer:)
97112
end
98113

114+
def selection_none_of_the_above_page
115+
selection_none_of_the_above_path(form_id: @form.id, form_slug: @form.form_slug, page_slug: @step.id)
116+
end
117+
99118
def next_page
100119
if changing_existing_answer
101120
return next_step_changing
@@ -166,9 +185,5 @@ def admin_edit_condition_url(form_id, page_id)
166185
def is_first_page?
167186
current_context.form.start_page.to_s == @step.id
168187
end
169-
170-
def save_url
171-
save_form_page_path(@form.id, @form.form_slug, @step.id, changing_existing_answer: @changing_existing_answer, answer_index:)
172-
end
173188
end
174189
end
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
module Forms
2+
class SelectionNoneOfTheAboveController < PageController
3+
before_action :redirect_if_not_show_none_of_the_above_question
4+
5+
def show
6+
return redirect_to form_page_path(@form.id, @form.form_slug, current_context.next_page_slug) unless current_context.can_visit?(@step.id)
7+
8+
setup_instance_vars_for_view
9+
end
10+
11+
def save
12+
page_params = params.fetch(:question, {}).permit(*@step.params)
13+
@step.question.with_none_of_the_above_selected
14+
@step.assign_question_attributes(page_params)
15+
16+
if current_context.save_step(@step)
17+
unless mode.preview?
18+
LogEventService.new(current_context, @step, request, changing_existing_answer, page_params).log_page_save
19+
end
20+
21+
redirect_to next_page
22+
else
23+
setup_instance_vars_for_view
24+
render :show, status: :unprocessable_content
25+
end
26+
end
27+
28+
def redirect_if_not_show_none_of_the_above_question
29+
unless @step.question.try(:has_none_of_the_above_question?) && @step.question.try(:autocomplete_component?)
30+
redirect_to form_page_path(@form.id, @form.form_slug, @step.id)
31+
end
32+
end
33+
34+
private
35+
36+
def setup_instance_vars_for_view
37+
@back_link = back_link
38+
@question_edit_link = question_edit_link
39+
end
40+
41+
def back_link
42+
if changing_existing_answer
43+
check_your_answers_path(form_id: current_context.form.id)
44+
else
45+
form_page_path(@form.id, @form.form_slug, @step.id)
46+
end
47+
end
48+
end
49+
end

app/lib/flow/context.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ def initialize(form:, store:)
1414
delegate :clear_stored_answer, :clear, :form_submitted?, :answers, to: :answer_store
1515
delegate :save_submission_details, :get_submission_reference, :requested_email_confirmation?, :clear_submission_details, to: :confirmation_details_store
1616

17-
def save_step(step)
18-
return false unless step.valid?
17+
def save_step(step, context: nil)
18+
return false unless step.valid?(context)
1919

2020
step.save_to_store(@answer_store)
2121
end

app/models/question/selection.rb

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,28 @@ class Selection < QuestionBase
1010
validate :selection, :validate_radio, unless: :allow_multiple_answers?
1111
validates :none_of_the_above_answer, length: { maximum: 499 }
1212

13-
with_options unless: :autocomplete_component? do
14-
validates :none_of_the_above_answer, presence: true, if: :validate_none_of_the_above_answer_presence?
15-
end
13+
# In the case of a selection component that uses an autocomplete UI component, we show the input for the none
14+
# of the above question on a separate page, so we provide a context to skip the validation of
15+
# none_of_the_above_answer for the first page.
16+
validates :none_of_the_above_answer,
17+
presence: true,
18+
if: :validate_none_of_the_above_answer_presence?,
19+
unless: -> { validation_context == :skip_none_of_the_above_question_validation }
1620

1721
def allow_multiple_answers?
1822
answer_settings.only_one_option != "true"
1923
end
2024

25+
def with_none_of_the_above_selected
26+
self.selection = allow_multiple_answers? ? [I18n.t("page.none_of_the_above")] : I18n.t("page.none_of_the_above")
27+
end
28+
29+
def answered?
30+
return false if show_none_of_the_above_question? && none_of_the_above_answer.nil?
31+
32+
super()
33+
end
34+
2135
def show_answer
2236
return selection_without_blanks.map { |selected| name_from_value(selected) }.join(", ") if allow_multiple_answers?
2337

app/models/repeatable_step.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def max_answers?
5959
questions.length >= MAX_ANSWERS
6060
end
6161

62-
def valid?
63-
questions.all?(&:valid?)
62+
def valid?(context = nil)
63+
questions.all? { |q| q.valid?(context) }
6464
end
6565

6666
def show_answer

app/models/step.rb

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ def load_from_store(answer_store)
4848
self
4949
end
5050

51-
def update!(params)
51+
def assign_question_attributes(params)
5252
question.assign_attributes(params)
53-
question.valid?
5453
end
5554

5655
def params
@@ -119,6 +118,14 @@ def exit_page_condition_matches?
119118
first_condition_matches? && has_exit_page_condition?
120119
end
121120

121+
def answered_file_question?
122+
question.is_a?(Question::File) && question.file_uploaded?
123+
end
124+
125+
def autocomplete_selection_question?
126+
question.is_a?(Question::Selection) && question.autocomplete_component?
127+
end
128+
122129
private
123130

124131
def goto_condition_page_slug(condition)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<% set_page_title(form_title(form_name: @current_context.form.name, page_name: @step.question.none_of_the_above_question_text, mode: @mode, error: @step.question&.errors&.any?)) %>
2+
3+
<% content_for :back_link do %>
4+
<% if @back_link.present? %>
5+
<%= link_to t("forms.back"), @back_link, class: "govuk-back-link" %>
6+
<% end %>
7+
<% end %>
8+
9+
<div class="govuk-grid-row">
10+
<div class="govuk-grid-column-two-thirds">
11+
<%= form_with model: @step.question, url: save_selection_none_of_the_above_path, scope: :question, method: :post do |f| %>
12+
<% if @step.question&.errors&.any? %>
13+
<%= f.govuk_error_summary(t("error_summary_title")) %>
14+
<% end %>
15+
16+
<%= f.govuk_text_field :none_of_the_above_answer,
17+
label: {
18+
text: question_text_with_hidden_mode(@step.question.none_of_the_above_question_text, @mode),
19+
tag: "h1",
20+
size: "l",
21+
} %>
22+
23+
<%= f.govuk_submit(t("continue")) %>
24+
<% end %>
25+
26+
<%= render SupportDetailsComponent::View.new(@support_details) %>
27+
</div>
28+
</div>

config/routes.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,19 @@
6666
as: :remove_file,
6767
constraints: page_constraints
6868

69+
# We don't currently support adding another answer for selection questions, so these routes don't include an
70+
# `answer_index` param
71+
get "/:page_slug/none-of-the-above/change" => "forms/selection_none_of_the_above#show",
72+
as: :change_selection_none_of_the_above,
73+
constraints: page_constraints,
74+
defaults: { changing_existing_answer: true }
75+
get "/:page_slug/none-of-the-above" => "forms/selection_none_of_the_above#show",
76+
as: :selection_none_of_the_above,
77+
constraints: page_constraints
78+
post "/:page_slug/none-of-the-above" => "forms/selection_none_of_the_above#save",
79+
as: :save_selection_none_of_the_above,
80+
constraints: page_constraints
81+
6982
get "/:page_slug/(/:answer_index)/change" => "forms/page#show",
7083
as: :form_change_answer,
7184
defaults: page_answer_defaults.merge(changing_existing_answer: true),

spec/components/check_your_answers_component/view_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,13 @@
8585
let(:selection) { nil }
8686
let(:none_of_the_above_answer) { nil }
8787
let(:none_of_the_above_question_is_optional) { "false" }
88+
let(:selection_options) { [DataStruct.new(name: "Option 1", value: "Option 1"), DataStruct.new(name: "Option 2", value: "Option 2")] }
8889
let(:question) do
8990
build(
9091
:single_selection_question,
9192
:with_none_of_the_above_question,
9293
question_text:,
94+
selection_options:,
9395
none_of_the_above_question_text:,
9496
selection:,
9597
none_of_the_above_answer:,
@@ -116,6 +118,10 @@
116118
expect(page).to have_css(".govuk-summary-list__key", text: none_of_the_above_question_text)
117119
expect(page).to have_css(".govuk-summary-list__value", text: none_of_the_above_answer)
118120
end
121+
122+
it "has a change link for the 'None of the above' question" do
123+
expect(page).to have_link("Change", href: form_change_answer_path(mode: mode, form_id: form.id, form_slug: form.form_slug, page_slug: steps[0].id))
124+
end
119125
end
120126

121127
context "when 'None of the above' question is optional" do
@@ -131,6 +137,14 @@
131137
end
132138
end
133139
end
140+
141+
context "when selection question uses an autocomplete component" do
142+
let(:selection_options) { Array.new(31).map { |i| OpenStruct.new(name: "Option #{i}", value: "Option #{i}") } }
143+
144+
it "has a change link to the 'None of the above' specific path" do
145+
expect(page).to have_link("Change", href: change_selection_none_of_the_above_path(mode: mode, form_id: form.id, form_slug: form.form_slug, page_slug: steps[0].id))
146+
end
147+
end
134148
end
135149
end
136150
end

0 commit comments

Comments
 (0)