Browse Source

Merge branch 'main' into CLDC-687ImplementPropertyInformation

pull/95/head
magicmilo 3 years ago
parent
commit
a76d01b354
  1. 1
      Gemfile
  2. 50
      Gemfile.lock
  3. 22
      README.md
  4. 2
      app/admin/case_logs.rb
  5. 10
      app/constants/db_enums.rb
  6. 23
      app/controllers/case_logs_controller.rb
  7. 26
      app/helpers/check_answers_helper.rb
  8. 12
      app/models/bulk_upload.rb
  9. 2
      app/models/case_log.rb
  10. 53
      app/models/form.rb
  11. 21
      app/validations/date_validations.rb
  12. 10
      app/validations/household_validations.rb
  13. 2
      app/views/case_logs/edit.html.erb
  14. 2
      app/views/case_logs/index.html.erb
  15. 8
      app/views/form/_check_answers_table.html.erb
  16. 12
      app/views/form/check_answers.html.erb
  17. 2
      app/views/form/page.html.erb
  18. 2
      app/views/layouts/_footer.html.erb
  19. 25
      app/views/layouts/application.html.erb
  20. 93
      config/forms/2021_2022.json
  21. 4
      config/forms/schema/generic.json
  22. 11
      db/migrate/20211116102527_change_datetime.rb
  23. 9
      db/schema.rb
  24. 12
      docs/adr/adr-009-form-routing-logic.md
  25. 12
      docs/api/DLUHC-CORE-Data.v1.json
  26. 27
      spec/controllers/case_logs_controller_spec.rb
  27. 87
      spec/features/case_log_spec.rb
  28. 3
      spec/fixtures/complete_case_log.json
  29. 27
      spec/fixtures/forms/test_aboutthislog.json
  30. 37
      spec/fixtures/forms/test_form.json
  31. 3
      spec/fixtures/forms/test_validator.json
  32. 16
      spec/helpers/check_answers_helper_spec.rb
  33. 10
      spec/models/case_log_spec.rb
  34. 38
      spec/models/form_spec.rb

1
Gemfile

@ -34,6 +34,7 @@ gem "roo"
# Json Schema
gem "json-schema"
gem "uk_postcode"
gem "turbo-rails", "~> 0.8"
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console

50
Gemfile.lock

@ -1,6 +1,6 @@
GIT
remote: https://github.com/rspec/rspec-core.git
revision: 42a9fe3a2dc9d5e68811ee646f4b9b4349c18b24
revision: e36aa2a9ebe68acee3ce05190fc2124947b45925
branch: main
specs:
rspec-core (3.11.0.pre)
@ -26,7 +26,7 @@ GIT
GIT
remote: https://github.com/rspec/rspec-rails.git
revision: cfe4db707cc5a0c9437aa90e3059256f30368da4
revision: d3e7b85877fcbcec63f8a76434d8750e7f3b7aef
branch: main
specs:
rspec-rails (5.1.0.pre)
@ -136,7 +136,7 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
chartkick (4.1.0)
chartkick (4.1.2)
childprocess (4.1.0)
coderay (1.1.3)
concurrent-ruby (1.1.9)
@ -170,7 +170,7 @@ GEM
activemodel (>= 6.0)
railties (>= 6.0)
view_component (~> 2.39.0)
govuk_design_system_formbuilder (2.7.5)
govuk_design_system_formbuilder (2.7.6)
actionview (>= 6.0)
activemodel (>= 6.0)
activesupport (>= 6.0)
@ -190,7 +190,7 @@ GEM
railties (>= 5.2, < 6.2)
responders (>= 2, < 4)
iniparse (1.5.0)
jbuilder (2.11.2)
jbuilder (2.11.3)
activesupport (>= 5.0.0)
jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
@ -225,8 +225,6 @@ GEM
minitest (5.14.4)
msgpack (1.4.2)
nio4r (2.5.8)
nokogiri (1.12.5-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.12.5-x86_64-linux)
racc (~> 1.4)
overcommit (0.58.0)
@ -297,35 +295,34 @@ GEM
roo (2.8.3)
nokogiri (~> 1)
rubyzip (>= 1.3.0, < 3.0.0)
rubocop (1.21.0)
rubocop (1.23.0)
parallel (~> 1.10)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.9.1, < 2.0)
rubocop-ast (>= 1.12.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.11.0)
rubocop-ast (1.13.0)
parser (>= 3.0.1.1)
rubocop-govuk (4.1.0)
rubocop (= 1.21.0)
rubocop-ast (= 1.11.0)
rubocop-rails (= 2.12.2)
rubocop-govuk (4.2.0)
rubocop (= 1.23.0)
rubocop-ast (= 1.13.0)
rubocop-rails (= 2.12.4)
rubocop-rake (= 0.6.0)
rubocop-rspec (= 2.4.0)
rubocop-rspec (= 2.6.0)
rubocop-performance (1.12.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.12.2)
rubocop-rails (2.12.4)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.7.0, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-rspec (2.4.0)
rubocop (~> 1.0)
rubocop-ast (>= 1.1.0)
rubocop-rspec (2.6.0)
rubocop (~> 1.19)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@ -352,14 +349,14 @@ GEM
sprockets (4.0.2)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (3.2.2)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets-rails (3.4.0)
actionpack (>= 5.2)
activesupport (>= 5.2)
sprockets (>= 3.0.0)
stimulus-rails (0.7.1)
stimulus-rails (0.7.2)
rails (>= 6.0.0)
thor (1.1.0)
turbo-rails (7.1.1)
turbo-rails (0.8.3)
rails (>= 6.0.0)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
@ -368,7 +365,7 @@ GEM
view_component (2.39.0)
activesupport (>= 5.0.0, < 8.0)
method_source (~> 1.0)
web-console (4.1.0)
web-console (4.2.0)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
@ -386,8 +383,6 @@ GEM
zeitwerk (2.5.1)
PLATFORMS
x86_64-darwin-19
x86_64-darwin-20
x86_64-linux
DEPENDENCIES
@ -424,6 +419,7 @@ DEPENDENCIES
scss_lint-govuk
selenium-webdriver
simplecov
turbo-rails (~> 0.8)
tzinfo-data
uk_postcode
web-console (>= 4.1.0)

22
README.md

