Browse Source

introduce new Answer class

answer
Phil Lee 2 years ago
parent
commit
19c9e00cc4
  1. 3
      app/components/check_answers_summary_list_card_component.html.erb
  2. 3
      app/components/check_answers_summary_list_card_component.rb
  3. 8
      app/helpers/check_answers_helper.rb
  4. 8
      app/helpers/interruption_screen_helper.rb
  5. 136
      app/models/answer.rb
  6. 67
      app/models/form/question.rb
  7. 5
      app/models/form/subsection.rb
  8. 3
      app/views/form/_check_answers_summary_list.html.erb
  9. 4
      app/views/form/_numeric_output_question.html.erb
  10. 3
      app/views/form/_numeric_question.html.erb
  11. 3
      spec/components/check_answers_summary_list_card_component_spec.rb
  12. 27
      spec/helpers/interruption_screen_helper_spec.rb
  13. 115
      spec/models/answer_spec.rb
  14. 90
      spec/models/form/question_spec.rb
  15. 15
      spec/spec_helper.rb

3
app/components/check_answers_summary_list_card_component.html.erb

@ -21,7 +21,8 @@
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= extra_value %></span>
<% end %>
<br>
<% question.get_inferred_answers(log).each do |inferred_answer| %>
<% answer = Answer.new(question:, log:) %>
<% answer.get_inferred_answers.each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span>
<% end %>
<% end %>

3
app/components/check_answers_summary_list_card_component.rb

@ -13,6 +13,7 @@ class CheckAnswersSummaryListCardComponent < ViewComponent::Base
end
def get_answer_label(question)
question.answer_label(log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
answer = Answer.new(question:, log:)
answer.answer_label.presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
end
end

8
app/helpers/check_answers_helper.rb

@ -43,7 +43,10 @@ private
end
def answered_questions(subsection, lettings_log, current_user)
total_applicable_questions(subsection, lettings_log, current_user).select { |q| q.completed?(lettings_log) }
total_applicable_questions(subsection, lettings_log, current_user).select do |question|
answer = Answer.new(question:, log: lettings_log)
answer.completed?
end
end
def total_count(subsection, lettings_log, current_user)
@ -55,6 +58,7 @@ private
end
def get_answer_label(question, lettings_log)
question.answer_label(lettings_log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
answer = Answer.new(question:, log: lettings_log)
answer.answer_label.presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
end
end

8
app/helpers/interruption_screen_helper.rb

@ -5,7 +5,9 @@ module InterruptionScreenHelper
translation_params = {}
informative_text["arguments"].each do |argument|
value = if argument["label"]
pre_casing_value = lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log)
question = lettings_log.form.get_question(argument["key"], lettings_log)
answer = Answer.new(question:, log: lettings_log)
pre_casing_value = answer.answer_label
pre_casing_value.downcase
else
lettings_log.public_send(argument["key"])
@ -29,7 +31,9 @@ module InterruptionScreenHelper
arguments = title_text["arguments"] || {}
arguments.each do |argument|
value = if argument["label"]
lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log).downcase
question = lettings_log.form.get_question(argument["key"], lettings_log)
answer = Answer.new(question:, log: lettings_log)
answer.answer_label.downcase
else
lettings_log.public_send(argument["key"])
end

136
app/models/answer.rb

