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. 24
      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. 24
      app/views/form/_check_answers_table.html.erb
  16. 14
      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. 24
      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. 39
      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 # Json Schema
gem "json-schema" gem "json-schema"
gem "uk_postcode" gem "uk_postcode"
gem "turbo-rails", "~> 0.8"
group :development, :test do group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console # Call 'byebug' anywhere in the code to stop execution and get a debugger console

50
Gemfile.lock

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

24
README.md

@ -135,10 +135,7 @@ The JSON should follow the structure:
} }
} }
}, },
"conditional_route_to": { "depends_on": { "question_key": "answer_value_required_for_this_page_to_be_shown" }
"[page_name_to_route_to]": {"question_name": "expected_answer"},
"[page_name_to_route_to]": {"question_name": "expected_answer"}
}
} }
} }
} }
@ -160,12 +157,25 @@ Assumptions made by the format:
- Radio question answer option selected matches one of conditional e.g. ["answer-options-1-string", "answer-option-3-string"] - 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"] - 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 ## JSON Form Validation against Schema
To validate the form JSON against the schema you can run: To validate the form JSON against the schema you can run:
`rake form_definition:validate["config/forms/2021_2022.json"]` `rake form_definition:validate["config/forms/2021_2022.json"]`
n.b. You may have to escape square brackets in zsh n.b. You may have to escape square brackets in zsh
`rake form_definition:validate\["config/forms/2021_2022.json"\]` `rake form_definition:validate\["config/forms/2021_2022.json"\]`
This will validate the given form definition against the schema in `config/forms/schema/generic.json`. This will validate the given form definition against the schema in `config/forms/schema/generic.json`.
@ -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/) - [Technical docs](https://www.rubydoc.info/gems/govuk_design_system_formbuilder/)
- [GitHub repository](https://github.com/DFE-Digital/govuk-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 ### GOV.UK Frontend
- [GitHub repository](https://github.com/alphagov/govuk-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: # See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters # https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
permit_params do 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 permitted
end end

10
app/constants/db_enums.rb

@ -699,4 +699,14 @@ module DbEnums
"East Renfrewshire" => "S12000011", "East Renfrewshire" => "S12000011",
} }
end 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 end

23
app/controllers/case_logs_controller.rb

@ -59,7 +59,7 @@ class CaseLogsController < ApplicationController
@case_log.page = params[:case_log][:page] @case_log.page = params[:case_log][:page]
responses_for_page = responses_for_page(@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? 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)) redirect_to(send(redirect_path, @case_log))
else else
page_info = form.all_pages[@case_log.page] page_info = form.all_pages[@case_log.page]
@ -107,9 +107,13 @@ private
day = params["case_log"]["#{question_key}(3i)"] day = params["case_log"]["#{question_key}(3i)"]
month = params["case_log"]["#{question_key}(2i)"] month = params["case_log"]["#{question_key}(2i)"]
year = params["case_log"]["#{question_key}(1i)"] 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 end
next unless question_params next unless question_params
@ -137,17 +141,4 @@ private
params.require(:case_log).permit(CaseLog.editable_fields) params.require(:case_log).permit(CaseLog.editable_fields)
end 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 end

26
app/helpers/check_answers_helper.rb

@ -10,19 +10,8 @@ module CheckAnswersHelper
end end
def total_questions(subsection, case_log, form) def total_questions(subsection, case_log, form)
total_questions = {} questions = form.questions_for_subsection(subsection)
subsection_keys = form.pages_for_subsection(subsection).keys form.filter_conditional_questions(questions, case_log)
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
end end
def get_next_page_name(form, page_name, case_log) def get_next_page_name(form, page_name, case_log)
@ -37,9 +26,14 @@ module CheckAnswersHelper
form.next_page(page_name) form.next_page(page_name)
end end
def create_update_answer_link(case_log_answer, case_log_id, page) def create_update_answer_link(question_title, question_info, case_log, form)
link_name = case_log_answer.blank? ? "Answer" : "Change" page = form.page_for_question(question_title)
link_to(link_name, "/case_logs/#{case_log_id}/#{page}", class: "govuk-link").html_safe 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 end
def create_next_missing_question_link(case_log_id, subsection, case_log, form) 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 else
data_range = FIRST_DATA_ROW..last_row data_range = FIRST_DATA_ROW..last_row
data_range.map do |row_num| 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| map_row(sheet.row(row_num)).each do |attr_key, attr_val|
begin update = case_log.update(attr_key => attr_val)
case_log.update_attribute(attr_key, attr_val) unless update
rescue ArgumentError # TODO: determine what to do when a bulk upload contains field values that don't pass validations
end end
rescue ArgumentError
# TODO: determine what we want to do when bulk upload contains totally invalid data for a field.
end end
end end
end end
@ -94,7 +96,7 @@ class BulkUpload
ecstat8: row[42], ecstat8: row[42],
ethnic: row[43], ethnic: row[43],
national: row[44], national: row[44],
armed_forces: row[45], armedforces: row[45],
reservist: row[46], reservist: row[46],
preg_occ: row[47], preg_occ: row[47],
hb: row[48], hb: row[48],