@ -135,10 +135,7 @@ The JSON should follow the structure:
}
}
},
"conditional_route_to": {
"[page_name_to_route_to]": {"question_name": "expected_answer"},
"[page_name_to_route_to]": {"question_name": "expected_answer"}
}
"depends_on": { "question_key": "answer_value_required_for_this_page_to_be_shown" }
}
}
}
@ -160,6 +157,19 @@ Assumptions made by the format:
- Radio question answer option selected matches one of conditional e.g. ["answer-options-1-string", "answer-option-3-string"]
- Numeric question value matches condition e.g. [">2"], ["<7"] or ["== 6"]
Page routing:
- Form navigation works by stepping sequentially through every page defined in the JSON form definition for the given subsection. For every page it checks if it has "depends_on" conditions. If it does, it evaluates them to determine whether that page should be show or not.
- In this way we can build up whole branches by having:
```jsonc
"page_1": { "questions": { "question_1: "answer_options": ["A", "B"] } },
"page_2": { "questions": { "question_2: "answer_options": ["C", "D"] }, "depends_on": { "question_1": "A" } },
"page_3": { "questions": { "question_3: "answer_options": ["E", "F"] }, "depends_on": { "question_1": "A" } },
"page_4": { "questions": { "question_4: "answer_options": ["G", "H"] }, "depends_on": { "question_1": "B" } },
```
## JSON Form Validation against Schema
To validate the form JSON against the schema you can run:
@ -182,6 +192,10 @@ This will validate all forms in directories = ["config/forms", "spec/fixtures/fo
- [Technical docs](https://www.rubydoc.info/gems/govuk_design_system_formbuilder/)
- [GitHub repository](https://github.com/DFE-Digital/govuk-formbuilder)
### GOV.UK Frontend for Rails
- [Github repository](https://github.com/DFE-Digital/govuk-components)
### GOV.UK Frontend
- [GitHub repository](https://github.com/alphagov/govuk-frontend)

2
app/admin/case_logs.rb

@ -2,7 +2,7 @@ ActiveAdmin.register CaseLog do
# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
permit_params do
permitted = %i[status tenant_code age1 sex1 tenant_ethnic_group tenant_nationality previous_housing_situation armed_forces ecstat1 other_hhmemb relat2 age2 sex2 ecstat2 relat3 age3 sex3 ecstat3 relat4 age4 sex4 ecstat4 relat5 age5 sex5 ecstat5 relat6 age6 sex6 ecstat6 relat7 age7 person_7_gender ecstat7 relat8 age8 sex8 ecstat8 homelessness reason benefit_cap_spare_room_subsidy armed_forces_active armed_forces_injured armed_forces_partner medical_conditions pregnancy accessibility_requirements condition_effects tenancy_code tenancy_start_date starter_tenancy fixed_term_tenancy tenancy_type letting_type letting_provider la previous_postcode property_relet property_vacancy_reason property_reference property_unit_type property_building_type property_number_of_bedrooms property_void_date majorrepairs mrcdate property_number_of_times_relet property_wheelchair_accessible net_income net_income_frequency net_income_uc_proportion hb rent_frequency basic_rent service_charge personal_service_charge support_charge total_charge tshortfall time_lived_in_la time_on_la_waiting_list prevloc property_postcode reasonable_preference reasonable_preference_reason cbl_letting chr_letting cap_letting hbrentshortfall other_reason accessibility_requirements_fully_wheelchair_accessible_housing accessibility_requirements_wheelchair_access_to_essential_rooms accessibility_requirements_level_access_housing accessibility_requirements_other_disability_requirements accessibility_requirements_no_disability_requirements accessibility_requirements_do_not_know accessibility_requirements_prefer_not_to_say condition_effects_vision condition_effects_hearing condition_effects_mobility condition_effects_dexterity condition_effects_stamina condition_effects_learning condition_effects_memory condition_effects_mental_health condition_effects_social_or_behavioral condition_effects_other condition_effects_prefer_not_to_say reasonable_preference_reason_homeless reasonable_preference_reason_unsatisfactory_housing reasonable_preference_reason_medical_grounds reasonable_preference_reason_avoid_hardship reasonable_preference_reason_do_not_know other_tenancy_type override_net_income_validation net_income_known]
permitted = %i[status tenant_code age1 sex1 tenant_ethnic_group tenant_nationality previous_housing_situation armedforces ecstat1 other_hhmemb relat2 age2 sex2 ecstat2 relat3 age3 sex3 ecstat3 relat4 age4 sex4 ecstat4 relat5 age5 sex5 ecstat5 relat6 age6 sex6 ecstat6 relat7 age7 person_7_gender ecstat7 relat8 age8 sex8 ecstat8 homelessness reason benefit_cap_spare_room_subsidy armed_forces_active armed_forces_injured medical_conditions pregnancy accessibility_requirements condition_effects tenancy_code tenancy_start_date starter_tenancy fixed_term_tenancy tenancy_type letting_type letting_provider la previous_postcode property_relet property_vacancy_reason property_reference property_unit_type property_building_type property_number_of_bedrooms property_void_date majorrepairs mrcdate property_number_of_times_relet property_wheelchair_accessible net_income net_income_frequency net_income_uc_proportion hb rent_frequency basic_rent service_charge personal_service_charge support_charge total_charge tshortfall time_lived_in_la time_on_la_waiting_list prevloc property_postcode reasonable_preference reasonable_preference_reason cbl_letting chr_letting cap_letting hbrentshortfall other_reason accessibility_requirements_fully_wheelchair_accessible_housing accessibility_requirements_wheelchair_access_to_essential_rooms accessibility_requirements_level_access_housing accessibility_requirements_other_disability_requirements accessibility_requirements_no_disability_requirements accessibility_requirements_do_not_know accessibility_requirements_prefer_not_to_say condition_effects_vision condition_effects_hearing condition_effects_mobility condition_effects_dexterity condition_effects_stamina condition_effects_learning condition_effects_memory condition_effects_mental_health condition_effects_social_or_behavioral condition_effects_other condition_effects_prefer_not_to_say reasonable_preference_reason_homeless reasonable_preference_reason_unsatisfactory_housing reasonable_preference_reason_medical_grounds reasonable_preference_reason_avoid_hardship reasonable_preference_reason_do_not_know other_tenancy_type override_net_income_validation net_income_known]
permitted
end

10
app/constants/db_enums.rb

@ -699,4 +699,14 @@ module DbEnums
"East Renfrewshire" => "S12000011",
}
end
def self.armed_forces
{
"A current or former regular in the UK Armed Forces (exc. National Service)" => 1,
"No" => 2,
"Tenant prefers not to say" => 3,
"A current or former reserve in the UK Armed Forces (exc. National Service)" => 4,
"A spouse / civil partner of a UK Armed Forces member who has separated or been bereaved within the last 2 years" => 5,
}
end
end

23
app/controllers/case_logs_controller.rb

@ -59,7 +59,7 @@ class CaseLogsController < ApplicationController
@case_log.page = params[:case_log][:page]
responses_for_page = responses_for_page(@case_log.page)
if @case_log.update(responses_for_page) && @case_log.has_no_unresolved_soft_errors?
redirect_path = get_next_page_path(form, @case_log.page, @case_log)
redirect_path = form.next_page_redirect_path(@case_log.page, @case_log)
redirect_to(send(redirect_path, @case_log))
else
page_info = form.all_pages[@case_log.page]
@ -107,9 +107,13 @@ private
day = params["case_log"]["#{question_key}(3i)"]
month = params["case_log"]["#{question_key}(2i)"]
year = params["case_log"]["#{question_key}(1i)"]
next unless day.present? && month.present? && year.present?
next unless [day, month, year].any?(&:present?)
result[question_key] = Date.new(year.to_i, month.to_i, day.to_i)
result[question_key] = if day.to_i.between?(1, 31) && month.to_i.between?(1, 12) && year.to_i.between?(2000, 2200)
Date.new(year.to_i, month.to_i, day.to_i)
else
Date.new(0, 1, 1)
end
end
next unless question_params
@ -137,17 +141,4 @@ private
params.require(:case_log).permit(CaseLog.editable_fields)
end
def get_next_page_path(form, page, case_log = {})
content = form.all_pages[page]
if content.key?("conditional_route_to")
content["conditional_route_to"].each do |route, conditions|
if conditions.keys.all? { |x| case_log[x].present? } && conditions.all? { |k, v| v.include?(case_log[k]) }
return "case_log_#{route}_path"
end
end
end
form.next_page_redirect_path(page)
end
end

26
app/helpers/check_answers_helper.rb

@ -10,19 +10,8 @@ module CheckAnswersHelper
end
def total_questions(subsection, case_log, form)
total_questions = {}
subsection_keys = form.pages_for_subsection(subsection).keys
page_name = subsection_keys.first
while page_name.to_s != "check_answers" && subsection_keys.include?(page_name)
questions = form.questions_for_page(page_name)
applicable_questions = form.filter_conditional_questions(questions, case_log)
total_questions = total_questions.merge(applicable_questions)
page_name = get_next_page_name(form, page_name, case_log)
end
total_questions
questions = form.questions_for_subsection(subsection)
form.filter_conditional_questions(questions, case_log)
end
def get_next_page_name(form, page_name, case_log)
@ -37,9 +26,14 @@ module CheckAnswersHelper
form.next_page(page_name)
end
def create_update_answer_link(case_log_answer, case_log_id, page)
link_name = case_log_answer.blank? ? "Answer" : "Change"
link_to(link_name, "/case_logs/#{case_log_id}/#{page}", class: "govuk-link").html_safe
def create_update_answer_link(question_title, question_info, case_log, form)
page = form.page_for_question(question_title)
link_name = if question_info["type"] == "checkbox"
question_info["answer_options"].keys.any? { |key| case_log[key] == "Yes" } ? "Change" : "Answer"
else
case_log[question_title].blank? ? "Answer" : "Change"
end
link_to(link_name, "/case_logs/#{case_log.id}/#{page}", class: "govuk-link").html_safe
end
def create_next_missing_question_link(case_log_id, subsection, case_log, form)

12
app/models/bulk_upload.rb

@ -26,12 +26,14 @@ class BulkUpload
else
data_range = FIRST_DATA_ROW..last_row
data_range.map do |row_num|
case_log = CaseLog.create
case_log = CaseLog.create!
map_row(sheet.row(row_num)).each do |attr_key, attr_val|
begin
case_log.update_attribute(attr_key, attr_val)
rescue ArgumentError
update = case_log.update(attr_key => attr_val)
unless update
# TODO: determine what to do when a bulk upload contains field values that don't pass validations
end
rescue ArgumentError
# TODO: determine what we want to do when bulk upload contains totally invalid data for a field.
end
end
end
@ -94,7 +96,7 @@ class BulkUpload
ecstat8: row[42],
ethnic: row[43],
national: row[44],
armed_forces: row[45],
armedforces: row[45],
reservist: row[46],
preg_occ: row[47],
hb: row[48],

2
app/models/case_log.rb

@ -5,6 +5,7 @@ class CaseLogValidator < ActiveModel::Validator
include PropertyValidations
include FinancialValidations
include TenancyValidations
include DateValidations
def validate(record)
# If we've come from the form UI we only want to validate the specific fields
@ -108,6 +109,7 @@ class CaseLog < ApplicationRecord
enum hb: DbEnums.housing_benefit, _suffix: true
enum hbrentshortfall: DbEnums.polar_with_unknown, _suffix: true
enum property_relet: DbEnums.polar, _suffix: true
enum armedforces: DbEnums.armed_forces, _suffix: true
AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze

53
app/models/form.rb

@ -60,37 +60,30 @@ class Form
}.first
end
def next_page(previous_page)
if all_pages[previous_page].key?("default_next_page")
next_page = all_pages[previous_page]["default_next_page"]
return :check_answers if next_page == "check_answers"
return next_page
def page_for_question(question)
all_pages.find { |_page_key, page_value| page_value["questions"].key?(question) }.first
end
subsection = subsection_for_page(previous_page)
previous_page_idx = pages_for_subsection(subsection).keys.index(previous_page)
pages_for_subsection(subsection).keys[previous_page_idx + 1] || :check_answers
def next_page(page, case_log)
subsection = subsection_for_page(page)
page_idx = pages_for_subsection(subsection).keys.index(page)
nxt_page = pages_for_subsection(subsection).keys[page_idx + 1]
return :check_answers if nxt_page.nil?
return nxt_page if page_routed_to?(nxt_page, case_log)
next_page(nxt_page, case_log)
end
def next_page_redirect_path(previous_page)
next_page = next_page(previous_page)
if next_page == :check_answers
subsection = subsection_for_page(previous_page)
def next_page_redirect_path(page, case_log)
nxt_page = next_page(page, case_log)
if nxt_page == :check_answers
subsection = subsection_for_page(page)
"case_log_#{subsection}_check_answers_path"
else
"case_log_#{next_page}_path"
"case_log_#{nxt_page}_path"
end
end
def previous_page(current_page)
subsection = subsection_for_page(current_page)
current_page_idx = pages_for_subsection(subsection).keys.index(current_page)
return unless current_page_idx.positive?
pages_for_subsection(subsection).keys[current_page_idx - 1]
end
def all_questions
@all_questions ||= all_pages.map { |_page_key, page_value|
page_value["questions"]
@ -101,6 +94,10 @@ class Form
applicable_questions = questions
questions.each do |k, question|
unless page_routed_to?(page_for_question(k), case_log)
applicable_questions = applicable_questions.reject { |z| z == k }
end
question.fetch("conditional_for", []).each do |conditional_question_key, condition|
if condition_not_met(case_log, k, question, condition)
applicable_questions = applicable_questions.reject { |z| z == conditional_question_key }
@ -110,6 +107,18 @@ class Form
applicable_questions
end
def page_routed_to?(page, case_log)
return true unless (conditions = page_dependencies(page))
conditions.all? do |question, value|
case_log[question].present? && case_log[question] == value
end
end
def page_dependencies(page)
all_pages[page]["depends_on"]
end
def condition_not_met(case_log, question_key, question, condition)
case question["type"]
when "numeric"

21
app/validations/date_validations.rb

@ -0,0 +1,21 @@
module DateValidations
def validate_property_major_repairs(record)
date_valid?("mrcdate", record)
end
def validate_startdate(record)
date_valid?("startdate", record)
end
def validate_sale_completion_date(record)
date_valid?("sale_completion_date", record)
end
private
def date_valid?(question, record)
if record[question].is_a?(ActiveSupport::TimeWithZone) && record[question].year.zero?
record.errors.add question, "Please enter a valid date"
end
end
end

10
app/validations/household_validations.rb

@ -26,26 +26,26 @@ module HouseholdValidations
end
def validate_armed_forces_injured(record)
if (record.armed_forces == "Yes - a regular" || record.armed_forces == "Yes - a reserve") && record.reservist.blank?
if (record.armedforces == "A current or former regular in the UK Armed Forces (exc. National Service)" || record.armedforces == "A current or former reserve in the UK Armed Forces (exc. National Service)") && record.reservist.blank?
record.errors.add :reservist, "You must answer the armed forces injury question if the tenant has served in the armed forces"
end
if (record.armed_forces == "No" || record.armed_forces == "Prefer not to say") && record.reservist.present?
if (record.armedforces == "No" || record.armedforces == "Prefer not to say") && record.reservist.present?
record.errors.add :reservist, "You must not answer the armed forces injury question if the tenant has not served in the armed forces or prefer not to say was chosen"
end
end
def validate_armed_forces_active_response(record)
if record.armed_forces == "Yes - a regular" && record.leftreg.blank?
if record.armedforces == "A current or former regular in the UK Armed Forces (exc. National Service)" && record.leftreg.blank?
record.errors.add :leftreg, "You must answer the armed forces active question if the tenant has served as a regular in the armed forces"
end
if record.armed_forces != "Yes - a regular" && record.leftreg.present?
if record.armedforces != "A current or former regular in the UK Armed Forces (exc. National Service)" && record.leftreg.present?
record.errors.add :leftreg, "You must not answer the armed forces active question if the tenant has not served as a regular in the armed forces"
end
end
def validate_household_pregnancy(record)
def validate_pregnancy(record)
if (record.preg_occ == "Yes" || record.preg_occ == "Prefer not to say") && !women_of_child_bearing_age_in_household(record)
record.errors.add :preg_occ, "You must answer no as there are no female tenants aged 16-50 in the property"
end

2
app/views/case_logs/edit.html.erb

@ -1,6 +1,6 @@
<%= turbo_frame_tag "case_log_form", target: "_top" do %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-grid-column-two-thirds-from-desktop">
<h1 class="govuk-heading-xl">Tasklist for log
<%= @case_log.id %></h1>

2
app/views/case_logs/index.html.erb

@ -2,7 +2,7 @@
<div class="govuk-grid-column-full">
<h1 class="govuk-heading-xl">Your logs</h1>
</div>
<div class="govuk-grid-column-two-thirds">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= link_to "Create new log", case_logs_path, method: :post, class: "govuk-button" %>

8
app/views/form/_check_answers_table.html.erb

@ -1,5 +1,4 @@
<dl class="govuk-summary-list govuk-!-margin-bottom-9">
<div class="govuk-summary-list__row">
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
<%= question_info["check_answer_label"].to_s.present? ? question_info["check_answer_label"].to_s : question_info["header"].to_s%>
<dt>
@ -7,7 +6,6 @@
<%= form.get_answer_label(@case_log, question_title) %>
</dd>
<dd class="govuk-summary-list__actions">
<%= create_update_answer_link(@case_log[question_title], @case_log.id, page)%>
<%= create_update_answer_link(question_title, question_info, @case_log, form) %>
</dd>
</div>
</dl>
</div>

12
app/views/form/check_answers.html.erb

@ -1,15 +1,13 @@
<%= turbo_frame_tag "case_log_form", target: "_top" do %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<div class="govuk-grid-column-three-quarters-from-desktop">
<h1 class="govuk-heading-l">Check the answers you gave for <%= subsection.humanize(capitalize: false) %></h1>
<%= display_answered_questions_summary(subsection, @case_log, form) %>
<% form.pages_for_subsection(subsection).each do |page, page_info| %>
<% page_info["questions"].each do |question_title, question_info| %>
<% if total_questions(subsection, @case_log, form).include?(question_title) %>
<%= render partial: 'form/check_answers_table', locals: { question_title: question_title, question_info: question_info, case_log: @case_log, page: page, form: form } %>
<%end %>
<%end %>
<dl class="govuk-summary-list govuk-!-margin-bottom-9">
<% total_questions(subsection, @case_log, form).each do |question_title, question_info| %>
<%= render partial: 'form/check_answers_table', locals: { question_title: question_title, question_info: question_info, case_log: @case_log, form: form } %>
<% end %>
</dl>
<%= form_with model: @case_log, method: "get", builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %>
<%= f.govuk_submit "Save and continue" %>
<% end %>

2
app/views/form/page.html.erb

@ -4,7 +4,7 @@
<%= turbo_frame_tag "case_log_form", target: "_top" do %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-grid-column-two-thirds-from-desktop">
<% if page_info["header"].present? %>
<h1 class="govuk-heading-xl">
<%= page_info["header"] %>

2
app/views/layouts/_footer.html.erb

@ -22,7 +22,7 @@
<h3 class="govuk-heading-s govuk-!-margin-bottom-1">Email</h3>
<ul class="govuk-list govuk-!-font-size-16">
<li>
<a class="govuk-link govuk-footer__link" href="mailto:mhclg.digital-services@communities.gov.uk?subject=CORE">mhclg.digital-services@communities.gov.uk</a>
<a class="govuk-link govuk-footer__link" href="mailto:mhclg.digital-services@communities.gov.uk?subject=CORE">dluhc.digital-services@communities.gov.uk</a>
</li>
<li>We aim to respond within 2 working days</li>
</ul>

25
app/views/layouts/application.html.erb

@ -33,25 +33,12 @@
<a href="#main-content" class="govuk-skip-link">Skip to main content</a>
<header class="govuk-header" role="banner" data-module="govuk-header">
<div class="govuk-header__container govuk-width-container">
<div class="govuk-header__logo">
<%= link_to "/", class: "govuk-header__link govuk-header__link--homepage" do %>
<span class="govuk-header__logotype">
<svg role="presentation" focusable="false" class="govuk-header__logotype-crown" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 132 97" height="32" width="36">
<path fill="currentColor" fill-rule="evenodd" d="M25 30.2c3.5 1.5 7.7-.2 9.1-3.7 1.5-3.6-.2-7.8-3.9-9.2-3.6-1.4-7.6.3-9.1 3.9-1.4 3.5.3 7.5 3.9 9zM9 39.5c3.6 1.5 7.8-.2 9.2-3.7 1.5-3.6-.2-7.8-3.9-9.1-3.6-1.5-7.6.2-9.1 3.8-1.4 3.5.3 7.5 3.8 9zM4.4 57.2c3.5 1.5 7.7-.2 9.1-3.8 1.5-3.6-.2-7.7-3.9-9.1-3.5-1.5-7.6.3-9.1 3.8-1.4 3.5.3 7.6 3.9 9.1zm38.3-21.4c3.5 1.5 7.7-.2 9.1-3.8 1.5-3.6-.2-7.7-3.9-9.1-3.6-1.5-7.6.3-9.1 3.8-1.3 3.6.4 7.7 3.9 9.1zm64.4-5.6c-3.6 1.5-7.8-.2-9.1-3.7-1.5-3.6.2-7.8 3.8-9.2 3.6-1.4 7.7.3 9.2 3.9 1.3 3.5-.4 7.5-3.9 9zm15.9 9.3c-3.6 1.5-7.7-.2-9.1-3.7-1.5-3.6.2-7.8 3.7-9.1 3.6-1.5 7.7.2 9.2 3.8 1.5 3.5-.3 7.5-3.8 9zm4.7 17.7c-3.6 1.5-7.8-.2-9.2-3.8-1.5-3.6.2-7.7 3.9-9.1 3.6-1.5 7.7.3 9.2 3.8 1.3 3.5-.4 7.6-3.9 9.1zM89.3 35.8c-3.6 1.5-7.8-.2-9.2-3.8-1.4-3.6.2-7.7 3.9-9.1 3.6-1.5 7.7.3 9.2 3.8 1.4 3.6-.3 7.7-3.9 9.1zM69.7 17.7l8.9 4.7V9.3l-8.9 2.8c-.2-.3-.5-.6-.9-.9L72.4 0H59.6l3.5 11.2c-.3.3-.6.5-.9.9l-8.8-2.8v13.1l8.8-4.7c.3.3.6.7.9.9l-5 15.4v.1c-.2.8-.4 1.6-.4 2.4 0 4.1 3.1 7.5 7 8.1h.2c.3 0 .7.1 1 .1.4 0 .7 0 1-.1h.2c4-.6 7.1-4.1 7.1-8.1 0-.8-.1-1.7-.4-2.4V34l-5.1-15.4c.4-.2.7-.6 1-.9zM66 92.8c16.9 0 32.8 1.1 47.1 3.2 4-16.9 8.9-26.7 14-33.5l-9.6-3.4c1 4.9 1.1 7.2 0 10.2-1.5-1.4-3-4.3-4.2-8.7L108.6 76c2.8-2 5-3.2 7.5-3.3-4.4 9.4-10 11.9-13.6 11.2-4.3-.8-6.3-4.6-5.6-7.9 1-4.7 5.7-5.9 8-.5 4.3-8.7-3-11.4-7.6-8.8 7.1-7.2 7.9-13.5 2.1-21.1-8 6.1-8.1 12.3-4.5 20.8-4.7-5.4-12.1-2.5-9.5 6.2 3.4-5.2 7.9-2 7.2 3.1-.6 4.3-6.4 7.8-13.5 7.2-10.3-.9-10.9-8-11.2-13.8 2.5-.5 7.1 1.8 11 7.3L80.2 60c-4.1 4.4-8 5.3-12.3 5.4 1.4-4.4 8-11.6 8-11.6H55.5s6.4 7.2 7.9 11.6c-4.2-.1-8-1-12.3-5.4l1.4 16.4c3.9-5.5 8.5-7.7 10.9-7.3-.3 5.8-.9 12.8-11.1 13.8-7.2.6-12.9-2.9-13.5-7.2-.7-5 3.8-8.3 7.1-3.1 2.7-8.7-4.6-11.6-9.4-6.2 3.7-8.5 3.6-14.7-4.6-20.8-5.8 7.6-5 13.9 2.2 21.1-4.7-2.6-11.9.1-7.7 8.8 2.3-5.5 7.1-4.2 8.1.5.7 3.3-1.3 7.1-5.7 7.9-3.5.7-9-1.8-13.5-11.2 2.5.1 4.7 1.3 7.5 3.3l-4.7-15.4c-1.2 4.4-2.7 7.2-4.3 8.7-1.1-3-.9-5.3 0-10.2l-9.5 3.4c5 6.9 9.9 16.7 14 33.5 14.8-2.1 30.8-3.2 47.7-3.2z"></path>
<image src="<%= asset_pack_path('media/images/govuk-logotype-crown.png') %>" class="govuk-header__logotype-crown-fallback-image"></image>
</svg>
<span class="govuk-header__logotype-text">
GOV.UK
</span>
</span>
<% end %>
</div>
<div class="govuk-header__content">
<%= link_to "Share Lettings and Sales for Social Housing in England Data with DLUHC", "/", class: "govuk-header__link govuk-header__link--service-name" %>
</div>
</div>
<%= render GovukComponent::HeaderComponent.new(
logotype: 'GOV.UK',
service_name: 'Share Lettings and Sales for Social Housing in England Data with DLUHC',
service_url: '/'
)
%>
</header>
<aside class="govuk-width-container">

93
config/forms/2021_2022.json

@ -23,20 +23,15 @@
"1": "No"
}
}
},
"conditional_route_to": {
"organisation_details": { "gdpr_acceptance": "Yes" }
},
"default_next_page": "gdpr_declined"
}
},
"gdpr_declined": {
"header": "You cannot use this service",
"hint_text": "",
"description": "We cannot accept data about a tenant or buyer unless they’ve seen the DLUHC privacy notice.",
"questions": {
},
"default_next_page" : "check_answers"
"depends_on": { "gdpr_acceptance": "No" }
},
"organisation_details": {
"header": "About this log",
@ -62,7 +57,8 @@
"1": "B"
}
}
}
},
"depends_on": { "gdpr_acceptance": "Yes" }
},
"sale_or_letting": {
"header": "About this log",
@ -79,9 +75,7 @@
}
}
},
"conditional_route_to": {
"sale_completion_date": { "sale_or_letting": "Sale" }
}
"depends_on": { "gdpr_acceptance": "Yes" }
},
"tenant_same_property_renewal": {
"header": "About this log",
@ -97,7 +91,8 @@
"1": "No"
}
}
}
},
"depends_on": { "gdpr_acceptance": "Yes", "sale_or_letting": "Letting" }
},
"startdate": {
"header": "About this log",
@ -109,7 +104,8 @@
"hint_text": "For example, 27 3 2007",
"type": "date"
}
}
},
"depends_on": { "gdpr_acceptance": "Yes", "sale_or_letting": "Letting" }
},
"about_this_letting": {
"header": "Tell us about this letting",
@ -147,7 +143,8 @@
"1": "General Needs"
}
}
}
},
"depends_on": { "gdpr_acceptance": "Yes", "sale_or_letting": "Letting" }
},
"tenant_code": {
"header": "",
@ -160,7 +157,7 @@
"type": "text"
}
},
"default_next_page": "check_answers"
"depends_on": { "gdpr_acceptance": "Yes", "sale_or_letting": "Letting" }
},
"sale_completion_date": {
"header": "Sale Completion Date",
@ -172,7 +169,8 @@
"hint_text": "For example, 27 3 2007",
"type": "date"
}
}
},
"depends_on": { "gdpr_acceptance": "Yes", "sale_or_letting": "Sale" }
},
"purchaser_code": {
"header": "About this log",
@ -185,7 +183,7 @@
"type": "text"
}
},
"default_next_page": "check_answers"
"depends_on": { "gdpr_acceptance": "Yes", "sale_or_letting": "Sale" }
}
}
}
@ -197,19 +195,7 @@
"household_characteristics": {
"label": "Household characteristics",
"pages": {
"tenant_code": {
"header": "",
"description": "",
"questions": {
"tenant_code": {
"check_answer_label": "Tenant code",
"header": "What is the tenant code?",
"hint_text": "",
"type": "text"
}
}
},
"age1": {
"person_1_age": {
"header": "",
"description": "",
"questions": {
@ -307,7 +293,7 @@
}
}
},
"tenant_economic_status": {
"person_1_economic": {
"header": "",
"description": "",
"questions": {
@ -884,20 +870,21 @@
"header": "Experience of the UK Armed Forces",
"description": "",
"questions": {
"armed_forces": {
"header": "Has the tenant ever served in the UK armed forces?",
"hint_text": "",
"armedforces": {
"header": "Is anyone in the household...",
"hint_text": "This excludes national service",
"type": "radio",
"check_answer_label": "Armed Forces",
"answer_options": {
"0": "Yes - a regular",
"1": "Yes - a reserve",
"2": "No",
"3": "Prefer not to say"
"0":"A current or former regular in the UK Armed Forces (exc. National Service)",
"1":"A current or former reserve in the UK Armed Forces (exc. National Service)",
"2": "A spouse / civil partner of a UK Armed Forces member who has separated or been bereaved within the last 2 years",
"3": "No",
"4": "Tenant prefers not to say"
},
"conditional_for": {
"leftreg": ["Yes - a regular", "Yes - a reserve"],
"reservist": ["Yes - a regular", "Yes - a reserve"]
"leftreg": ["A current or former regular in the UK Armed Forces (exc. National Service)"],
"reservist": ["A current or former regular in the UK Armed Forces (exc. National Service)"]
}
},
"leftreg": {
@ -922,18 +909,6 @@
"1": "No",
"2": "Prefer not to say"
}
},
"armed_forces_partner": {
"header": "Was the tenant the spouse or civil partner of someone who served in the UK armed forces?",
"hint_text": "",
"type": "radio",
"check_answer_label": "Was the tenant the spouse or civil partner of someone who served in the UK armed forces?",
"answer_options": {
"0": "Yes - was the spouse or civil partner of a UK Armed Forces member and have separated within the last 2 years",
"1": "Yes - was the spouse or civil partner of a UK Armed Forces member who died within the last 2 years",
"2": "No",
"3": "Prefer not to say"
}
}
}
},
@ -1028,18 +1003,6 @@
"tenancy_information": {
"label": "Tenancy information",
"pages": {
"tenancy_code": {
"header": "",
"description": "",
"questions": {
"tenancy_code": {
"check_answer_label": "What is the tenancy code?",
"header": "What is the tenancy code?",
"hint_text": "",
"type": "text"
}
}
},
"starter_tenancy": {
"header": "",
"description": "",
@ -1088,7 +1051,7 @@
"4": "Other"
},
"conditional_for": {
"other_tenancy_type": ["Other"]
"tenancyother": ["Other"]
}
},
"tenancyother": {

4
config/forms/schema/generic.json

@ -44,7 +44,7 @@
"pages": {
"type": "object",
"patternProperties": {
"^(?!(conditional_route_to))[a-z_]+$": {
"^(?!(depends_on))[a-z_]+$": {
"description": "Page Name",
"type": "object",
"required": ["header", "questions"],
@ -105,7 +105,7 @@
}
},
"additionalProperties": {
"conditional_route_to": {
"depends_on": {
"description": "",
"type": "object"
}

11
db/migrate/20211116102527_change_datetime.rb

@ -1,5 +1,5 @@
class ChangeDatetime < ActiveRecord::Migration[6.1]
def change
def up
change_table :case_logs, bulk: true do |t|
t.remove :sale_completion_date
t.column :sale_completion_date, :datetime
@ -7,4 +7,13 @@ class ChangeDatetime < ActiveRecord::Migration[6.1]
t.column :startdate, :datetime
end
end
def down
change_table :case_logs, bulk: true do |t|
t.remove :sale_completion_date
t.column :sale_completion_date, :string
t.remove :startdate
t.column :startdate, :string
end
end
end

9
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_11_16_102527) do
ActiveRecord::Schema.define(version: 2021_11_18_090831) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -25,7 +25,6 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "ethnic"
t.integer "national"
t.integer "prevten"
t.string "armed_forces"
t.integer "ecstat1"
t.integer "hhmemb"
t.string "relat2"
@ -60,7 +59,6 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "underoccupation_benefitcap"
t.integer "leftreg"
t.integer "reservist"
t.string "armed_forces_partner"
t.integer "illness"
t.integer "preg_occ"
t.string "accessibility_requirements"
@ -121,6 +119,8 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "rp_dontknow"
t.datetime "discarded_at"
t.string "tenancyother"
t.integer "override_net_income_validation"
t.string "net_income_known"
t.string "gdpr_acceptance"
t.string "gdpr_declined"
t.string "property_owner_organisation"
@ -131,8 +131,6 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.string "intermediate_rent_product_name"
t.string "needs_type"
t.string "purchaser_code"
t.integer "override_net_income_validation"
t.string "net_income_known"
t.integer "reason"
t.string "propcode"
t.integer "majorrepairs"
@ -154,6 +152,7 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "incref"
t.datetime "sale_completion_date"
t.datetime "startdate"
t.integer "armedforces"
t.index ["discarded_at"], name: "index_case_logs_on_discarded_at"
end

12
docs/adr/adr-009-form-routing-logic.md

@ -0,0 +1,12 @@
### ADR - 009: Form Routing Logic
There are 2 ways you can think about form (page) routing logic:
1. Based on the answer you give to a page you are navigated to some point in the form, i.e. a "Jump to"
2. Each question is considered sequentially and independently and we evaluate whether it should be shown or not
Our Form Definition DSL takes the second approach. This has a couple of advantages:
- It makes the check answers pattern easier to code as you can ask each page directly: "Have the conditions for you to be shown been met?", with approach 1, you would effectively have to traverse the full route branch to see if a particular page was shown for each page/question which adds complexity.
- It makes it easier to look at the JSON and see at a glance what conditions will show or hide a page, which is closer to how the business logic is discussed and is easier to reason about.

12
docs/api/DLUHC-CORE-Data.v1.json

@ -261,7 +261,7 @@
"ethnic": "White: English/Scottish/Welsh/Northern Irish/British",
"national": "UK national resident in UK",
"prevten": "Private sector tenancy",
"armed_forces": "Yes - a regular",
"armedforces": "A current or former regular in the UK Armed Forces (exc. National Service)",
"ecstat1": "Full-time - 30 hours or more",
"other_hhmemb": 7,
"relat2": "Partner",
@ -297,7 +297,6 @@
"underoccupation_benefitcap": "No",
"leftreg": "No",
"reservist": "No",
"armed_forces_partner": "No",
"illness": "Yes",
"preg_occ": "No",
"accessibility_requirements": "No",
@ -438,7 +437,7 @@
"type": "string",
"minLength": 1
},
"armed_forces": {
"armedforces": {
"type": "string",
"minLength": 1
},
@ -787,10 +786,6 @@
"type": "string",
"minLength": 1
},
"armed_forces_partner": {
"type": "string",
"minLength": 1
},
"illness": {
"type": "string",
"minLength": 1
@ -1050,7 +1045,7 @@
"ethnic",
"national",
"prevten",
"armed_forces",
"armedforces",
"ecstat1",
"other_hhmemb",
"relat2",
@ -1086,7 +1081,6 @@
"underoccupation_benefitcap",
"leftreg",
"reservist",
"armed_forces_partner",
"illness",
"preg_occ",
"accessibility_requirements",

27
spec/controllers/case_logs_controller_spec.rb

@ -125,7 +125,7 @@ RSpec.describe CaseLogsController, type: :controller do
context "conditional routing" do
before do
allow_any_instance_of(CaseLogValidator).to receive(:validate_household_pregnancy).and_return(true)
allow_any_instance_of(CaseLogValidator).to receive(:validate_pregnancy).and_return(true)
end
let(:case_log_form_conditional_question_yes_params) do
@ -184,7 +184,7 @@ RSpec.describe CaseLogsController, type: :controller do
"mrcdate(1i)": "2021",
"mrcdate(2i)": "05",
"mrcdate(3i)": "04",
page: "major_repairs_date",
page: "property_major_repairs",
}
end
it "saves full and partial dates" do
@ -200,27 +200,4 @@ RSpec.describe CaseLogsController, type: :controller do
end
end
end
describe "get_next_page_path" do
let(:previous_page) { "net_income" }
let(:last_previous_page) { "housing_benefit" }
let(:previous_conditional_page) { "conditional_question" }
let(:form_handler) { FormHandler.instance }
let(:form) { form_handler.get_form("test_form") }
let(:case_log_controller) { CaseLogsController.new }
it "returns a correct page path if there is no conditional routing" do
expect(case_log_controller.send(:get_next_page_path, form, previous_page)).to eq("case_log_net_income_uc_proportion_path")
end
it "returns a check answers page if previous page is the last page" do
expect(case_log_controller.send(:get_next_page_path, form, last_previous_page)).to eq("case_log_income_and_benefits_check_answers_path")
end
it "returns a correct page path if there is conditional routing" do
responses_for_page = {}
responses_for_page["preg_occ"] = "No"
expect(case_log_controller.send(:get_next_page_path, form, previous_conditional_page, responses_for_page)).to eq("case_log_conditional_question_no_page_path")
end
end
end

87
spec/features/case_log_spec.rb

@ -301,6 +301,19 @@ RSpec.describe "Test Features" do
expect(page).to have_link("Change", href: "/case_logs/#{empty_case_log.id}/person_1_age")
end
it "should have a change link for answered questions" do
visit("/case_logs/#{empty_case_log.id}/household_needs/check_answers")
assert_selector "a", text: /Answer\z/, count: 4
assert_selector "a", text: "Change", count: 0
visit("/case_logs/#{empty_case_log.id}/accessibility_requirements")
check("case-log-accessibility-requirements-housingneeds-c-field")
click_button("Save and continue")
visit("/case_logs/#{empty_case_log.id}/household_needs/check_answers")
assert_selector "a", text: /Answer\z/, count: 3
assert_selector "a", text: "Change", count: 1
expect(page).to have_link("Change", href: "/case_logs/#{empty_case_log.id}/accessibility_requirements")
end
it "should have a link pointing to the first question if no questions are answered" do
visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers")
expect(page).to have_content("You answered 0 of 4 questions")
@ -362,13 +375,13 @@ RSpec.describe "Test Features" do
it "shows conditional questions if the required answer is selected and hides it again when a different answer option is selected", js: true do
visit("/case_logs/#{id}/armed_forces")
# Something about our styling makes the selenium webdriver think the actual radio buttons are not visible so we allow label click here
choose("case-log-armed-forces-yes-a-regular-field", allow_label_click: true)
choose("case-log-armedforces-a-current-or-former-regular-in-the-uk-armed-forces-exc-national-service-field", allow_label_click: true)
expect(page).to have_selector("#reservist_div")
choose("case-log-reservist-no-field", allow_label_click: true)
expect(page).to have_checked_field("case-log-reservist-no-field", visible: false)
choose("case-log-armed-forces-no-field", allow_label_click: true)
choose("case-log-armedforces-no-field", allow_label_click: true)
expect(page).not_to have_selector("#reservist_div")
choose("case-log-armed-forces-yes-a-regular-field", allow_label_click: true)
choose("case-log-armedforces-a-current-or-former-regular-in-the-uk-armed-forces-exc-national-service-field", allow_label_click: true)
expect(page).to have_unchecked_field("case-log-reservist-no-field", visible: false)
end
end
@ -449,7 +462,7 @@ RSpec.describe "Test Features" do
describe "conditional page routing", js: true do
before do
allow_any_instance_of(CaseLogValidator).to receive(:validate_household_pregnancy).and_return(true)
allow_any_instance_of(CaseLogValidator).to receive(:validate_pregnancy).and_return(true)
end
it "can route the user to a different page based on their answer on the current page" do
@ -466,27 +479,69 @@ RSpec.describe "Test Features" do
expect(page).to have_current_path("/case_logs/#{id}/conditional_question_no_page")
end
it "can route based on page inclusion rules" do
visit("/case_logs/#{id}/conditional_question_yes_page")
choose("case-log-cbl-yes-field", allow_label_click: true)
it "can route based on multiple conditions" do
visit("/case_logs/#{id}/person_1_gender")
choose("case-log-sex1-female-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/household_number_of_other_members")
visit("/case_logs/#{id}/conditional_question")
choose("case-log-preg-occ-no-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/conditional_question_no_page")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/conditional_question/check_answers")
end
end
it "can route to the default next page" do
visit("/case_logs/#{id}/conditional_question")
describe "date validation", js: true do
def fill_in_date(case_log_id, question, day, month, year, path)
visit("/case_logs/#{case_log_id}/#{path}")
fill_in("#{question}_1i", with: year)
fill_in("#{question}_2i", with: month)
fill_in("#{question}_3i", with: day)
end
it "does not allow out of range dates to be submitted" do
fill_in_date(id, "case_log_mrcdate", 3100, 12, 2000, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/conditional_question/check_answers")
expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
fill_in_date(id, "case_log_mrcdate", 12, 1, 20_000, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
fill_in_date(id, "case_log_mrcdate", 13, 100, 2020, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
fill_in_date(id, "case_log_mrcdate", 21, 11, 2020, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/local_authority/check_answers")
end
it "can route based on multiple conditions" do
visit("/case_logs/#{id}/person_1_gender")
choose("case-log-sex1-female-field", allow_label_click: true)
it "does not allow non numeric inputs to be submitted" do
fill_in_date(id, "case_log_mrcdate", "abc", "de", "ff", "property_major_repairs")
click_button("Save and continue")
visit("/case_logs/#{id}/conditional_question")
choose("case-log-preg-occ-yes-field", allow_label_click: true)
expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
end
it "does not allow partial inputs to be submitted" do
fill_in_date(id, "case_log_mrcdate", 21, 12, nil, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
fill_in_date(id, "case_log_mrcdate", 12, nil, 2000, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
fill_in_date(id, "case_log_mrcdate", nil, 10, 2020, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
end
it "allows valid inputs to be submitted" do
fill_in_date(id, "case_log_mrcdate", 21, 11, 2020, "property_major_repairs")
click_button("Save and continue")
expect(page).to have_current_path("/case_logs/#{id}/rent")
expect(page).to have_current_path("/case_logs/#{id}/local_authority/check_answers")
end
end
end

3
spec/fixtures/complete_case_log.json vendored

@ -6,7 +6,7 @@
"ethnic": "White: English/Scottish/Welsh/Northern Irish/British",
"national": "UK national resident in UK",
"prevten": "Private sector tenancy",
"armed_forces": "Yes - a regular",
"armedforces": "A current or former regular in the UK Armed Forces (exc. National Service)",
"ecstat1": "Full-time - 30 hours or more",
"other_hhmemb": 7,
"hhmemb": 8,
@ -43,7 +43,6 @@
"underoccupation_benefitcap": "No",
"leftreg": "No - they left up to 5 years ago",
"reservist": "No",
"armed_forces_partner": "No",
"illness": "Yes",
"preg_occ": "No",
"accessibility_requirements": "No",

27
spec/fixtures/forms/test_aboutthislog.json vendored

@ -24,8 +24,7 @@
},
"conditional_route_to": {
"organisation_details": { "gdpr_acceptance": "Yes" }
},
"default_next_page": "gdpr_declined"
}
},
"gdpr_declined": {
"header": "You cannot use this service",
@ -33,8 +32,7 @@
"description": "We cannot accept data about a tenant or buyer unless they’ve seen the DLUHC privacy notice.",
"questions": {
},
"default_next_page" : "check_answers"
}
},
"organisation_details": {
"header": "About this log",
@ -76,11 +74,7 @@
"1": "Letting"
}
}
},
"conditional_route_to": {
"tenant_same_property_renewal": { "sale_or_letting": "Letting" }
},
"default_next_page" : "check_answers"
}
},
"tenant_same_property_renewal": {
"header": "About this log",
@ -96,7 +90,8 @@
"1": "No"
}
}
}
},
"depends_on": { "sale_or_letting": "Letting" }
},
"tenancy_start_date": {
"header": "About this log",
@ -108,7 +103,8 @@
"hint_text": "For example, 27 3 2007",
"type": "date"
}
}
},
"depends_on": { "sale_or_letting": "Letting" }
},
"letting_type": {
"header": "About this log",
@ -146,7 +142,8 @@
"1": "General Needs"
}
}
}
},
"depends_on": { "sale_or_letting": "Letting" }
},
"sale_completion_date": {
"header": "About this log",
@ -158,7 +155,8 @@
"hint_text": "For example, 27 3 2007",
"type": "date"
}
}
},
"depends_on": { "sale_or_letting": "Sale" }
},
"purchaser_code": {
"header": "About this log",
@ -170,7 +168,8 @@
"hint_text": "",
"type": "text"
}
}
},
"depends_on": { "sale_or_letting": "Sale" }
}
}
}

