Browse Source

add synonyms, appended text and hint text to the autocomplete

pull/739/head
Kat 3 years ago
parent
commit
ed73238426
  1. 28
      app/frontend/modules/search.js
  2. 19
      app/models/form/question.rb
  3. 23
      app/views/form/_select_question.html.erb
  4. 23
      spec/features/form/accessible_autocomplete_spec.rb

28
app/frontend/modules/search.js

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

19
app/models/form/question.rb

@ -191,6 +191,25 @@ class Form::Question
label label
end 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 private
def selected_answer_option_is_derived?(case_log) def selected_answer_option_is_derived?(case_log)

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

@ -2,12 +2,17 @@
<% selected = @case_log.public_send(question.id) || "" %> <% selected = @case_log.public_send(question.id) || "" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value) } %> <% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value) } %>
<%= f.govuk_collection_select question.id.to_sym, <%= f.govuk_select(question.id.to_sym,
answers, label: legend(question, page_header, conditional),
:id, "data-controller": "accessible-autocomplete",
:name, caption: caption(caption_text, page_header, conditional),
caption: caption(caption_text, page_header, conditional), hint: { text: question.hint_text&.html_safe }) do %>
label: legend(question, page_header, conditional), <% answers.each do |answer| %>
hint: { text: question.hint_text&.html_safe }, <option value="<%= answer.id %>"
options: { disabled: [""], selected: }, data-synonyms="<%= question.answer_option_synonyms(answer.id) %>"
"data-controller": "accessible-autocomplete" %> data-append="<%= question.answer_option_append(answer.id) %>"
data-hint="<%= question.answer_option_hint(answer.id) %>"
<%= @case_log[question.id] == answer.name || @case_log[question.id].to_s == answer.id ? "selected" : "" %>
<%= answer.id == "" ? "disabled" : "" %>><%= answer.name %></option>
<% end %>
<% end %>

23
spec/features/form/accessible_autocomplete_spec.rb

@ -23,6 +23,12 @@ RSpec.describe "Accessible Automcomplete" do
context "when using accessible autocomplete" do context "when using accessible autocomplete" do
before 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") visit("/logs/#{case_log.id}/accessible-select")
end 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") expect(find("#case-log-prevloc-field").value).to eq("The one and only york town")
end 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 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) find("#case-log-prevloc-field").click.native.send_keys("T", "h", "a", "n", :down, :enter)
click_button("Save and continue") click_button("Save and continue")
@ -59,7 +80,7 @@ RSpec.describe "Accessible Automcomplete" do
end end
it "has the correct option selected if one has been saved" do 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") visit("/logs/#{case_log.id}/accessible-select")
expect(page).to have_select("case-log-prevloc-field", selected: %w[Oxford]) expect(page).to have_select("case-log-prevloc-field", selected: %w[Oxford])
end end

Loading…
Cancel
Save