Browse Source

Merge remote-tracking branch 'origin/main' into cldc-1228-fixes-3

pull/745/head
Stéphane Meny 3 years ago
parent
commit
4bb4703964
No known key found for this signature in database
GPG Key ID: 9D0AFEA988527923
  1. 37
      app/controllers/form_controller.rb
  2. 2
      app/controllers/locations_controller.rb
  3. 2
      app/frontend/controllers/accessible_autocomplete_controller.js
  4. 28
      app/frontend/modules/search.js
  5. 1
      app/models/case_log.rb
  6. 34
      app/models/form/question.rb
  7. 2
      app/models/form/setup/questions/location_id.rb
  8. 6
      app/models/form/setup/questions/scheme_id.rb
  9. 12
      app/models/scheme.rb
  10. 10
      app/models/validations/submission_validations.rb
  11. 25
      app/views/form/_select_question.html.erb
  12. 15
      app/views/form/page.html.erb
  13. 22
      app/views/locations/edit.html.erb
  14. 22
      app/views/locations/new.html.erb
  15. 66
      config/forms/2021_2022.json
  16. 70
      config/forms/2022_2023.json
  17. 17
      config/locales/en.yml
  18. 7
      db/migrate/20220714080044_add_location_startdate.rb
  19. 3
      db/schema.rb
  20. 27
      spec/features/form/accessible_autocomplete_spec.rb
  21. 2
      spec/features/form/check_answers_page_spec.rb
  22. 76
      spec/features/form/form_navigation_spec.rb
  23. 1
      spec/features/form/page_routing_spec.rb
  24. 4
      spec/features/form/progressive_total_field_spec.rb
  25. 1
      spec/features/form/saving_data_spec.rb
  26. 47
      spec/features/schemes_spec.rb
  27. 71
      spec/models/form/setup/questions/location_id_spec.rb
  28. 18
      spec/models/form/setup/questions/scheme_id_spec.rb
  29. 4
      spec/models/scheme_spec.rb
  30. 1
      spec/rails_helper.rb
  31. 2
      spec/requests/case_logs_controller_spec.rb
  32. 10
      spec/requests/locations_controller_spec.rb

37
app/controllers/form_controller.rb

@ -7,11 +7,16 @@ class FormController < ApplicationController
if @case_log
@page = @case_log.form.get_page(params[:case_log][:page])
responses_for_page = responses_for_page(@page)
if @case_log.update(responses_for_page)
session[:errors] = nil
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
if mandatory_questions_with_no_response.empty? && @case_log.update(responses_for_page)
session[:errors] = session[:fields] = nil
redirect_to(successful_redirect_path)
else
redirect_path = "case_log_#{@page.id}_path"
mandatory_questions_with_no_response.map do |question|
@case_log.errors.add question.id.to_sym, question.unanswered_error_message
end
session[:errors] = @case_log.errors.to_json
Rails.logger.info "User triggered validation(s) on: #{@case_log.errors.map(&:attribute).join(', ')}"
redirect_to(send(redirect_path, @case_log))
@ -48,6 +53,7 @@ class FormController < ApplicationController
messages.each { |message| @case_log.errors.add field.to_sym, message }
end
end
session["fields"].each { |field, value| @case_log[field] = value } if session["fields"]
@subsection = @case_log.form.subsection_for_page(page)
@page = @case_log.form.get_page(page.id)
if @page.routed_to?(@case_log, current_user)
@ -120,4 +126,31 @@ private
redirect_path = @case_log.form.next_page_redirect_path(@page, @case_log, current_user)
send(redirect_path, @case_log)
end
def mandatory_questions_with_no_response(responses_for_page)
session["fields"] = {}
calc_questions = @page.questions.map(&:result_field)
@page.questions.select do |question|
next if calc_questions.include?(question.id)
question_is_required?(question) && question_missing_response?(responses_for_page, question)
end
end
def question_is_required?(question)
CaseLog::OPTIONAL_FIELDS.exclude?(question.id) &&
@page.subsection.applicable_questions(@case_log).map(&:id).include?(question.id)
end
def question_missing_response?(responses_for_page, question)
if %w[checkbox validation_override].include?(question.type)
question.answer_options.keys.reject { |x| x.match(/divider/) }.all? do |option|
session["fields"][option] = @case_log[option] = params["case_log"][question.id].include?(option) ? 1 : 0
params["case_log"][question.id].exclude?(option)
end
else
session["fields"][question.id] = @case_log[question.id] = responses_for_page[question.id]
responses_for_page[question.id].nil? || responses_for_page[question.id].blank?
end
end
end

2
app/controllers/locations_controller.rb