2
app/models/case_log.rb

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

53
app/models/form.rb

@ -60,37 +60,30 @@ class Form
}.first }.first
end end
def next_page(previous_page) def page_for_question(question)
if all_pages[previous_page].key?("default_next_page") all_pages.find { |_page_key, page_value| page_value["questions"].key?(question) }.first
next_page = all_pages[previous_page]["default_next_page"] end
return :check_answers if next_page == "check_answers"
return next_page def next_page(page, case_log)
end 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)
subsection = subsection_for_page(previous_page) next_page(nxt_page, case_log)
previous_page_idx = pages_for_subsection(subsection).keys.index(previous_page)
pages_for_subsection(subsection).keys[previous_page_idx + 1] || :check_answers
end end
def next_page_redirect_path(previous_page) def next_page_redirect_path(page, case_log)
next_page = next_page(previous_page) nxt_page = next_page(page, case_log)
if next_page == :check_answers if nxt_page == :check_answers
subsection = subsection_for_page(previous_page) subsection = subsection_for_page(page)
"case_log_#{subsection}_check_answers_path" "case_log_#{subsection}_check_answers_path"
else else
"case_log_#{next_page}_path" "case_log_#{nxt_page}_path"
end end
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 def all_questions
@all_questions ||= all_pages.map { |_page_key, page_value| @all_questions ||= all_pages.map { |_page_key, page_value|
page_value["questions"] page_value["questions"]
@ -101,6 +94,10 @@ class Form
applicable_questions = questions applicable_questions = questions
questions.each do |k, question| 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| question.fetch("conditional_for", []).each do |conditional_question_key, condition|
if condition_not_met(case_log, k, question, condition) if condition_not_met(case_log, k, question, condition)
applicable_questions = applicable_questions.reject { |z| z == conditional_question_key } applicable_questions = applicable_questions.reject { |z| z == conditional_question_key }
@ -110,6 +107,18 @@ class Form
applicable_questions applicable_questions
end 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) def condition_not_met(case_log, question_key, question, condition)
case question["type"] case question["type"]
when "numeric" 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 end
def validate_armed_forces_injured(record) 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" record.errors.add :reservist, "You must answer the armed forces injury question if the tenant has served in the armed forces"
end 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" 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
end end
def validate_armed_forces_active_response(record) 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" 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 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" 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
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) 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" record.errors.add :preg_occ, "You must answer no as there are no female tenants aged 16-50 in the property"
end end

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

@ -1,6 +1,6 @@
<%= turbo_frame_tag "case_log_form", target: "_top" do %> <%= turbo_frame_tag "case_log_form", target: "_top" do %>
<div class="govuk-grid-row"> <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 <h1 class="govuk-heading-xl">Tasklist for log
<%= @case_log.id %></h1> <%= @case_log.id %></h1>

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

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

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

@ -1,13 +1,11 @@
<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">
<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%>
<%= question_info["check_answer_label"].to_s.present? ? question_info["check_answer_label"].to_s : question_info["header"].to_s%> <dt>
<dt> <dd class="govuk-summary-list__value">
<dd class="govuk-summary-list__value"> <%= form.get_answer_label(@case_log, question_title) %>
<%= form.get_answer_label(@case_log, question_title) %> </dd>
</dd> <dd class="govuk-summary-list__actions">
<dd class="govuk-summary-list__actions"> <%= create_update_answer_link(question_title, question_info, @case_log, form) %>
<%= create_update_answer_link(@case_log[question_title], @case_log.id, page)%> </dd>
</dd> </div>
</div>
</dl>

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

