Skip to content

Commit 090b293

Browse files
authored
Merge pull request #1799 from alphagov/implement-describing-none-of-the-above
Implement describing none of the above for radio and checkbox selection questions
2 parents 2ad0e0a + a3df05f commit 090b293

File tree

9 files changed

+334
-25
lines changed

9 files changed

+334
-25
lines changed

.review_apps/ecs_task_definition.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ locals {
4646
{ name = "SETTINGS__FORMS_ENV", value = "review" },
4747
{ name = "SETTINGS__FORMS_RUNNER__URL", value = "https://${local.runner_review_app_hostname}" },
4848
{ name = "ALLOWED_HOST_PATTERNS", value = "localhost:3000" },
49-
{ name = "SETTINGS__FEATURES__JSON_SUBMISSION_ENABLED", value = "true" }
49+
{ name = "SETTINGS__FEATURES__DESCRIBE_NONE_OF_THE_ABOVE_ENABLED", value = "true" }
5050
]
5151
}
5252

app/components/question/selection_component/view.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<% if question.answer_settings.selection_options.count > 30 %>
1+
<% if question.autocomplete_component? %>
22
<%= render DfE::Autocomplete::View.new(
33
form_builder,
44
attribute_name: :selection,

app/components/question/selection_component/view.rb

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,37 @@ def divider
3636
def none_of_the_above_radio_button
3737
return nil unless question.is_optional?
3838

39-
option = form_builder.govuk_radio_button :selection, I18n.t("page.none_of_the_above"), label: { text: I18n.t("page.none_of_the_above") }
39+
option = form_builder.govuk_radio_button :selection,
40+
I18n.t("page.none_of_the_above"),
41+
label: { text: I18n.t("page.none_of_the_above") },
42+
&method(:none_of_the_above_question_field)
4043
safe_join([divider, option])
4144
end
4245

4346
def none_of_the_above_checkbox
4447
return nil unless question.is_optional?
4548

46-
option = form_builder.govuk_check_box :selection, I18n.t("page.none_of_the_above"), exclusive: true, label: { text: I18n.t("page.none_of_the_above") }
49+
option = form_builder.govuk_check_box :selection,
50+
I18n.t("page.none_of_the_above"),
51+
exclusive: true,
52+
label: { text: I18n.t("page.none_of_the_above") },
53+
&method(:none_of_the_above_question_field)
4754
safe_join([divider, option])
4855
end
4956

57+
def none_of_the_above_question_field
58+
if question.has_none_of_the_above_question?
59+
form_builder.govuk_text_field :none_of_the_above_answer, label: { text: none_of_the_above_question_text }, width: "three-quarters"
60+
end
61+
end
62+
63+
def none_of_the_above_question_text
64+
none_of_the_above_question = question.answer_settings.none_of_the_above_question
65+
return none_of_the_above_question.question_text if none_of_the_above_question.is_optional != "true"
66+
67+
"#{none_of_the_above_question.question_text} #{I18n.t('page.optional')}"
68+
end
69+
5070
def radio_button_options
5171
question.answer_settings.selection_options.map.with_index do |option, index|
5272
form_builder.govuk_radio_button :selection, option.name, label: { text: option.name }, link_errors: index.zero?

app/models/question/selection.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
module Question
22
class Selection < QuestionBase
33
attribute :selection
4+
attribute :none_of_the_above_answer
5+
6+
before_validation :clear_none_of_the_above_answer_if_not_selected
7+
48
validates :selection, presence: true
59
validate :selection, :validate_checkbox, if: :allow_multiple_answers?
610
validate :selection, :validate_radio, unless: :allow_multiple_answers?
11+
validates :none_of_the_above_answer, length: { maximum: 499 }
12+
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
716

817
def allow_multiple_answers?
918
answer_settings.only_one_option != "true"
@@ -42,8 +51,20 @@ def selection_options_with_none_of_the_above
4251
[*options, none_of_the_above_option]
4352
end
4453

54+
def autocomplete_component?
55+
answer_settings.selection_options.count > 30
56+
end
57+
58+
def has_none_of_the_above_question?
59+
none_of_the_above_question.present?
60+
end
61+
4562
private
4663

64+
def clear_none_of_the_above_answer_if_not_selected
65+
self.none_of_the_above_answer = nil unless none_of_the_above_selected?
66+
end
67+
4768
def allowed_options
4869
selection_options_with_none_of_the_above.map(&:name)
4970
end
@@ -68,5 +89,23 @@ def validate_checkbox
6889

6990
errors.add(:selection, :inclusion) if selection_without_blanks.any? { |item| allowed_options.exclude?(item) }
7091
end
92+
93+
def validate_none_of_the_above_answer_presence?
94+
none_of_the_above_question.present? && none_of_the_above_question.is_optional != "true" && none_of_the_above_selected?
95+
end
96+
97+
def none_of_the_above_question
98+
return nil unless is_optional?
99+
return nil unless answer_settings.respond_to?(:none_of_the_above_question)
100+
return nil unless answer_settings.none_of_the_above_question.respond_to?(:question_text)
101+
102+
answer_settings.none_of_the_above_question
103+
end
104+
105+
def none_of_the_above_selected?
106+
return selection_without_blanks.include?(I18n.t("page.none_of_the_above")) if allow_multiple_answers?
107+
108+
selection == I18n.t("page.none_of_the_above")
109+
end
71110
end
72111
end

config/locales/cy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ cy:
174174
phone_too_short: Dylai’r rhif ffôn fod ag 8 neu fwy o ddigidau
175175
question/selection:
176176
attributes:
177+
none_of_the_above_answer:
178+
blank: Rhowch ateb
179+
too_long: Rhaid i’r ateb fod yn llai na 500 o gymeriadau
177180
selection:
178181
blank: Dewiswch un opsiwn
179182
both_none_and_value_selected: Dewiswch un neu ragor o opsiynau neu dewiswch 'Dim un o’r uchod'

config/locales/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,9 @@ en:
174174
phone_too_short: The phone number should have 8 digits or more
175175
question/selection:
176176
attributes:
177+
none_of_the_above_answer:
178+
blank: Enter an answer
179+
too_long: The answer must be shorter than 500 characters
177180
selection:
178181
blank: Select one option
179182
both_none_and_value_selected: Select one or more options, or select ‘None of the above’

spec/components/question/selection_component/view_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,42 @@
1414
render_inline(described_class.new(form_builder:, question:, extra_question_text_suffix:))
1515
end
1616

17+
shared_examples "None of the above question field" do
18+
context "when a 'None of the above' question is not defined" do
19+
it "does not render a conditional text field for the 'None of the above' option" do
20+
expect(page).not_to have_css("input[type='text'][name='form[none_of_the_above_answer]']")
21+
end
22+
end
23+
24+
context "when a 'None of the above' question is defined" do
25+
let(:none_of_the_above_question_is_optional) { "true" }
26+
let(:question) do
27+
build(:single_selection_question,
28+
:with_none_of_the_above_question,
29+
none_of_the_above_question_text: "Enter another answer",
30+
none_of_the_above_question_is_optional:)
31+
end
32+
33+
it "renders a conditional text field for the 'None of the above' option" do
34+
expect(page).to have_css("input[type='text'][name='form[none_of_the_above_answer]']")
35+
end
36+
37+
context "when the 'None of the above' question is optional" do
38+
it "has the question text with an optional suffix as the label for the field" do
39+
expect(page).to have_css("label[for='form-none-of-the-above-answer-field']", text: "Enter another answer (optional)")
40+
end
41+
end
42+
43+
context "when the 'None of the above' question is mandatory" do
44+
let(:none_of_the_above_question_is_optional) { "false" }
45+
46+
it "has the question text as the label for the field" do
47+
expect(page).to have_css("label[for='form-none-of-the-above-answer-field']", text: "Enter another answer")
48+
end
49+
end
50+
end
51+
end
52+
1753
describe "when component is select one from a list field" do
1854
context "when there are 30 or fewer options" do
1955
let(:question) { build :single_selection_question, is_optional:, selection_options: }
@@ -70,6 +106,8 @@
70106
it "contains the 'None of the above' option" do
71107
expect(page).to have_css("input[type='radio'] + label", text: "None of the above")
72108
end
109+
110+
include_examples "None of the above question field"
73111
end
74112

75113
context "when question has guidance" do
@@ -197,6 +235,8 @@
197235
it "contains the 'None of the above' option" do
198236
expect(page).to have_css("input[type='checkbox'] + label", text: "None of the above")
199237
end
238+
239+
include_examples "None of the above question field"
200240
end
201241

202242
context "when question has guidance" do

spec/factories/models/question/selection.rb

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
question_text { Faker::Lorem.question }
44
hint_text { nil }
55
is_optional { false }
6+
answer_settings do
7+
if none_of_the_above_question
8+
Struct.new(:only_one_option, :selection_options, :none_of_the_above_question)
9+
.new(only_one_option, selection_options, none_of_the_above_question)
10+
else
11+
Struct.new(:only_one_option, :selection_options).new(only_one_option, selection_options)
12+
end
13+
end
14+
15+
transient do
16+
only_one_option { "true" }
17+
selection_options { [DataStruct.new(name: "Option 1"), DataStruct.new(name: "Option 2")] }
18+
none_of_the_above_question { nil }
19+
end
620

721
trait :with_hints do
822
hint_text { Faker::Quote.yoda }
@@ -13,18 +27,24 @@
1327
guidance_markdown { "## List of items \n\n\n #{Faker::Markdown.ordered_list}" }
1428
end
1529

16-
factory :single_selection_question do
30+
trait :with_none_of_the_above_question do
1731
transient do
18-
selection_options { [DataStruct.new(name: "Option 1"), DataStruct.new(name: "Option 2")] }
32+
none_of_the_above_question_text { Faker::Lorem.question }
33+
none_of_the_above_question_is_optional { "false" }
34+
end
35+
is_optional { true }
36+
none_of_the_above_question do
37+
Struct.new(:question_text, :is_optional)
38+
.new(none_of_the_above_question_text, none_of_the_above_question_is_optional)
1939
end
20-
answer_settings { DataStruct.new(only_one_option: "true", selection_options:) }
40+
end
41+
42+
factory :single_selection_question do
43+
only_one_option { "true" }
2144
end
2245

2346
factory :multiple_selection_question do
24-
transient do
25-
selection_options { [DataStruct.new(name: "Option 1"), DataStruct.new(name: "Option 2")] }
26-
end
27-
answer_settings { DataStruct.new(only_one_option: "false", selection_options:) }
47+
only_one_option { "false" }
2848
selection { ["Option 1", "Option 2"] }
2949
end
3050
end

0 commit comments

Comments
 (0)