Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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_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_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_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_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_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