@ -1,15 +1,13 @@
<%= turbo_frame_tag "case_log_form", target: "_top" do %> <%= turbo_frame_tag "case_log_form", target: "_top" do %>
<div class="govuk-grid-row"> <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> <h1 class="govuk-heading-l">Check the answers you gave for <%= subsection.humanize(capitalize: false) %></h1>
<%= display_answered_questions_summary(subsection, @case_log, form) %> <%= display_answered_questions_summary(subsection, @case_log, form) %>
<% form.pages_for_subsection(subsection).each do |page, page_info| %> <dl class="govuk-summary-list govuk-!-margin-bottom-9">
<% page_info["questions"].each do |question_title, question_info| %> <% total_questions(subsection, @case_log, form).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, form: form } %>
<%= 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>
<%end %>
<% end %>
<%= form_with model: @case_log, method: "get", builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %> <%= form_with model: @case_log, method: "get", builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %>
<%= f.govuk_submit "Save and continue" %> <%= f.govuk_submit "Save and continue" %>
<% end %> <% end %>

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

@ -4,7 +4,7 @@
<%= turbo_frame_tag "case_log_form", target: "_top" do %> <%= turbo_frame_tag "case_log_form", target: "_top" do %>
<div class="govuk-grid-row"> <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? %> <% if page_info["header"].present? %>
<h1 class="govuk-heading-xl"> <h1 class="govuk-heading-xl">
<%= page_info["header"] %> <%= 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> <h3 class="govuk-heading-s govuk-!-margin-bottom-1">Email</h3>
<ul class="govuk-list govuk-!-font-size-16"> <ul class="govuk-list govuk-!-font-size-16">
<li> <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>
<li>We aim to respond within 2 working days</li> <li>We aim to respond within 2 working days</li>
</ul> </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> <a href="#main-content" class="govuk-skip-link">Skip to main content</a>
<header class="govuk-header" role="banner" data-module="govuk-header"> <header class="govuk-header" role="banner" data-module="govuk-header">
<div class="govuk-header__container govuk-width-container"> <%= render GovukComponent::HeaderComponent.new(
<div class="govuk-header__logo"> logotype: 'GOV.UK',
<%= link_to "/", class: "govuk-header__link govuk-header__link--homepage" do %> service_name: 'Share Lettings and Sales for Social Housing in England Data with DLUHC',
<span class="govuk-header__logotype"> service_url: '/'
<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>
</header> </header>
<aside class="govuk-width-container"> <aside class="govuk-width-container">

93
config/forms/2021_2022.json

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

24
config/forms/schema/generic.json

@ -27,9 +27,9 @@
"properties": { "properties": {
"label": { "label": {
"description": "", "description": "",
"type": "string" "type": "string"
}, },
"subsections": { "subsections": {
"type": "object", "type": "object",
"patternProperties": { "patternProperties": {
"[a-z_]+": { "[a-z_]+": {
@ -41,10 +41,10 @@
"description": "", "description": "",
"type": "string" "type": "string"
}, },
"pages": { "pages": {
"type": "object", "type": "object",
"patternProperties": { "patternProperties": {
"^(?!(conditional_route_to))[a-z_]+$": { "^(?!(depends_on))[a-z_]+$": {
"description": "Page Name", "description": "Page Name",
"type": "object", "type": "object",
"required": ["header", "questions"], "required": ["header", "questions"],
@ -64,7 +64,7 @@
"description": "Question Name", "description": "Question Name",
"type": "object", "type": "object",
"required": ["header", "type"], "required": ["header", "type"],
"properties": { "properties": {
"header": { "header": {
"description": "", "description": "",
"type": "string" "type": "string"
@ -77,7 +77,7 @@
"description": "", "description": "",
"type": "string", "type": "string",
"optional": "true" "optional": "true"
} }
}, },
"additionalProperties": { "additionalProperties": {
"hint_text": { "hint_text": {
@ -105,25 +105,25 @@
} }
}, },
"additionalProperties": { "additionalProperties": {
"conditional_route_to": { "depends_on": {
"description": "", "description": "",
"type": "object" "type": "object"
} }
}, },
"minProperties": 1 "minProperties": 1
} }
} }
} }
}, },
"minProperties": 1 "minProperties": 1
} }
} }
} }
}, },
"minProperties": 2 "minProperties": 2
} }
}, },
"minProperties": 1 "minProperties": 1
} }
} }
} }