@ -0,0 +1,136 @@
class Answer
attr_reader :question, :log
delegate :type, to: :question
delegate :id, to: :question
delegate :answer_options, to: :question
delegate :prefix, to: :question
delegate :suffix, to: :question
delegate :inferred_check_answers_value, to: :question
delegate :inferred_answers, to: :question
delegate :page, to: :question
delegate :subsection, to: :page
delegate :form, to: :subsection
def initialize(question:, log:)
@question = question
@log = log
end
def answer_label
return checkbox_answer_label if checkbox?
return log[id]&.to_formatted_s(:govuk_date).to_s if date?
answer = label_from_value(log[id]) if log[id].present?
answer_label = [prefix, format_value(answer), suffix_label].join("") if answer
inferred = inferred_check_answers_value["value"] if inferred_check_answers_value && has_inferred_check_answers_value?
return inferred if inferred.present?
answer_label
end
def suffix_label
return "" unless suffix
return suffix if suffix.is_a?(String)
label = ""
suffix.each do |s|
condition = s["depends_on"]
next unless condition
answer = log.send(condition.keys.first)
if answer == condition.values.first
label = s["label"]
end
end
label
end
def completed?
return answer_options.keys.any? { |key| value_is_yes?(log[key]) } if checkbox?
log[id].present? || !log.respond_to?(id.to_sym) || has_inferred_display_value?
end
def get_inferred_answers
return [] unless inferred_answers
enabled_inferred_answers(inferred_answers).keys.map do |question_id|
question = form.get_question(question_id, log)
if question.present?
question.label_from_value(log[question_id])
else
Array(question_id.to_s.split(".")).inject(log) { |l, method| l.present? ? l.public_send(*method) : "" }
end
end
end
private
def enabled_inferred_answers(inferred_answers)
inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| log[condition_key] == condition_value } }
end
def has_inferred_display_value?
inferred_check_answers_value.present? && log[inferred_check_answers_value["condition"].keys.first] == inferred_check_answers_value["condition"].values.first
end
def has_inferred_check_answers_value?
return true if selected_answer_option_is_derived?
return inferred_check_answers_value["condition"].values[0] == log[inferred_check_answers_value["condition"].keys[0]] if inferred_check_answers_value.present?
false
end
def selected_answer_option_is_derived?
selected_option = answer_options&.dig(log[id].to_s.presence)
selected_option.is_a?(Hash) && selected_option["depends_on"] && form.depends_on_met(selected_option["depends_on"], log)
end
def format_value(answer_label)
if prefix == "£"
ActionController::Base.helpers.number_to_currency(answer_label, delimiter: ",", format: "%n")
else
answer_label
end
end
def label_from_value(value)
question.label_from_value(value)
end
def checkbox?
question.type == "checkbox"
end
def date?
question.type == "date"
end
def checkbox_answer_label
answer = []
return "Yes" if declaration? && value_is_yes?(log["declaration"])
answer_options.each { |key, options| value_is_yes?(log[key]) ? answer << options["value"] : nil }
answer.join(", ")
end
def declaration?
question.id == "declaration"
end
def value_is_yes?(value)
case type
when "checkbox"
value == 1
when "radio"
RADIO_YES_VALUE[id.to_sym]&.include?(value)
else
%w[yes].include?(value.downcase)
end
end
end

67
app/models/form/question.rb