@ -69,7 +69,7 @@ private
end
def location_params
required_params = params.require(:location).permit(:postcode, :name, :units, :type_of_unit, :wheelchair_adaptation, :add_another_location).merge(scheme_id: @scheme.id)
required_params = params.require(:location).permit(:postcode, :name, :units, :type_of_unit, :wheelchair_adaptation, :add_another_location, :startdate).merge(scheme_id: @scheme.id)
required_params[:postcode] = PostcodeService.clean(required_params[:postcode]) if required_params[:postcode]
required_params
end

2
app/frontend/controllers/accessible_autocomplete_controller.js

@ -15,7 +15,7 @@ export default class extends Controller {
accessibleAutocomplete.enhanceSelectElement({
defaultValue: '',
selectElement: selectEl,
minLength: 2,
minLength: 1,
source: (query, populateResults) => {
if (/\S/.test(query)) {
populateResults(sort(query, options))

28
app/frontend/modules/search.js

@ -12,9 +12,13 @@ const clean = (text) =>
.toLowerCase()
const cleanseOption = (option) => {
const synonyms = (option.synonyms || []).map(clean)
option.clean = {
name: clean(option.name),
nameWithoutStopWords: removeStopWords(option.name),
synonyms,
synonymsWithoutStopWords: synonyms.map(removeStopWords),
boost: option.boost || 1
}
@ -41,19 +45,34 @@ const startsWith = (word, query) => word.search(startsWithRegExp(query)) === 0
const wordsStartsWithQuery = (word, regExps) =>
regExps.every((regExp) => word.search(regExp) >= 0)
const calculateWeight = ({ name, nameWithoutStopWords }, query) => {
const anyMatch = (words, query, evaluatorFunc) => words.some((word) => evaluatorFunc(word, query))
const synonymsExactMatch = (synonyms, query) => anyMatch(synonyms, query, exactMatch)
const synonymsStartsWith = (synonyms, query) => anyMatch(synonyms, query, startsWith)
const wordInSynonymStartsWithQuery = (synonyms, startsWithQueryWordsRegexes) =>
anyMatch(synonyms, startsWithQueryWordsRegexes, wordsStartsWithQuery)
const calculateWeight = ({ name, synonyms, nameWithoutStopWords, synonymsWithoutStopWords }, query) => {
const queryWithoutStopWords = removeStopWords(query)
if (exactMatch(name, query)) return 100
if (exactMatch(nameWithoutStopWords, queryWithoutStopWords)) return 95
if (synonymsExactMatch(synonyms, query)) return 75
if (synonymsExactMatch(synonymsWithoutStopWords, queryWithoutStopWords)) return 70
if (startsWith(name, query)) return 60
if (startsWith(nameWithoutStopWords, queryWithoutStopWords)) return 55
if (synonymsStartsWith(synonyms, query)) return 50
if (synonymsStartsWith(synonyms, queryWithoutStopWords)) return 40
const startsWithRegExps = queryWithoutStopWords
.split(/\s+/)
.map(startsWithRegExp)
if (wordsStartsWithQuery(nameWithoutStopWords, startsWithRegExps)) return 25
if (wordInSynonymStartsWithQuery(synonymsWithoutStopWords, startsWithRegExps)) return 10
return 0
}
@ -91,8 +110,8 @@ export const sort = (query, options) => {
export const suggestion = (value, options) => {
const option = options.find((o) => o.name === value)
if (option) {
const html = `<span>${value}</span>`
return html
const html = option.append ? `<span>${value}</span> <span class="autocomplete__option__append">${option.append}</span>` : `<span>${value}</span>`
return option.hint ? `${html}<div class="autocomplete__option__hint">${option.hint}</div>` : html
} else {
return '<span>No results found</span>'
}
@ -101,6 +120,9 @@ export const suggestion = (value, options) => {
export const enhanceOption = (option) => {
return {
name: option.label,
synonyms: (option.getAttribute('data-synonyms') ? option.getAttribute('data-synonyms').split('|') : []),
append: option.getAttribute('data-append'),
hint: option.getAttribute('data-hint'),
boost: parseFloat(option.getAttribute('data-boost')) || 1
}
}

1
app/models/case_log.rb

@ -8,7 +8,6 @@ class CaseLogValidator < ActiveModel::Validator
include Validations::TenancyValidations
include Validations::DateValidations
include Validations::LocalAuthorityValidations
include Validations::SubmissionValidations
def validate(record)
validation_methods = public_methods.select { |method| method.starts_with?("validate_") }

34
app/models/form/question.rb

@ -136,7 +136,11 @@ class Form::Question
labels = answer_options[value.to_s]
labels["value"] if labels
when "select"
answer_options[value.to_s]
if answer_options[value.to_s].respond_to?(:service_name)
answer_options[value.to_s].service_name
else
answer_options[value.to_s]
end
else
value.to_s
end
@ -173,6 +177,16 @@ class Form::Question
type == "radio" && RADIO_REFUSED_VALUE[id.to_sym]&.include?(value)
end
def display_label
check_answer_label || header || id.humanize
end
def unanswered_error_message
return I18n.t("validations.declaration.missing") if id == "declaration"
I18n.t("validations.not_answered", question: display_label.downcase)
end
def suffix_label(case_log)
return "" unless suffix
return suffix if suffix.is_a?(String)
@ -191,6 +205,24 @@ class Form::Question
label
end
def answer_option_synonyms(resource)
return unless resource.respond_to?(:synonyms)
resource.synonyms
end
def answer_option_append(resource)
return unless resource.respond_to?(:appended_text)
resource.appended_text
end
def answer_option_hint(resource)
return unless resource.respond_to?(:hint)
resource.hint
end
private
def selected_answer_option_is_derived?(case_log)

2
app/models/form/setup/questions/location_id.rb

@ -13,7 +13,7 @@ class Form::Setup::Questions::LocationId < ::Form::Question
answer_opts = {}
return answer_opts unless ActiveRecord::Base.connected?
Location.select(:id, :postcode, :name).each_with_object(answer_opts) do |location, hsh|
Location.select(:id, :postcode, :name).where("startdate <= ? or startdate IS NULL", Time.zone.today).each_with_object(answer_opts) do |location, hsh|
hsh[location.id.to_s] = { "value" => location.postcode, "hint" => location.name }
hsh
end

6
app/models/form/setup/questions/scheme_id.rb

@ -13,8 +13,8 @@ class Form::Setup::Questions::SchemeId < ::Form::Question
answer_opts = {}
return answer_opts unless ActiveRecord::Base.connected?
Scheme.select(:id, :service_name).each_with_object(answer_opts) do |scheme, hsh|
hsh[scheme.id.to_s] = scheme.service_name
Scheme.select(:id, :service_name, :primary_client_group, :secondary_client_group).each_with_object(answer_opts) do |scheme, hsh|
hsh[scheme.id.to_s] = scheme
hsh
end
end
@ -22,7 +22,7 @@ class Form::Setup::Questions::SchemeId < ::Form::Question
def displayed_answer_options(case_log)
return {} unless case_log.created_by
user_org_scheme_ids = Scheme.select(:id).where(owning_organisation_id: case_log.created_by.organisation_id).map(&:id)
user_org_scheme_ids = Scheme.select(:id).where(owning_organisation_id: case_log.created_by.organisation_id).joins(:locations).merge(Location.where("startdate <= ? or startdate IS NULL", Time.zone.today)).map(&:id)
answer_options.select do |k, _v|
user_org_scheme_ids.include?(k.to_i)
end

12
app/models/scheme.rb

@ -142,4 +142,16 @@ class Scheme < ApplicationRecord
{ name: "Intended length of stay", value: intended_stay },
]
end
def synonyms
locations.map(&:postcode).join(",")
end
def appended_text
"(#{locations.count { |location| location.startdate.blank? || location.startdate <= Time.zone.today }} locations)"
end
def hint
[primary_client_group, secondary_client_group].filter(&:present?).join(", ")
end
end

10
app/models/validations/submission_validations.rb

@ -1,10 +0,0 @@
module Validations::SubmissionValidations
# Validations methods need to be called 'validate_<page_name>' to run on model save
# or 'validate_' to run on submit as well
def validate_declaration(record)
if record.declaration&.zero?
record.errors.add :declaration, I18n.t("validations.declaration.missing")
end
end
end

25
app/views/form/_select_question.html.erb

@ -1,13 +1,18 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<% selected = @case_log.public_send(question.id) || "" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value) } %>
<%= f.govuk_collection_select question.id.to_sym,
answers,
:id,
:name,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
options: { disabled: [""], selected: },
"data-controller": "accessible-autocomplete" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value.respond_to?(:service_name) ? value.service_name : nil, resource: value) } %>
<%= f.govuk_select(question.id.to_sym,
label: legend(question, page_header, conditional),
"data-controller": "accessible-autocomplete",
caption: caption(caption_text, page_header, conditional),
hint: { text: question.hint_text&.html_safe }) do %>
<% answers.each do |answer| %>
<option value="<%= answer.id %>"
data-synonyms="<%= question.answer_option_synonyms(answer.resource) %>"
data-append="<%= question.answer_option_append(answer.resource) %>"
data-hint="<%= question.answer_option_hint(answer.resource) %>"
<%= @case_log[question.id] == answer.name || @case_log[question.id] == answer.resource || @case_log[question.id].to_s == answer.id ? "selected" : "" %>
<%= answer.id == "" ? "disabled" : "" %>><%= answer.name || answer.resource %></option>
<% end %>
<% end %>

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

@ -5,7 +5,6 @@
<% end %>
<div data-controller="govukfrontend"></div>
<%= form_with model: @case_log, url: form_case_log_path(@case_log), method: "post", local: true do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>">
@ -39,9 +38,17 @@
<% end %>
<%= f.hidden_field :page, value: @page.id %>
<% if !@page.id.include?("value_check") %>
<%= f.govuk_submit "Save and continue" %>
<% end %>
<div class="govuk-button-group">
<% if !@page.id.include?("value_check") && if request.query_parameters["referrer"] != "check_answers" %>
<%= f.govuk_submit "Save and continue" %>
<%= govuk_link_to "Skip for now", send(@case_log.form.next_page_redirect_path(@page, @case_log, current_user), @case_log) %>
<% else %>
<%= f.govuk_submit "Save changes" %>
<%= govuk_link_to "Cancel", "/logs/#{@case_log.id}/setup/check-answers" %>
<% end %>
<% end %>
</div>
</div>
</div>
<% end %>

22
app/views/locations/edit.html.erb

@ -16,17 +16,17 @@
<%= f.govuk_text_field :postcode,
label: { size: "m" },
hint: { text: "For example, SW1P 4DF." },
hint: { text: I18n.t("hints.location.postcode") },
width: 5 %>
<%= f.govuk_text_field :name,
label: { text: "Location name (optional)", size: "m" },
hint: { text: "This is how you refer to this location within your organisation" } %>
label: { text: I18n.t("questions.location.name"), size: "m" },
hint: { text: I18n.t("hints.location.name") } %>
<%= f.govuk_number_field :units,
label: { text: "Total number of units at this location", size: "m" },
label: { text: I18n.t("questions.location.units"), size: "m" },
width: 2,
hint: { text: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff.s" },
hint: { text: I18n.t("hints.location.units") },
autofocus: true %>
<% type_of_units_selection = Location.type_of_units.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
@ -34,15 +34,19 @@
type_of_units_selection,
:id,
:name,
legend: { text: "What is this type of scheme?", size: "m" } %>
legend: { text: I18n.t("questions.location.type_of_unit"), size: "m" } %>
<% wheelchair_user_selection = Location.wheelchair_adaptations.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :wheelchair_adaptation,
wheelchair_user_selection,
:id,
:name,
hint: { text: "This includes stairlifts, ramps, level-access showers or grab rails" },
legend: { text: "Are the majority of units in this location built or adapted to wheelchair-user standards?", size: "m" } %>
hint: { text: I18n.t("hints.location.wheelchair_adaptation") },
legend: { text: I18n.t("questions.location.wheelchair_adaptation"), size: "m" } %>
<%= f.govuk_date_field :startdate,
legend: { text: I18n.t("questions.location.startdate"), size: "m" },
width: 20 %>
<%= govuk_section_break(visible: true, size: "m") %>
@ -52,7 +56,7 @@
:id,
:name,
inline: true,
legend: { text: "Do you want to add another location?", size: "m" } %>
legend: { text: I18n.t("questions.location.add_another_location"), size: "m" } %>
<%= f.hidden_field :page, value: "edit" %>

22
app/views/locations/new.html.erb

@ -16,17 +16,17 @@
<%= f.govuk_text_field :postcode,
label: { size: "m" },
hint: { text: "For example, SW1P 4DF." },
hint: { text: I18n.t("hints.location.postcode") },
width: 5 %>
<%= f.govuk_text_field :name,
label: { text: "Location name (optional)", size: "m" },
hint: { text: "This is how you refer to this location within your organisation" } %>
label: { text: I18n.t("questions.location.name"), size: "m" },
hint: { text: I18n.t("hints.location.name") } %>
<%= f.govuk_number_field :units,
label: { text: "Total number of units at this location", size: "m" },
label: { text: I18n.t("questions.location.units"), size: "m" },
width: 2,
hint: { text: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff.s" },
hint: { text: I18n.t("hints.location.units") },
autofocus: true %>
<% type_of_units_selection = Location.type_of_units.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
@ -35,7 +35,7 @@
type_of_units_selection,
:id,
:name,
legend: { text: "What is this type of scheme?", size: "m" } %>
legend: { text: I18n.t("questions.location.type_of_unit"), size: "m" } %>
<% wheelchair_user_selection = Location.wheelchair_adaptations.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
@ -43,8 +43,12 @@
wheelchair_user_selection,
:id,
:name,
hint: { text: "This includes stairlifts, ramps, level-access showers or grab rails" },
legend: { text: "Are the majority of units in this location built or adapted to wheelchair-user standards?", size: "m" } %>
hint: { text: I18n.t("hints.location.wheelchair_adaptation") },
legend: { text: I18n.t("questions.location.wheelchair_adaptation"), size: "m" } %>
<%= f.govuk_date_field :startdate,
legend: { text: I18n.t("questions.location.startdate"), size: "m" },
width: 20 %>
<%= govuk_section_break(visible: true, size: "m") %>
@ -55,7 +59,7 @@
:id,
:name,
inline: true,
legend: { text: "Do you want to add another location?", size: "m" } %>
legend: { text: I18n.t("questions.location.add_another_location"), size: "m" } %>
<%= f.govuk_submit "Save and continue" %>
</div>

66
config/forms/2021_2022.json

@ -1078,7 +1078,8 @@
"step": 1,
"width": 2
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1120,11 +1121,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1207,7 +1204,8 @@
"value": "Not known"
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_age_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1249,11 +1247,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_age_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1318,7 +1312,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1360,11 +1355,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1435,7 +1426,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_ethnic_background_arab": {
"header": "",
@ -1456,11 +1448,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 4
}
]
"depends_on": [{ "ethnic_group": 4 }]
},
"lead_tenant_ethnic_background_asian": {
"header": "",
@ -1490,11 +1478,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 2
}
]
"depends_on": [{ "ethnic_group": 2 }]
},
"lead_tenant_ethnic_background_black": {
"header": "",
@ -1518,11 +1502,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 3
}
]
"depends_on": [{ "ethnic_group": 3 }]
},
"lead_tenant_ethnic_background_mixed": {
"header": "",
@ -1549,11 +1529,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 1
}
]
"depends_on": [{ "ethnic_group": 1 }]
},
"lead_tenant_ethnic_background_white": {
"header": "",
@ -1580,11 +1556,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 0
}
]
"depends_on": [{ "ethnic_group": 0 }]
},
"lead_tenant_nationality": {
"header": "",
@ -1650,7 +1622,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_working_situation": {
"header": "",
@ -1697,7 +1670,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_under_retirement_value_check": {
"depends_on": [{ "person_1_retired_under_soft_min_age?": true }],

70
config/forms/2022_2023.json

@ -1113,7 +1113,8 @@
"step": 1,
"width": 2
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1155,11 +1156,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1242,7 +1239,8 @@
"value": "Not known"
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_age_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1284,11 +1282,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_age_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1353,7 +1347,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1395,11 +1390,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1470,7 +1461,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_ethnic_background_arab": {
"header": "",
@ -1491,11 +1483,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 4
}
]
"depends_on": [{ "ethnic_group": 4 }]
},
"lead_tenant_ethnic_background_asian": {
"header": "",
@ -1525,11 +1513,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 2
}
]
"depends_on": [{ "ethnic_group": 2 }]
},
"lead_tenant_ethnic_background_black": {
"header": "",
@ -1553,11 +1537,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 3
}
]
"depends_on": [{ "ethnic_group": 3 }]
},
"lead_tenant_ethnic_background_mixed": {
"header": "",
@ -1584,11 +1564,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 1
}
]
"depends_on": [{ "ethnic_group": 1 }]
},
"lead_tenant_ethnic_background_white": {
"header": "",
@ -1615,11 +1591,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 0
}
]
"depends_on": [{ "ethnic_group": 0 }]
},
"lead_tenant_nationality": {
"header": "",
@ -1649,7 +1621,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_working_situation": {
"header": "",
@ -1696,7 +1669,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_under_retirement_value_check": {
"depends_on": [{ "person_1_retired_under_soft_min_age?": true }],
@ -1743,9 +1717,7 @@
}
},
"lead_tenant_over_retirement_value_check": {
"depends_on": [
{ "person_1_not_retired_over_soft_max_age?": true }
],
"depends_on": [{ "person_1_not_retired_over_soft_max_age?": true }],
"title_text": {
"translation": "soft_validations.retirement.max.title",
"arguments": [

17
config/locales/en.yml

@ -51,6 +51,7 @@ en:
organisation:
name_missing: "Enter the organisation’s name"
provider_type_missing: "Select the organisation’s type"
not_answered: "You must answer %{question}"
other_field_missing: "If %{main_field_label} is other then %{other_field_label} must be provided"
other_field_not_required: "%{other_field_label} must not be provided if %{main_field_label} was not other"
@ -287,6 +288,22 @@ en:
code_required: "Security code is required"
code_incorrect: "Security code is incorrect"
questions:
location:
name: "Location name (optional)"
units: "Total number of units at this location"
type_of_unit: "What is this type of scheme?"
wheelchair_adaptation: "Are the majority of units in this location built or adapted to wheelchair-user standards?"
startdate: "When did the first property in this location become available under this scheme?"
add_another_location: "Do you want to add another location?"
hints:
location:
postcode: "For example, SW1P 4DF."
name: "This is how you refer to this location within your organisation"
units: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff."
wheelchair_adaptation: "This includes stairlifts, ramps, level-access showers or grab rails"
test:
one_argument: "This is based on the tenant’s work situation: %{ecstat1}"
title_text:

7
db/migrate/20220714080044_add_location_startdate.rb

@ -0,0 +1,7 @@
class AddLocationStartdate < ActiveRecord::Migration[7.0]
def change
change_table :locations, bulk: true do |t|
t.column :startdate, :datetime
end
end
end

3
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_07_13_095713) do
ActiveRecord::Schema[7.0].define(version: 2022_07_14_080044) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -251,6 +251,7 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_13_095713) do
t.string "old_id"
t.integer "old_visible_id"
t.string "mobility_type"
t.datetime "startdate", precision: nil
t.index ["old_id"], name: "index_locations_on_old_id", unique: true
t.index ["scheme_id"], name: "index_locations_on_scheme_id"
end

27
spec/features/form/accessible_autocomplete_spec.rb

@ -14,6 +14,7 @@ RSpec.describe "Accessible Automcomplete" do
is_la_inferred: false,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
@ -58,6 +59,32 @@ RSpec.describe "Accessible Automcomplete" do
end
end
context "when searching schemes" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation_id: case_log.created_by.organisation_id, primary_client_group: "Q", secondary_client_group: "P") }
before do
FactoryBot.create(:location, scheme:, postcode: "W6 0ST")
FactoryBot.create(:location, scheme:, postcode: "SE6 1LB")
case_log.update!(needstype: 2)
visit("/logs/#{case_log.id}/scheme")
end
it "can match on synonyms", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find("#case-log-scheme-id-field").value).to eq(scheme.service_name)
end
it "displays appended text next to the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option__append", visible: :hidden, text: /(2 locations)/)).to be_present
end
it "displays hint text under the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option__hint", visible: :hidden, text: /Young people at risk, Young people leaving care/)).to be_present
end
end
it "has the correct option selected if one has been saved" do
case_log.update!(postcode_known: 0, previous_la_known: 1, prevloc: "E07000178")
visit("/logs/#{case_log.id}/accessible-select")