11
db/migrate/20211116102527_change_datetime.rb

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

9
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -25,7 +25,6 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "ethnic" t.integer "ethnic"
t.integer "national" t.integer "national"
t.integer "prevten" t.integer "prevten"
t.string "armed_forces"
t.integer "ecstat1" t.integer "ecstat1"
t.integer "hhmemb" t.integer "hhmemb"
t.string "relat2" t.string "relat2"
@ -60,7 +59,6 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "underoccupation_benefitcap" t.integer "underoccupation_benefitcap"
t.integer "leftreg" t.integer "leftreg"
t.integer "reservist" t.integer "reservist"
t.string "armed_forces_partner"
t.integer "illness" t.integer "illness"
t.integer "preg_occ" t.integer "preg_occ"
t.string "accessibility_requirements" t.string "accessibility_requirements"
@ -121,6 +119,8 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "rp_dontknow" t.integer "rp_dontknow"
t.datetime "discarded_at" t.datetime "discarded_at"
t.string "tenancyother" t.string "tenancyother"
t.integer "override_net_income_validation"
t.string "net_income_known"
t.string "gdpr_acceptance" t.string "gdpr_acceptance"
t.string "gdpr_declined" t.string "gdpr_declined"
t.string "property_owner_organisation" 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 "intermediate_rent_product_name"
t.string "needs_type" t.string "needs_type"
t.string "purchaser_code" t.string "purchaser_code"
t.integer "override_net_income_validation"
t.string "net_income_known"
t.integer "reason" t.integer "reason"
t.string "propcode" t.string "propcode"
t.integer "majorrepairs" t.integer "majorrepairs"
@ -154,6 +152,7 @@ ActiveRecord::Schema.define(version: 2021_11_16_102527) do
t.integer "incref" t.integer "incref"
t.datetime "sale_completion_date" t.datetime "sale_completion_date"
t.datetime "startdate" t.datetime "startdate"
t.integer "armedforces"
t.index ["discarded_at"], name: "index_case_logs_on_discarded_at" t.index ["discarded_at"], name: "index_case_logs_on_discarded_at"
end 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", "ethnic": "White: English/Scottish/Welsh/Northern Irish/British",
"national": "UK national resident in UK", "national": "UK national resident in UK",
"prevten": "Private sector tenancy", "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", "ecstat1": "Full-time - 30 hours or more",
"other_hhmemb": 7, "other_hhmemb": 7,
"relat2": "Partner", "relat2": "Partner",
@ -297,7 +297,6 @@
"underoccupation_benefitcap": "No", "underoccupation_benefitcap": "No",
"leftreg": "No", "leftreg": "No",
"reservist": "No", "reservist": "No",
"armed_forces_partner": "No",
"illness": "Yes", "illness": "Yes",
"preg_occ": "No", "preg_occ": "No",
"accessibility_requirements": "No", "accessibility_requirements": "No",
@ -438,7 +437,7 @@
"type": "string", "type": "string",
"minLength": 1 "minLength": 1
}, },
"armed_forces": { "armedforces": {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1
}, },
@ -787,10 +786,6 @@
"type": "string", "type": "string",
"minLength": 1 "minLength": 1
}, },
"armed_forces_partner": {
"type": "string",
"minLength": 1
},
"illness": { "illness": {
"type": "string", "type": "string",
"minLength": 1 "minLength": 1
@ -1050,7 +1045,7 @@
"ethnic", "ethnic",
"national", "national",
"prevten", "prevten",
"armed_forces", "armedforces",
"ecstat1", "ecstat1",
"other_hhmemb", "other_hhmemb",
"relat2", "relat2",
@ -1086,7 +1081,6 @@
"underoccupation_benefitcap", "underoccupation_benefitcap",
"leftreg", "leftreg",
"reservist", "reservist",
"armed_forces_partner",
"illness", "illness",
"preg_occ", "preg_occ",
"accessibility_requirements", "accessibility_requirements",

27
spec/controllers/case_logs_controller_spec.rb

@ -125,7 +125,7 @@ RSpec.describe CaseLogsController, type: :controller do
context "conditional routing" do context "conditional routing" do
before 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 end
let(:case_log_form_conditional_question_yes_params) do let(:case_log_form_conditional_question_yes_params) do
@ -184,7 +184,7 @@ RSpec.describe CaseLogsController, type: :controller do
"mrcdate(1i)": "2021", "mrcdate(1i)": "2021",
"mrcdate(2i)": "05", "mrcdate(2i)": "05",
"mrcdate(3i)": "04", "mrcdate(3i)": "04",
page: "major_repairs_date", page: "property_major_repairs",
} }
end end
it "saves full and partial dates" do it "saves full and partial dates" do
@ -200,27 +200,4 @@ RSpec.describe CaseLogsController, type: :controller do
end end
end 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 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") expect(page).to have_link("Change", href: "/case_logs/#{empty_case_log.id}/person_1_age")
end 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 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") visit("/case_logs/#{empty_case_log.id}/#{subsection}/check_answers")
expect(page).to have_content("You answered 0 of 4 questions") 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 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") 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 # 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") expect(page).to have_selector("#reservist_div")
choose("case-log-reservist-no-field", allow_label_click: true) choose("case-log-reservist-no-field", allow_label_click: true)
expect(page).to have_checked_field("case-log-reservist-no-field", visible: false) 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") 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) expect(page).to have_unchecked_field("case-log-reservist-no-field", visible: false)
end end
end end
@ -449,7 +462,7 @@ RSpec.describe "Test Features" do
describe "conditional page routing", js: true do describe "conditional page routing", js: true do
before 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 end
it "can route the user to a different page based on their answer on the current page" do 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") expect(page).to have_current_path("/case_logs/#{id}/conditional_question_no_page")
end end
it "can route based on page inclusion rules" do it "can route based on multiple conditions" do
visit("/case_logs/#{id}/conditional_question_yes_page") visit("/case_logs/#{id}/person_1_gender")
choose("case-log-cbl-yes-field", allow_label_click: true) 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") 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}/conditional_question/check_answers")
end end
end
it "can route to the default next page" do describe "date validation", js: true do
visit("/case_logs/#{id}/conditional_question") 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") 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 end
it "can route based on multiple conditions" do it "does not allow non numeric inputs to be submitted" do
visit("/case_logs/#{id}/person_1_gender") fill_in_date(id, "case_log_mrcdate", "abc", "de", "ff", "property_major_repairs")
choose("case-log-sex1-female-field", allow_label_click: true)
click_button("Save and continue") click_button("Save and continue")
visit("/case_logs/#{id}/conditional_question") expect(page).to have_current_path("/case_logs/#{id}/property_major_repairs")
choose("case-log-preg-occ-yes-field", allow_label_click: true) 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") 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 end
end end

