diff --git a/app/frontend/modules/search.js b/app/frontend/modules/search.js
index d9d233ab1..5e22c0cf5 100644
--- a/app/frontend/modules/search.js
+++ b/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 = `${value}`
- return html
+ const html = option.append ? `${value} ${option.append}` : `${value}`
+ return option.hint ? `${html}
${option.hint}
` : html
} else {
return 'No results found'
}
@@ -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
}
}
diff --git a/app/models/form/question.rb b/app/models/form/question.rb
index 9dd7488cb..d98fe5388 100644
--- a/app/models/form/question.rb
+++ b/app/models/form/question.rb
@@ -191,6 +191,25 @@ class Form::Question
label
end
+ def answer_option_synonyms(answer_id)
+ if id == "scheme_id"
+ Scheme.find(answer_id).locations.map(&:postcode).join(",")
+ end
+ end
+
+ def answer_option_append(answer_id)
+ if id == "scheme_id"
+ "(" + Scheme.find(answer_id).locations.count.to_s + " locations)"
+ end
+ end
+
+ def answer_option_hint(answer_id)
+ if id == "scheme_id"
+ scheme = Scheme.find(answer_id)
+ [scheme.primary_client_group, scheme.secondary_client_group].filter { |x| x.present? }.join(", ")
+ end
+ end
+
private
def selected_answer_option_is_derived?(case_log)
diff --git a/app/views/form/_select_question.html.erb b/app/views/form/_select_question.html.erb
index 52c47d66d..bd25041ab 100644
--- a/app/views/form/_select_question.html.erb
+++ b/app/views/form/_select_question.html.erb
@@ -2,12 +2,17 @@
<% 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" %>
+ <%= 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| %>
+
+ <% end %>
+ <% end %>
diff --git a/spec/features/form/accessible_autocomplete_spec.rb b/spec/features/form/accessible_autocomplete_spec.rb
index a8537199b..55bbba6bb 100644
--- a/spec/features/form/accessible_autocomplete_spec.rb
+++ b/spec/features/form/accessible_autocomplete_spec.rb
@@ -23,6 +23,12 @@ RSpec.describe "Accessible Automcomplete" do
context "when using accessible autocomplete" do
before do
+ allow_any_instance_of(Form::Question).to receive(:answer_option_synonyms).and_return(nil)
+ allow_any_instance_of(Form::Question).to receive(:answer_option_synonyms).with("E08000003").and_return("synonym")
+ allow_any_instance_of(Form::Question).to receive(:answer_option_append).and_return(nil)
+ allow_any_instance_of(Form::Question).to receive(:answer_option_append).with("E08000003").and_return(" (append)")
+ allow_any_instance_of(Form::Question).to receive(:answer_option_hint).and_return(nil)
+ allow_any_instance_of(Form::Question).to receive(:answer_option_hint).with("E08000003").and_return("hint")
visit("/logs/#{case_log.id}/accessible-select")
end
@@ -46,6 +52,21 @@ RSpec.describe "Accessible Automcomplete" do
expect(find("#case-log-prevloc-field").value).to eq("The one and only york town")
end
+ it "can match on synonyms", js: true do
+ find("#case-log-prevloc-field").click.native.send_keys("s", "y", "n", "o", "n", :down, :enter)
+ expect(find("#case-log-prevloc-field").value).to eq("Manchester")
+ end
+
+ it "displays appended text next to the options", js: true do
+ find("#case-log-prevloc-field").click.native.send_keys("m", "a", "n", :down, :enter)
+ expect(find(".autocomplete__option__append", visible: :hidden, text: /(append)/)).to be_present
+ end
+
+ it "displays hint text under the options", js: true do
+ find("#case-log-prevloc-field").click.native.send_keys("m", "a", "n", :down, :enter)
+ expect(find(".autocomplete__option__hint", visible: :hidden, text: /hint/)).to be_present
+ end
+
it "maintains enhancement state across back navigation", js: true do
find("#case-log-prevloc-field").click.native.send_keys("T", "h", "a", "n", :down, :enter)
click_button("Save and continue")
@@ -59,7 +80,7 @@ RSpec.describe "Accessible Automcomplete" do
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")
+ case_log.update!(postcode_known: 0, previous_la_known: 1, prevloc: "Oxford")
visit("/logs/#{case_log.id}/accessible-select")
expect(page).to have_select("case-log-prevloc-field", selected: %w[Oxford])
end