@ -13,6 +13,7 @@ class Form::Question
def initialize(id, hsh, page)
@id = id
@page = page
if hsh
@check_answer_label = hsh["check_answer_label"]
@header = hsh["header"]
@ -44,32 +45,6 @@ class Form::Question
delegate :subsection, to: :page
delegate :form, to: :subsection
def answer_label(log)
return checkbox_answer_label(log) if type == "checkbox"
return log[id]&.to_formatted_s(:govuk_date).to_s if type == "date"
answer = label_from_value(log[id]) if log[id].present?
answer_label = [prefix, format_value(answer), suffix_label(log)].join("") if answer
inferred = inferred_check_answers_value["value"] if inferred_check_answers_value && has_inferred_check_answers_value?(log)
return inferred if inferred.present?
answer_label
end
def get_inferred_answers(log)
return [] unless inferred_answers
enabled_inferred_answers(inferred_answers, log).keys.map do |question_id|
question = form.get_question(question_id, log)
if question.present?
question.label_from_value(log[question_id])
else
Array(question_id.to_s.split(".")).inject(log) { |l, method| l.present? ? l.public_send(*method) : "" }
end
end
end
def get_extra_check_answer_value(_log)
nil
end
@ -127,12 +102,6 @@ class Form::Question
"/#{log.model_name.param_key.dasherize}s/#{log.id}/#{page_id.to_s.dasherize}?referrer=check_answers"
end
def completed?(log)
return answer_options.keys.any? { |key| value_is_yes?(log[key]) } if type == "checkbox"
log[id].present? || !log.respond_to?(id.to_sym) || has_inferred_display_value?(log)
end
def value_from_label(label)
return unless label
@ -205,24 +174,6 @@ class Form::Question
I18n.t("validations.not_answered", question: display_label.downcase)
end
def suffix_label(log)
return "" unless suffix
return suffix if suffix.is_a?(String)
label = ""
suffix.each do |s|
condition = s["depends_on"]
next unless condition
answer = log.send(condition.keys.first)
if answer == condition.values.first
label = s["label"]
end
end
label
end
def answer_option_synonyms(resource)
return unless resource.respond_to?(:synonyms)
@ -262,18 +213,6 @@ private
selected_option.is_a?(Hash) && selected_option["depends_on"] && form.depends_on_met(selected_option["depends_on"], log)
end
def has_inferred_display_value?(log)
inferred_check_answers_value.present? && log[inferred_check_answers_value["condition"].keys.first] == inferred_check_answers_value["condition"].values.first
end
def checkbox_answer_label(log)
answer = []
return "Yes" if id == "declaration" && value_is_yes?(log["declaration"])
answer_options.each { |key, options| value_is_yes?(log[key]) ? answer << options["value"] : nil }
answer.join(", ")
end
def format_value(answer_label)
prefix == "£" ? ActionController::Base.helpers.number_to_currency(answer_label, delimiter: ",", format: "%n") : answer_label
end
@ -297,10 +236,6 @@ private
end
end
def enabled_inferred_answers(inferred_answers, log)
inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| log[condition_key] == condition_value } }
end
RADIO_YES_VALUE = {
renewal: [1],
postcode_known: [1],

5
app/models/form/subsection.rb

@ -31,7 +31,10 @@ class Form::Subsection
qs = applicable_questions(log)
qs_optional_removed = qs.reject { |q| log.optional_fields.include?(q.id) }
return :not_started if qs.count.positive? && qs.all? { |question| log[question.id].blank? || question.read_only? || question.derived? }
return :completed if qs_optional_removed.all? { |question| question.completed?(log) }
return :completed if qs_optional_removed.all? do |question|
answer = Answer.new(question:, log:)
answer.completed?
end
:in_progress
end

3
app/views/form/_check_answers_summary_list.html.erb

@ -9,7 +9,8 @@
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= extra_value %></span>
<% end %>
<br>
<% question.get_inferred_answers(@log).each do |inferred_answer| %>
<% answer = Answer.new(log: @log, question:) %>
<% answer.get_inferred_answers.each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span>
<% end %>
<% end %>

4
app/views/form/_numeric_output_question.html.erb

@ -17,7 +17,9 @@
name="lettings_log[tcharge]"
for="<%= question.fields_added.present? ? question.fields_added.map { |x| "lettings-log-#{x}-field" }.join(" ") : "" %>">
<%= lettings_log[question.id] %></output>
<span class="govuk-input__suffix"><%= question.suffix_label(lettings_log) %></span>
<% answer = Answer.new(log: lettings_log, question:) %>
<span class="govuk-input__suffix"><%= answer.suffix_label %></span>
</div>
</div>

3
app/views/form/_numeric_question.html.erb

@ -1,5 +1,6 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %>
<% answer = Answer.new(log: @log, question:) %>
<%= f.govuk_number_field question.id.to_sym,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
@ -8,7 +9,7 @@
width: question.width,
readonly: question.read_only?,
prefix_text: question.prefix.to_s,
suffix_text: question.suffix_label(@log),
suffix_text: answer.suffix_label,
**stimulus_html_attributes(question) %>
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.bottom_guidance? %>

3
spec/components/check_answers_summary_list_card_component_spec.rb

@ -10,7 +10,8 @@ RSpec.describe CheckAnswersSummaryListCardComponent, type: :component do
it "renders a summary list card for the answers to those questions" do
result = render_inline(described_class.new(questions:, log:, user:))
expect(result).to have_content(questions.first.answer_label(log))
answer = Answer.new(log:, question: questions.first)
expect(result).to have_content(answer.answer_label)
end
it "applicable questions doesn't return questions that are hidden in check answers" do

27
spec/helpers/interruption_screen_helper_spec.rb

@ -35,8 +35,15 @@ RSpec.describe InterruptionScreenHelper do
},
],
}
ecstat_question = lettings_log.form.get_question("ecstat1", lettings_log)
ecstat_answer = Answer.new(log: lettings_log, question: ecstat_question)
earnings_question = lettings_log.form.get_question("earnings", lettings_log)
earnings_answer = Answer.new(log: lettings_log, question: earnings_question)
expect(display_informative_text(informative_text, lettings_log))
.to eq(I18n.t("soft_validations.net_income.hint_text", ecstat1: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase, earnings: lettings_log.form.get_question("earnings", lettings_log).answer_label(lettings_log)))
.to eq(I18n.t("soft_validations.net_income.hint_text", ecstat1: ecstat_answer.answer_label.downcase, earnings: earnings_answer.answer_label))
end
end
@ -52,8 +59,12 @@ RSpec.describe InterruptionScreenHelper do
},
],
}
question = lettings_log.form.get_question("ecstat1", lettings_log)
answer = Answer.new(question:, log: lettings_log)
expect(display_informative_text(informative_text, lettings_log))
.to eq(I18n.t("test.one_argument", ecstat1: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase))
.to eq(I18n.t("test.one_argument", ecstat1: answer.answer_label.downcase))
end
end
@ -74,8 +85,12 @@ RSpec.describe InterruptionScreenHelper do
},
],
}
question = lettings_log.form.get_question("ecstat1", lettings_log)
answer = Answer.new(question:, log: lettings_log)
expect(display_informative_text(informative_text, lettings_log))
.to eq(I18n.t("test.one_argument", ecstat1: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase))
.to eq(I18n.t("test.one_argument", ecstat1: answer.answer_label.downcase))
end
end
@ -118,8 +133,12 @@ RSpec.describe InterruptionScreenHelper do
},
],
}
question = lettings_log.form.get_question("ecstat1", lettings_log)
answer = Answer.new(log: lettings_log, question:)
expect(display_title_text(title_text, lettings_log))
.to eq(I18n.t("test.title_text.one_argument", ecstat1: lettings_log.form.get_question("ecstat1", lettings_log).answer_label(lettings_log).downcase))
.to eq(I18n.t("test.title_text.one_argument", ecstat1: answer.answer_label.downcase))
end
end