3
spec/fixtures/complete_case_log.json vendored

@ -6,7 +6,7 @@
"ethnic": "White: English/Scottish/Welsh/Northern Irish/British", "ethnic": "White: English/Scottish/Welsh/Northern Irish/British",
"national": "UK national resident in UK", "national": "UK national resident in UK",
"prevten": "Private sector tenancy", "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", "ecstat1": "Full-time - 30 hours or more",
"other_hhmemb": 7, "other_hhmemb": 7,
"hhmemb": 8, "hhmemb": 8,
@ -43,7 +43,6 @@
"underoccupation_benefitcap": "No", "underoccupation_benefitcap": "No",
"leftreg": "No - they left up to 5 years ago", "leftreg": "No - they left up to 5 years ago",
"reservist": "No", "reservist": "No",
"armed_forces_partner": "No",
"illness": "Yes", "illness": "Yes",
"preg_occ": "No", "preg_occ": "No",
"accessibility_requirements": "No", "accessibility_requirements": "No",

27
spec/fixtures/forms/test_aboutthislog.json vendored

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

39
spec/fixtures/forms/test_form.json vendored

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

3
spec/fixtures/forms/test_validator.json vendored

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

16
spec/helpers/check_answers_helper_spec.rb

@ -11,7 +11,7 @@ RSpec.describe CheckAnswersHelper do
) )
end end
let(:case_log_with_met_radio_condition) do 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", reservist: "No",
leftreg: "Yes") leftreg: "Yes")
end end
@ -47,7 +47,7 @@ RSpec.describe CheckAnswersHelper do
end end
it "ignores questions with unmet radio conditions" do 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) expect(total_answered_questions(subsection_with_radio_conditionals, case_log, form)).to equal(1)
end end
@ -119,6 +119,7 @@ RSpec.describe CheckAnswersHelper do
it "counts correct questions when the conditional question is answered" do it "counts correct questions when the conditional question is answered" do
case_log["preg_occ"] = "No" case_log["preg_occ"] = "No"
case_log["sex1"] = "Male"
expect(total_number_of_questions(conditional_routing_subsection, case_log, form)).to eq(3) expect(total_number_of_questions(conditional_routing_subsection, case_log, form)).to eq(3)
end end
end end
@ -126,7 +127,6 @@ RSpec.describe CheckAnswersHelper do
context "total questions" do context "total questions" do
it "returns total questions" do it "returns total questions" do
result = total_questions(subsection, case_log, form) result = total_questions(subsection, case_log, form)
expect(result.class).to eq(Hash)
expected_keys = %w[earnings incfreq benefits hb] expected_keys = %w[earnings incfreq benefits hb]
expect(result.keys).to eq(expected_keys) expect(result.keys).to eq(expected_keys)
end end
@ -134,14 +134,14 @@ RSpec.describe CheckAnswersHelper do
context "conditional questions on the same page" do context "conditional questions on the same page" do
it "it filters out conditional questions that were not displayed" do it "it filters out conditional questions that were not displayed" do
result = total_questions(conditional_page_subsection, case_log, form) 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) expect(result.keys).to eq(expected_keys)
end end
it "it includes conditional questions that were displayed" do 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) 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) expect(result.keys).to eq(expected_keys)
end end
end end
@ -157,14 +157,14 @@ RSpec.describe CheckAnswersHelper do
case_log["preg_occ"] = "Yes" case_log["preg_occ"] = "Yes"
case_log["sex1"] = "Female" case_log["sex1"] = "Female"
result = total_questions(conditional_routing_subsection, case_log, form) 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) expect(result.keys).to match_array(expected_keys)
end end
it "it includes conditional pages and questions that were displayed" do it "it includes conditional pages and questions that were displayed" do
case_log["preg_occ"] = "No" case_log["preg_occ"] = "No"
result = total_questions(conditional_routing_subsection, case_log, form) 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) expect(result.keys).to match_array(expected_keys)
end end
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 context "armed forces injured validation" do
it "must be answered if tenant was a regular or reserve in armed forces" do it "must be answered if tenant was a regular or reserve in armed forces" do
expect { 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) reservist: nil)
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
end end
it "must be answered if tenant was not a regular or reserve in armed forces" do it "must be answered if tenant was not a regular or reserve in armed forces" do
expect { expect {
CaseLog.create!(armed_forces: "No", CaseLog.create!(armedforces: "No",
reservist: "Yes") reservist: "Yes")
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
end end
@ -223,14 +223,14 @@ RSpec.describe Form, type: :model do
context "armed forces active validation" do context "armed forces active validation" do
it "must be answered if ever served in the forces as a regular" do it "must be answered if ever served in the forces as a regular" do
expect { 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) leftreg: nil)
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
end end
it "must not be answered if not ever served as a regular" do it "must not be answered if not ever served as a regular" do
expect { expect {
CaseLog.create!(armed_forces: "No", CaseLog.create!(armedforces: "No",
leftreg: "Yes") leftreg: "Yes")
}.to raise_error(ActiveRecord::RecordInvalid) }.to raise_error(ActiveRecord::RecordInvalid)
end 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 # 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 it "must be answered if ever served in the forces as a regular" do
expect 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", leftreg: "Yes",
reservist: "Yes") reservist: "Yes")
end end

38
spec/models/form_spec.rb

@ -3,11 +3,12 @@ require "rails_helper"
RSpec.describe Form, type: :model do RSpec.describe Form, type: :model do
form_handler = FormHandler.instance form_handler = FormHandler.instance
let(:form) { form_handler.get_form("test_form") } let(:form) { form_handler.get_form("test_form") }
let(:case_log) { FactoryBot.build(:case_log, :in_progress) }
describe ".next_page" do describe ".next_page" do
let(:previous_page) { "person_1_age" } let(:previous_page) { "person_1_age" }
it "returns the next page given the previous" do 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
end end
@ -18,22 +19,6 @@ RSpec.describe Form, type: :model do
end end
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 describe ".questions_for_subsection" do
let(:subsection) { "income_and_benefits" } let(:subsection) { "income_and_benefits" }
it "returns all questions for subsection" do 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]) expect(result.keys).to eq(%w[earnings incfreq benefits hb])
end end
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 end

Loading…
Cancel
Save