Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .review_apps/ecs_task_definition.tf
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ locals {
{ name = "SETTINGS__FORMS_ENV", value = "review" },
{ name = "SETTINGS__FORMS_RUNNER__URL", value = "https://${local.runner_review_app_hostname}" },
{ name = "ALLOWED_HOST_PATTERNS", value = "localhost:3000" },
{ name = "SETTINGS__FEATURES__JSON_SUBMISSION_ENABLED", value = "true" }
{ name = "SETTINGS__FEATURES__DESCRIBE_NONE_OF_THE_ABOVE_ENABLED", value = "true" }
]
}

Expand Down
2 changes: 1 addition & 1 deletion app/components/question/selection_component/view.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<% if question.answer_settings.selection_options.count > 30 %>
<% if question.autocomplete_component? %>
<%= render DfE::Autocomplete::View.new(
form_builder,
attribute_name: :selection,
Expand Down
24 changes: 22 additions & 2 deletions app/components/question/selection_component/view.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,37 @@ def divider
def none_of_the_above_radio_button
return nil unless question.is_optional?

option = form_builder.govuk_radio_button :selection, I18n.t("page.none_of_the_above"), label: { text: I18n.t("page.none_of_the_above") }
option = form_builder.govuk_radio_button :selection,
I18n.t("page.none_of_the_above"),
label: { text: I18n.t("page.none_of_the_above") },
&method(:none_of_the_above_question_field)
safe_join([divider, option])
end

def none_of_the_above_checkbox
return nil unless question.is_optional?

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") }
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") },
&method(:none_of_the_above_question_field)
safe_join([divider, option])
end

def none_of_the_above_question_field
if question.has_none_of_the_above_question?
form_builder.govuk_text_field :none_of_the_above_answer, label: { text: none_of_the_above_question_text }, width: "three-quarters"
end
end

def none_of_the_above_question_text
none_of_the_above_question = question.answer_settings.none_of_the_above_question
return none_of_the_above_question.question_text if none_of_the_above_question.is_optional != "true"

"#{none_of_the_above_question.question_text} #{I18n.t('page.optional')}"
end

def radio_button_options
question.answer_settings.selection_options.map.with_index do |option, index|
form_builder.govuk_radio_button :selection, option.name, label: { text: option.name }, link_errors: index.zero?
Expand Down
39 changes: 39 additions & 0 deletions app/models/question/selection.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
module Question
class Selection < QuestionBase
attribute :selection
attribute :none_of_the_above_answer

before_validation :clear_none_of_the_above_answer_if_not_selected

validates :selection, presence: true
validate :selection, :validate_checkbox, if: :allow_multiple_answers?
validate :selection, :validate_radio, unless: :allow_multiple_answers?
validates :none_of_the_above_answer, length: { maximum: 499 }

with_options unless: :autocomplete_component? do
validates :none_of_the_above_answer, presence: true, if: :validate_none_of_the_above_answer_presence?
end

def allow_multiple_answers?
answer_settings.only_one_option != "true"
Expand Down Expand Up @@ -42,8 +51,20 @@ def selection_options_with_none_of_the_above
[*options, none_of_the_above_option]
end

def autocomplete_component?
answer_settings.selection_options.count > 30
end

def has_none_of_the_above_question?
none_of_the_above_question.present?
end

private

def clear_none_of_the_above_answer_if_not_selected
self.none_of_the_above_answer = nil unless none_of_the_above_selected?
end

def allowed_options
selection_options_with_none_of_the_above.map(&:name)
end
Expand All @@ -68,5 +89,23 @@ def validate_checkbox

errors.add(:selection, :inclusion) if selection_without_blanks.any? { |item| allowed_options.exclude?(item) }
end

def validate_none_of_the_above_answer_presence?
none_of_the_above_question.present? && none_of_the_above_question.is_optional != "true" && none_of_the_above_selected?
end

def none_of_the_above_question
return nil unless is_optional?
return nil unless answer_settings.respond_to?(:none_of_the_above_question)
return nil unless answer_settings.none_of_the_above_question.respond_to?(:question_text)

answer_settings.none_of_the_above_question
end

def none_of_the_above_selected?
return selection_without_blanks.include?(I18n.t("page.none_of_the_above")) if allow_multiple_answers?

selection == I18n.t("page.none_of_the_above")
end
end
end
3 changes: 3 additions & 0 deletions config/locales/cy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ cy:
phone_too_short: Dylai’r rhif ffôn fod ag 8 neu fwy o ddigidau
question/selection:
attributes:
none_of_the_above_answer:
blank: Rhowch ateb
too_long: Rhaid i’r ateb fod yn llai na 500 o gymeriadau
selection:
blank: Dewiswch un opsiwn
both_none_and_value_selected: Dewiswch un neu ragor o opsiynau neu dewiswch 'Dim un o’r uchod'
Expand Down
3 changes: 3 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ en:
phone_too_short: The phone number should have 8 digits or more
question/selection:
attributes:
none_of_the_above_answer:
blank: Enter an answer
too_long: The answer must be shorter than 500 characters
selection:
blank: Select one option
both_none_and_value_selected: Select one or more options, or select ‘None of the above’
Expand Down
40 changes: 40 additions & 0 deletions spec/components/question/selection_component/view_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,42 @@
render_inline(described_class.new(form_builder:, question:, extra_question_text_suffix:))
end

shared_examples "None of the above question field" do
context "when a 'None of the above' question is not defined" do
it "does not render a conditional text field for the 'None of the above' option" do
expect(page).not_to have_css("input[type='text'][name='form[none_of_the_above_answer]']")
end
end

context "when a 'None of the above' question is defined" do
let(:none_of_the_above_question_text_is_optional) { "true" }
let(:question) do
build(:single_selection_question,
:with_none_of_the_above_question,
none_of_the_above_question_text: "Enter another answer",
none_of_the_above_question_text_is_optional:)
end

it "renders a conditional text field for the 'None of the above' option" do
expect(page).to have_css("input[type='text'][name='form[none_of_the_above_answer]']")
end

context "when the 'None of the above' question is optional" do
it "has the question text with an optional suffix as the label for the field" do
expect(page).to have_css("label[for='form-none-of-the-above-answer-field']", text: "Enter another answer (optional)")
end
end

context "when the 'None of the above' question is mandatory" do
let(:none_of_the_above_question_text_is_optional) { "false" }

it "has the question text as the label for the field" do
expect(page).to have_css("label[for='form-none-of-the-above-answer-field']", text: "Enter another answer")
end
end
end
end

describe "when component is select one from a list field" do
context "when there are 30 or fewer options" do
let(:question) { build :single_selection_question, is_optional:, selection_options: }
Expand Down Expand Up @@ -70,6 +106,8 @@
it "contains the 'None of the above' option" do
expect(page).to have_css("input[type='radio'] + label", text: "None of the above")
end

include_examples "None of the above question field"
end

context "when question has guidance" do
Expand Down Expand Up @@ -197,6 +235,8 @@
it "contains the 'None of the above' option" do
expect(page).to have_css("input[type='checkbox'] + label", text: "None of the above")
end

include_examples "None of the above question field"
end

context "when question has guidance" do
Expand Down
34 changes: 27 additions & 7 deletions spec/factories/models/question/selection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
question_text { Faker::Lorem.question }
hint_text { nil }
is_optional { false }
answer_settings do
if none_of_the_above_question
Struct.new(:only_one_option, :selection_options, :none_of_the_above_question)
.new(only_one_option, selection_options, none_of_the_above_question)
else
Struct.new(:only_one_option, :selection_options).new(only_one_option, selection_options)
end
end

transient do
only_one_option { "true" }
selection_options { [DataStruct.new(name: "Option 1"), DataStruct.new(name: "Option 2")] }
none_of_the_above_question { nil }
end

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

factory :single_selection_question do
trait :with_none_of_the_above_question do
transient do
selection_options { [DataStruct.new(name: "Option 1"), DataStruct.new(name: "Option 2")] }
none_of_the_above_question_text { Faker::Lorem.question }
none_of_the_above_question_text_is_optional { "false" }
end
is_optional { true }
none_of_the_above_question do
Struct.new(:question_text, :is_optional)
.new(none_of_the_above_question_text, none_of_the_above_question_text_is_optional)
end
answer_settings { DataStruct.new(only_one_option: "true", selection_options:) }
end

factory :single_selection_question do
only_one_option { "true" }
end

factory :multiple_selection_question do
transient do
selection_options { [DataStruct.new(name: "Option 1"), DataStruct.new(name: "Option 2")] }
end
answer_settings { DataStruct.new(only_one_option: "false", selection_options:) }
only_one_option { "false" }
selection { ["Option 1", "Option 2"] }
end
end
Expand Down
Loading