115
spec/models/answer_spec.rb

@ -0,0 +1,115 @@
require "rails_helper"
RSpec.describe Answer, type: :model do
subject(:answer) { described_class.new(question:, log:) }
let(:log) { FactoryBot.build(:lettings_log, :in_progress) }
let(:form) { log.form }
let(:question_id) { "incfreq" }
let(:section_id) { "rent_and_charges" }
let(:section_definition) { form.form_definition["sections"][section_id] }
let(:section) { Form::Section.new(section_id, section_definition, form) }
let(:subsection_id) { "income_and_benefits" }
let(:subsection_definition) { section_definition["subsections"][subsection_id] }
let(:subsection) { Form::Subsection.new(subsection_id, subsection_definition, section) }
let(:page_definition) { subsection_definition["pages"][page_id] }
let(:question_definition) { page_definition["questions"][question_id] }
let(:page) { Form::Page.new(page_id, page_definition, subsection) }
let(:page_id) { "net_income" }
let(:question) { Form::Question.new(question_id, question_definition, page) }
describe "#answer_label" do
context "with a lettings log" do
it "has an answer label" do
log.incfreq = 1
expect(answer.answer_label).to eql("Weekly")
end
end
context "when type is date" do
let(:section_id) { "local_authority" }
let(:subsection_id) { "local_authority" }
let(:page_id) { "property_major_repairs" }
let(:question_id) { "mrcdate" }
it "displays a formatted answer label" do
log.mrcdate = Time.zone.local(2021, 10, 11)
expect(answer.answer_label).to eql("11 October 2021")
end
it "can handle nils" do
log.mrcdate = nil
expect(answer.answer_label).to eql("")
end
end
context "when type is checkbox" do
let(:section_id) { "household" }
let(:subsection_id) { "household_needs" }
let(:page_id) { "accessibility_requirements" }
let(:question_id) { "accessibility_requirements" }
it "has a joined answers label" do
log.housingneeds_a = 1
log.housingneeds_c = 1
expected_answer_label = "Fully wheelchair accessible housing, Level access housing"
expect(answer.answer_label).to eql(expected_answer_label)
end
end
context "when answers have a suffix dependent on another answer" do
let(:section_id) { "rent_and_charges" }
let(:subsection_id) { "income_and_benefits" }
let(:page_id) { "net_income" }
let(:question_id) { "earnings" }
it "displays the correct label for given suffix and answer the suffix depends on" do
log.incfreq = 1
log.earnings = 500
expect(answer.answer_label).to eql("£500.00 every week")
log.incfreq = 2
expect(answer.answer_label).to eql("£500.00 every month")
log.incfreq = 3
expect(answer.answer_label).to eql("£500.00 every year")
end
end
context "with inferred_check_answers_value" do
context "when Lettings form" do
let(:section_id) { "household" }
let(:subsection_id) { "household_needs" }
let(:page_id) { "armed_forces" }
let(:question_id) { "armedforces" }
it "returns the inferred label value" do
log.armedforces = 3
expect(answer.answer_label).to eql("Prefers not to say")
end
end
context "when Sales form" do
let(:log) { FactoryBot.create(:sales_log, :completed, ethnic_group: 17) }
let(:question) { log.form.get_question("ethnic_group", log) }
it "returns the inferred label value" do
expect(answer.answer_label).to eql("Prefers not to say")
end
end
end
end
describe "#completed?" do
context "when the question has inferred value only for check answers display" do
let(:section_id) { "tenancy_and_property" }
let(:subsection_id) { "property_information" }
let(:page_id) { "property_postcode" }
let(:question_id) { "postcode_full" }
it "returns true" do
log["postcode_known"] = 0
expect(answer.completed?).to be(true)
end
end
end
end