2
spec/features/form/check_answers_page_spec.rb

@ -136,7 +136,7 @@ RSpec.describe "Form Check Answers Page" do
first("a", text: /Change/).click
uncheck("case-log-accessibility-requirements-housingneeds-c-field")
check("case-log-accessibility-requirements-housingneeds-b-field")
click_button("Save and continue")
click_button("Save changes")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/household-needs/check-answers")
end
end

76
spec/features/form/form_navigation_spec.rb

@ -13,6 +13,15 @@ RSpec.describe "Form Navigation" do
created_by: user,
)
end
let(:empty_case_log) do
FactoryBot.create(
:case_log,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
let(:id) { case_log.id }
let(:question_answers) do
{
@ -47,11 +56,23 @@ RSpec.describe "Form Navigation" do
pages = question_answers.map { |_key, val| val[:path] }
pages[0..-2].each_with_index do |val, index|
visit("/logs/#{id}/#{val}")
click_button("Save and continue")
click_link("Skip for now")
expect(page).to have_current_path("/logs/#{id}/#{pages[index + 1]}")
end
end
it "a question page has a Skip for now link that lets you move on to the next question without inputting anything" do
visit("logs/#{empty_case_log.id}/tenant-code-test")
click_link(text: "Skip for now")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/person-1-age")
end
it "routes to check answers when skipping on the last page in the form" do
visit("logs/#{empty_case_log.id}/propcode")
click_link(text: "Skip for now")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/household-characteristics/check-answers")
end
describe "Back link directs correctly", js: true do
it "go back to tasklist page from tenant code" do
visit("/logs/#{id}")
@ -89,4 +110,57 @@ RSpec.describe "Form Navigation" do
end
end
end
describe "Editing a log" do
it "a question page has a link allowing you to cancel your input and return to the check answers page" do
visit("logs/#{id}/tenant-code-test?referrer=check_answers")
click_link(text: "Cancel")
expect(page).to have_current_path("/logs/#{id}/setup/check-answers")
end
context "when clicking save and continue on a mandatory question with no input" do
let(:id) { empty_case_log.id }
it "shows a validation error on radio questions" do
visit("/logs/#{id}/renewal")
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-renewal-error")
expect(page).to have_title("Error")
end
it "shows a validation error on date questions" do
visit("/logs/#{id}/tenancy-start-date")
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-startdate-error")
expect(page).to have_title("Error")
end
context "when the page has a main and conditional question" do
context "when the conditional question is required but not answered" do
it "shows a validation error for the conditional question" do
visit("/logs/#{id}/armed-forces")
choose("case-log-armedforces-1-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-leftreg-error")
expect(page).to have_title("Error")
end
end
end
end
context "when clicking save and continue on an optional question with no input" do
let(:id) { empty_case_log.id }
it "does not show a validation error" do
visit("/logs/#{id}/tenant-code")
click_button("Save and continue")
expect(page).not_to have_selector("#error-summary-title")
expect(page).not_to have_title("Error")
expect(page).to have_current_path("/logs/#{id}/property-reference")
end
end
end
end

1
spec/features/form/page_routing_spec.rb

@ -43,6 +43,7 @@ RSpec.describe "Form Page Routing" do
choose("case-log-preg-occ-2-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question-no-page")
choose("case-log-cbl-0-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question/check-answers")
end

4
spec/features/form/progressive_total_field_spec.rb

@ -33,6 +33,7 @@ RSpec.describe "Accessible Automcomplete" do
it "total displays despite error message", js: true do
visit("/logs/#{case_log.id}/rent")
choose("case-log-period-1-field", allow_label_click: true)
fill_in("case-log-brent-field", with: 500)
fill_in("case-log-scharge-field", with: 50)
fill_in("case-log-pscharge-field", with: 50)
@ -40,6 +41,9 @@ RSpec.describe "Accessible Automcomplete" do
expect(find("#case-log-tcharge-field").value).to eq("5600.00")
click_button("Save and continue")
expect(page).to have_selector(".govuk-error-summary")
fill_in("case-log-scharge-field", with: nil)
fill_in("case-log-pscharge-field", with: nil)
fill_in("case-log-supcharg-field-error", with: nil)
fill_in("case-log-brent-field", with: 500)
expect(find("#case-log-tcharge-field").value).to eq("500.00")
fill_in("case-log-supcharg-field-error", with: 50)

1
spec/features/form/saving_data_spec.rb

@ -26,7 +26,6 @@ RSpec.describe "Form Saving Data" do
tenancycode: { type: "text", answer: "BZ737", path: "tenant-code-test" },
age1: { type: "numeric", answer: 25, path: "person_1_age" },
sex1: { type: "radio", answer: { "F" => "Female" }, path: "person_1_gender" },
hhmemb: { type: "numeric", answer: 3, path: "household_number_of_members" },
}
end

47
spec/features/schemes_spec.rb

@ -772,4 +772,51 @@ RSpec.describe "Schemes scheme Features" do
end
end
end
context "when selecting a scheme" do
let!(:user) { FactoryBot.create(:user, :data_coordinator, last_sign_in_at: Time.zone.now) }
let!(:schemes) { FactoryBot.create_list(:scheme, 5, owning_organisation: user.organisation) }
let(:location) { FactoryBot.create(:location, scheme: schemes[2]) }
let!(:case_log) { FactoryBot.create(:case_log, created_by: user, needstype: 2) }
before do
Timecop.freeze(Time.utc(2022, 6, 3))
location.update!(startdate: nil)
FactoryBot.create(:location, scheme: schemes[0], startdate: nil)
FactoryBot.create(:location, scheme: schemes[1], startdate: nil)
FactoryBot.create(:location, scheme: schemes[1], startdate: nil)
FactoryBot.create(:location, scheme: schemes[1], startdate: Time.utc(2023, 6, 3))
visit("/logs")
fill_in("user[email]", with: user.email)
fill_in("user[password]", with: user.password)
click_button("Sign in")
end
after do
Timecop.unfreeze
end
it "does not display the schemes without a location" do
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(3)
end
it "does not display the schemes with a location with a startdate in the future" do
location.update!(startdate: Time.utc(2022, 7, 4))
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(2)
end
it "does display the schemes with a location with a startdate in the past" do
location.update!(startdate: Time.utc(2022, 5, 2))
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(3)
end
it "does display the schemes with a location with a startdate being today" do
location.update!(startdate: Time.utc(2022, 6, 3))
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(3)
end
end
end

71
spec/models/form/setup/questions/location_id_spec.rb

@ -31,7 +31,74 @@ RSpec.describe Form::Setup::Questions::LocationId, type: :model do
expect(question).not_to be_derived
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({})
context "when there are no locations" do
it "the answer_options is an empty hash" do
expect(question.answer_options).to eq({})
end
end
context "when getting available locations" do
let(:scheme) { FactoryBot.create(:scheme) }
let(:case_log) { FactoryBot.create(:case_log, scheme:, needstype: 2) }
context "when there are no locations" do
it "the displayed_answer_options is an empty hash" do
expect(question.displayed_answer_options(case_log)).to eq({})
end
end
context "when selected scheme has locations" do
before do
Timecop.freeze(Time.utc(2022, 5, 12))
end
after do
Timecop.unfreeze
end
context "and all the locations have a future startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 13))
FactoryBot.create(:location, scheme:, startdate: Time.utc(2023, 1, 1))
end
it "the displayed_answer_options is an empty hash" do
expect(question.displayed_answer_options(case_log)).to eq({})
end
end
context "and the locations have a no startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: nil)
FactoryBot.create(:location, scheme:, startdate: nil)
end
it "the displayed_answer_options shows the locations" do
expect(question.displayed_answer_options(case_log).count).to eq(2)
end
end
context "and the locations have a past startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 4, 10))
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 12))
end
it "the displayed_answer_options shows the locations" do
expect(question.displayed_answer_options(case_log).count).to eq(2)
end
end
context "and some locations have a past startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 10, 10))
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 12))
end
it "the displayed_answer_options shows the active location" do
expect(question.displayed_answer_options(case_log).count).to eq(1)
end
end
end
end
end