37
spec/fixtures/forms/test_form.json vendored

@ -107,23 +107,26 @@
"armed_forces": {
"header": "Experience of the UK Armed Forces",
"questions": {
"armed_forces": {
"header": "Has the tenant ever served in the UK armed forces?",
"armedforces": {
"header": "Is anyone in the household...",
"hint_text": "This excludes national service",
"type": "radio",
"check_answer_label": "Armed Forces",
"answer_options": {
"0": "Yes - a regular",
"1": "Yes - a reserve",
"2": "No",
"3": "Prefer not to say"
"0":"A current or former regular in the UK Armed Forces (exc. National Service)",
"1":"A current or former reserve in the UK Armed Forces (exc. National Service)",
"2": "A spouse / civil partner of a UK Armed Forces member who has separated or been bereaved within the last 2 years",
"3": "No",
"4": "Tenant prefers not to say"
},
"conditional_for": {
"leftreg": ["Yes - a regular", "Yes - a reserve"],
"reservist": ["Yes - a regular", "Yes - a reserve"]
"leftreg": ["A current or former regular in the UK Armed Forces (exc. National Service)"],
"reservist": ["A current or former regular in the UK Armed Forces (exc. National Service)"]
}
},
"leftreg": {
"header": "Are they still serving?",
"hint_text": "",
"type": "radio",
"check_answer_label": "When did they leave the Armed Forces?",
"answer_options": {
@ -135,6 +138,7 @@
},
"reservist": {
"header": "Were they seriously injured or ill as a result of their service?",
"hint_text": "",
"type": "radio",
"check_answer_label": "Has anyone in the household been seriously injured or ill as a result of their service in the armed forces?",
"answer_options": {
@ -244,13 +248,7 @@
"1": "No"
}
}
},
"conditional_route_to": {
"rent": { "preg_occ": "Yes", "sex1": "Female" },
"conditional_question_yes_page": { "preg_occ": "Yes" },
"conditional_question_no_page": { "preg_occ": "No" }
},
"default_next_page": "check_answers"
}
},
"conditional_question_yes_page": {
"questions": {
@ -264,7 +262,7 @@
}
}
},
"default_next_page": "check_answers"
"depends_on": { "preg_occ": "Yes" }
},
"conditional_question_no_page": {
"questions": {
@ -278,7 +276,7 @@
}
}
},
"default_next_page": "conditional_question_no_second_page"
"depends_on": { "preg_occ": "No" }
},
"conditional_question_no_second_page": {
"questions": {
@ -291,7 +289,8 @@
"1": "No"
}
}
}
},
"depends_on": { "preg_occ": "No", "sex1": "Male" }
}
}
}
@ -511,7 +510,7 @@
}
}
},
"major_repairs_date": {
"property_major_repairs": {
"questions": {
"mrcdate": {
"check_answer_label": "What was the major repairs completion date?",

3
spec/fixtures/forms/test_validator.json vendored

@ -22,9 +22,8 @@
"type": "text"
}
},
"conditional_route_to": {"test": "Yes"}
"depends_on": {"test": "Yes"}
},
"conditional_route_to": {"test": "Yes"},
"person_1_age": {
"header": "",
"description": "",

16
spec/helpers/check_answers_helper_spec.rb

@ -11,7 +11,7 @@ RSpec.describe CheckAnswersHelper do
)
end
let(:case_log_with_met_radio_condition) do
FactoryBot.create(:case_log, armed_forces: "Yes - a regular",
FactoryBot.create(:case_log, armedforces: "A current or former regular in the UK Armed Forces (exc. National Service)",
reservist: "No",
leftreg: "Yes")
end
@ -47,7 +47,7 @@ RSpec.describe CheckAnswersHelper do
end
it "ignores questions with unmet radio conditions" do
case_log["armed_forces"] = "No"
case_log["armedforces"] = "No"
expect(total_answered_questions(subsection_with_radio_conditionals, case_log, form)).to equal(1)
end
@ -119,6 +119,7 @@ RSpec.describe CheckAnswersHelper do
it "counts correct questions when the conditional question is answered" do
case_log["preg_occ"] = "No"
case_log["sex1"] = "Male"
expect(total_number_of_questions(conditional_routing_subsection, case_log, form)).to eq(3)
end
end
@ -126,7 +127,6 @@ RSpec.describe CheckAnswersHelper do
context "total questions" do
it "returns total questions" do
result = total_questions(subsection, case_log, form)
expect(result.class).to eq(Hash)
expected_keys = %w[earnings incfreq benefits hb]
expect(result.keys).to eq(expected_keys)
end
@ -134,14 +134,14 @@ RSpec.describe CheckAnswersHelper do
context "conditional questions on the same page" do
it "it filters out conditional questions that were not displayed" do
result = total_questions(conditional_page_subsection, case_log, form)
expected_keys = %w[armed_forces illness accessibility_requirements condition_effects]
expected_keys = %w[armedforces illness accessibility_requirements condition_effects]
expect(result.keys).to eq(expected_keys)
end
it "it includes conditional questions that were displayed" do
case_log["armed_forces"] = "Yes - a regular"
case_log["armedforces"] = "A current or former regular in the UK Armed Forces (exc. National Service)"
result = total_questions(conditional_page_subsection, case_log, form)
expected_keys = %w[armed_forces leftreg reservist illness accessibility_requirements condition_effects]
expected_keys = %w[armedforces leftreg reservist illness accessibility_requirements condition_effects]
expect(result.keys).to eq(expected_keys)
end
end
@ -157,14 +157,14 @@ RSpec.describe CheckAnswersHelper do
case_log["preg_occ"] = "Yes"
case_log["sex1"] = "Female"
result = total_questions(conditional_routing_subsection, case_log, form)
expected_keys = %w[preg_occ]
expected_keys = %w[preg_occ cbl]
expect(result.keys).to match_array(expected_keys)
end
it "it includes conditional pages and questions that were displayed" do
case_log["preg_occ"] = "No"
result = total_questions(conditional_routing_subsection, case_log, form)
expected_keys = %w[preg_occ conditional_question_no_question conditional_question_no_second_question]
expected_keys = %w[preg_occ conditional_question_no_question]
expect(result.keys).to match_array(expected_keys)
end
end

10
spec/models/case_log_spec.rb

@ -84,14 +84,14 @@ RSpec.describe Form, type: :model do
context "armed forces injured validation" do
it "must be answered if tenant was a regular or reserve in armed forces" do
expect {
CaseLog.create!(armed_forces: "Yes - a regular",
CaseLog.create!(armedforces: "A current or former regular in the UK Armed Forces (exc. National Service)",
reservist: nil)
}.to raise_error(ActiveRecord::RecordInvalid)
end
it "must be answered if tenant was not a regular or reserve in armed forces" do
expect {
CaseLog.create!(armed_forces: "No",
CaseLog.create!(armedforces: "No",
reservist: "Yes")
}.to raise_error(ActiveRecord::RecordInvalid)
end
@ -223,14 +223,14 @@ RSpec.describe Form, type: :model do
context "armed forces active validation" do
it "must be answered if ever served in the forces as a regular" do
expect {
CaseLog.create!(armed_forces: "Yes - a regular",
CaseLog.create!(armedforces: "A current or former regular in the UK Armed Forces (exc. National Service)",
leftreg: nil)
}.to raise_error(ActiveRecord::RecordInvalid)
end
it "must not be answered if not ever served as a regular" do
expect {
CaseLog.create!(armed_forces: "No",
CaseLog.create!(armedforces: "No",
leftreg: "Yes")
}.to raise_error(ActiveRecord::RecordInvalid)
end
@ -238,7 +238,7 @@ RSpec.describe Form, type: :model do
# Crossover over tests here as injured must be answered as well for no error
it "must be answered if ever served in the forces as a regular" do
expect do
CaseLog.create!(armed_forces: "Yes - a regular",
CaseLog.create!(armedforces: "A current or former regular in the UK Armed Forces (exc. National Service)",
leftreg: "Yes",
reservist: "Yes")
end

38
spec/models/form_spec.rb

@ -3,11 +3,12 @@ require "rails_helper"
RSpec.describe Form, type: :model do
form_handler = FormHandler.instance
let(:form) { form_handler.get_form("test_form") }
let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
describe ".next_page" do
let(:previous_page) { "person_1_age" }
it "returns the next page given the previous" do
expect(form.next_page(previous_page)).to eq("person_1_gender")
expect(form.next_page(previous_page, case_log)).to eq("person_1_gender")
end
end
@ -18,22 +19,6 @@ RSpec.describe Form, type: :model do
end
end
describe ".previous_page" do
context "given a page in the middle of a subsection" do
let(:current_page) { "person_1_age" }
it "returns the previous page given the current" do
expect(form.previous_page(current_page)).to eq("tenant_code")
end
end
context "given the first page in a subsection" do
let(:current_page) { "tenant_code" }
it "returns empty string" do
expect(form.previous_page(current_page)).to be_nil
end
end
end
describe ".questions_for_subsection" do
let(:subsection) { "income_and_benefits" }
it "returns all questions for subsection" do
@ -42,4 +27,23 @@ RSpec.describe Form, type: :model do
expect(result.keys).to eq(%w[earnings incfreq benefits hb])
end
end
describe "next_page_redirect_path" do
let(:previous_page) { "net_income" }
let(:last_previous_page) { "housing_benefit" }
let(:previous_conditional_page) { "conditional_question" }
it "returns a correct page path if there is no conditional routing" do
expect(form.next_page_redirect_path(previous_page, case_log)).to eq("case_log_net_income_uc_proportion_path")
end
it "returns a check answers page if previous page is the last page" do
expect(form.next_page_redirect_path(last_previous_page, case_log)).to eq("case_log_income_and_benefits_check_answers_path")
end
it "returns a correct page path if there is conditional routing" do
case_log["preg_occ"] = "No"
expect(form.next_page_redirect_path(previous_conditional_page, case_log)).to eq("case_log_conditional_question_no_page_path")
end
end
end

Loading…
Cancel
Save