90
spec/models/form/question_spec.rb

@ -225,11 +225,6 @@ RSpec.describe Form::Question, type: :model do
let(:lettings_log) { FactoryBot.build(:lettings_log, :in_progress) }
let(:question_id) { "incfreq" }
it "has an answer label" do
lettings_log.incfreq = 1
expect(question.answer_label(lettings_log)).to eq("Weekly")
end
it "has an update answer link text helper" do
expect(question.action_text(lettings_log)).to match(/Answer/)
lettings_log["incfreq"] = 0
@ -267,37 +262,6 @@ RSpec.describe Form::Question, type: :model do
end
end
context "when type is date" do
let(:section_id) { "local_authority" }
let(:subsection_id) { "local_authority" }
let(:page_id) { "property_major_repairs" }
let(:question_id) { "mrcdate" }
it "displays a formatted answer label" do
lettings_log.mrcdate = Time.zone.local(2021, 10, 11)
expect(question.answer_label(lettings_log)).to eq("11 October 2021")
end
it "can handle nils" do
lettings_log.mrcdate = nil
expect(question.answer_label(lettings_log)).to eq("")
end
end
context "when type is checkbox" do
let(:section_id) { "household" }
let(:subsection_id) { "household_needs" }
let(:page_id) { "accessibility_requirements" }
let(:question_id) { "accessibility_requirements" }
it "has a joined answers label" do
lettings_log.housingneeds_a = 1
lettings_log.housingneeds_c = 1
expected_answer_label = "Fully wheelchair accessible housing, Level access housing"
expect(question.answer_label(lettings_log)).to eq(expected_answer_label)
end
end
context "when a condition is present" do
let(:page_id) { "housing_benefit" }
let(:question_id) { "conditional_question" }
@ -323,60 +287,6 @@ RSpec.describe Form::Question, type: :model do
end
end
end
context "when answers have a suffix dependent on another answer" do
let(:section_id) { "rent_and_charges" }
let(:subsection_id) { "income_and_benefits" }
let(:page_id) { "net_income" }
let(:question_id) { "earnings" }
it "displays the correct label for given suffix and answer the suffix depends on" do
lettings_log.incfreq = 1
lettings_log.earnings = 500
expect(question.answer_label(lettings_log)).to eq("£500.00 every week")
lettings_log.incfreq = 2
expect(question.answer_label(lettings_log)).to eq("£500.00 every month")
lettings_log.incfreq = 3
expect(question.answer_label(lettings_log)).to eq("£500.00 every year")
end
end
context "with inferred_check_answers_value" do
context "when Lettings form" do
let(:section_id) { "household" }
let(:subsection_id) { "household_needs" }
let(:page_id) { "armed_forces" }
let(:question_id) { "armedforces" }
it "returns the inferred label value" do
lettings_log.armedforces = 3
expect(question.answer_label(lettings_log)).to eq("Prefers not to say")
end
end
context "when Sales form" do
let(:sales_log) { FactoryBot.create(:sales_log, :completed, ethnic_group: 17) }
let(:question) { sales_log.form.get_question("ethnic_group", sales_log) }
it "returns the inferred label value" do
expect(question.answer_label(sales_log)).to eq("Prefers not to say")
end
end
end
end
describe ".completed?" do
context "when the question has inferred value only for check answers display" do
let(:section_id) { "tenancy_and_property" }
let(:subsection_id) { "property_information" }
let(:page_id) { "property_postcode" }
let(:question_id) { "postcode_full" }
it "returns true" do
lettings_log["postcode_known"] = 0
expect(question.completed?(lettings_log)).to be(true)
end
end
end
context "when the question has a hidden in check answers attribute with dependencies" do

15
spec/spec_helper.rb

@ -62,13 +62,14 @@ RSpec.configure do |config|
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
# # This allows you to limit a spec run to individual examples or groups
# # you care about by tagging them with `:focus` metadata. When nothing
# # is tagged with `:focus`, all examples get run. RSpec also provides
# # aliases for `it`, `describe`, and `context` that include `:focus`
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
# config.filter_run_when_matching :focus
#
# This allows you to limit a spec run to individual examples or groups
# you care about by tagging them with `:focus` metadata. When nothing
# is tagged with `:focus`, all examples get run. RSpec also provides
# aliases for `it`, `describe`, and `context` that include `:focus`
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
config.filter_run_when_matching :focus
# # Allows RSpec to persist some state between runs in order to support
# # the `--only-failures` and `--next-failure` CLI options. We recommend
# # you configure your source control system to ignore this file.

Loading…
Cancel
Save