18
spec/models/form/setup/questions/scheme_id_spec.rb

@ -50,9 +50,21 @@ RSpec.describe Form::Setup::Questions::SchemeId, type: :model do
FactoryBot.create(:scheme, owning_organisation: organisation_2)
end
it "has the correct answer_options based on the schemes the user's organisation owns or manages" do
expected_answer = { scheme.id.to_s => scheme.service_name }
expect(question.displayed_answer_options(case_log)).to eq(expected_answer)
context "when a scheme with at least 1 location exists" do
before do
FactoryBot.create(:location, scheme:)
end
it "has the correct answer_options based on the schemes the user's organisation owns or manages" do
expected_answer = { scheme.id.to_s => scheme }
expect(question.displayed_answer_options(case_log)).to eq(expected_answer)
end
end
context "when there are no schemes with locations" do
it "returns an empty hash" do
expect(question.displayed_answer_options(case_log)).to eq({})
end
end
end
end

4
spec/models/scheme_spec.rb

@ -46,6 +46,10 @@ RSpec.describe Scheme, type: :model do
end
context "when searching by all searchable fields" do
before do
location_2.update!(postcode: location_2.postcode.gsub(scheme_1.id.to_s, "0"))
end
it "returns case insensitive matching records" do
expect(described_class.search_by(scheme_1.id.to_s).count).to eq(1)
expect(described_class.search_by(scheme_1.id.to_s).first.id).to eq(scheme_1.id)

1
spec/rails_helper.rb

@ -80,6 +80,7 @@ RSpec.configure do |config|
Capybara.server = :puma, { Silent: true }
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :view
config.include Devise::Test::IntegrationHelpers, type: :request
config.include ViewComponent::TestHelpers, type: :component
config.include Capybara::RSpecMatchers, type: :component

2
spec/requests/case_logs_controller_spec.rb

@ -778,7 +778,7 @@ RSpec.describe CaseLogsController, type: :request do
it "dowloads searched logs" do
get "/logs?search=#{case_log.id}", headers:, params: {}
csv = CSV.parse(response.body)
expect(csv.count).to eq(2)
expect(csv.count).to eq(CaseLog.search_by(case_log.id.to_s).count + 1)
end
context "when both filter and search applied" do

10
spec/requests/locations_controller_spec.rb

@ -90,7 +90,8 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
let(:startdate) { Time.utc(2022, 2, 2) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", startdate: } } }
before do
sign_in user
@ -111,6 +112,7 @@ RSpec.describe LocationsController, type: :request do
expect(Location.last.units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
expect(Location.last.wheelchair_adaptation).to eq("No")
expect(Location.last.startdate).to eq(startdate)
end
context "when postcode is submitted with lower case" do
@ -389,8 +391,9 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
let!(:location) { FactoryBot.create(:location, scheme:) }
let(:startdate) { Time.utc(2021, 1, 2) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", startdate:, page: "edit" } } }
before do
sign_in user
@ -410,6 +413,7 @@ RSpec.describe LocationsController, type: :request do
expect(Location.last.units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
expect(Location.last.wheelchair_adaptation).to eq("No")
expect(Location.last.startdate).to eq(startdate)
end
context "when updating from edit-name page" do

Loading…
Cancel
Save