diff --git a/app/assets/javascripts/liveSearch.js b/app/assets/javascripts/liveSearch.js
index 25cf880e5..024de40e9 100644
--- a/app/assets/javascripts/liveSearch.js
+++ b/app/assets/javascripts/liveSearch.js
@@ -1,32 +1,54 @@
(function(Modules) {
"use strict";
+ let state;
let normalize = (string) => string.toLowerCase().replace(/ /g,'');
+ let resultsSummary = (num) => {
+ if (num === 0) {
+ return "no results";
+ } else {
+ return num + (num === 1 ? " result" : " results");
+ }
+ };
- let filter = ($searchBox, $targets) => () => {
+ let filter = ($searchBox, $searchLabel, $liveRegion, $targets) => () => {
let query = normalize($searchBox.val());
+ let results = 0;
$targets.each(function() {
let content = $('.live-search-relevant', this).text() || $(this).text();
+ let isMatch = normalize(content).indexOf(normalize(query)) > -1;
if ($(this).has(':checked').length) {
$(this).show();
+ results++;
return;
}
if (query == '') {
$(this).css('display', '');
+ results++;
return;
}
- $(this).toggle(
- normalize(content).indexOf(normalize(query)) > -1
- );
+ $(this).toggle(isMatch);
+
+ if (isMatch) { results++; }
});
+ if (state === 'loaded') {
+ if (query !== '') {
+ $searchBox.attr('aria-label', $searchLabel.text().trim() + ', ' + resultsSummary(results));
+ }
+ state = 'active';
+ } else {
+ $searchBox.removeAttr('aria-label');
+ $liveRegion.text(resultsSummary(results));
+ }
+
// make sticky JS recalculate its cache of the element's position
// because live search can change the height document
if ('stickAtBottomWhenScrolling' in GOVUK) {
@@ -43,12 +65,18 @@
let $component = $(component);
let $searchBox = $('input', $component);
+ let $searchLabel = $('label', $component);
+ let $liveRegion = $('.live-search__status', $component);
let filterFunc = filter(
$searchBox,
+ $searchLabel,
+ $liveRegion,
$($component.data('targets'))
);
+ state = 'loaded';
+
$searchBox.on('keyup input', filterFunc);
filterFunc();
diff --git a/app/templates/components/live-search.html b/app/templates/components/live-search.html
index 6ba673350..4108ded7f 100644
--- a/app/templates/components/live-search.html
+++ b/app/templates/components/live-search.html
@@ -14,6 +14,7 @@
"label": {"text": search_label},
"autocomplete": "off"
}) }}
+
{% endif %}
diff --git a/tests/javascripts/liveSearch.test.js b/tests/javascripts/liveSearch.test.js
index 94fc99c94..ba7422b92 100644
--- a/tests/javascripts/liveSearch.test.js
+++ b/tests/javascripts/liveSearch.test.js
@@ -10,11 +10,25 @@ afterAll(() => {
describe('Live search', () => {
+ let searchLabelText;
let searchTextbox;
+ let liveRegion
let list;
+ function liveRegionResults (num) {
+ if (num === 1) {
+ return "1 result";
+ } else if (num === 0) {
+ return "no results";
+ } else {
+ return `${num} results`;
+ }
+ };
+
describe("With a list of radios", () => {
+ searchLabelText = "Search branding styles by name";
+
function getRadiosHTML (departments) {
let result = '';
@@ -62,9 +76,10 @@ describe('Live search', () => {
`;
searchTextbox = document.getElementById('search');
+ liveRegion = document.querySelector('.live-search__status');
list = document.querySelector('form');
});
@@ -87,6 +103,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(listItems.length);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(false);
});
@@ -101,6 +118,8 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(2);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(2)}`);
});
@@ -115,6 +134,8 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`);
});
@@ -152,6 +173,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(listItems.length);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(listItemsShowing.length));
});
@@ -170,6 +192,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
});
@@ -188,6 +211,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(2);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(2));
});
@@ -216,6 +240,8 @@ describe('Live search', () => {
describe("With a list of checkboxes", () => {
+ searchLabelText = "Search branding styles by name";
+
beforeEach(() => {
const templatesAndFolders = [
@@ -246,9 +272,10 @@ describe('Live search', () => {
`;
searchTextbox = document.getElementById('search');
+ liveRegion = document.querySelector('.live-search__status');
list = document.querySelector('form');
});
@@ -273,6 +301,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(listItems.length);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(false);
});
@@ -288,6 +317,8 @@ describe('Live search', () => {
// should match 'New patient' and 'New doctor'
expect(listItemsShowing.length).toEqual(2);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(2)}`);
});
@@ -302,6 +333,8 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`);
});
@@ -334,6 +367,7 @@ describe('Live search', () => {
// 2 items contain the "Email template" text
// only the text containing the name of the item is matched against (ie 'New patient')
expect(listItemsShowing.length).toEqual(0);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(0)}`);
});
@@ -356,6 +390,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(listItems.length);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(listItemsShowing.length));
});
@@ -374,6 +409,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
});
@@ -392,6 +428,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
});
@@ -432,6 +469,7 @@ describe('Live search', () => {
// 2 items contain the "Email template" text
// only the text containing the name of the item is matched against (ie 'New patient')
expect(listItemsShowing.length).toEqual(0);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(0));
});
@@ -441,6 +479,8 @@ describe('Live search', () => {
describe("With a list of content items", () => {
+ searchLabelText = "Search by name or email address";
+
function getContentItems (users) {
function getPermissionsHTML (permissions) {
@@ -523,9 +563,10 @@ describe('Live search', () => {
`;
searchTextbox = document.getElementById('search');
+ liveRegion = document.querySelector('.live-search__status');
list = document.querySelector('form');
});
@@ -548,6 +590,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(listItems.length);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(false);
});
@@ -562,6 +605,8 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`);
});
@@ -576,6 +621,8 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`);
});
@@ -592,6 +639,7 @@ describe('Live search', () => {
// all items contain the "Add and edit templates" permission so would match if all text was matched against the search term
// only the text containing the label and email address is matched against
expect(listItemsShowing.length).toEqual(0);
+ expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(0)}`);
});
@@ -632,6 +680,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
});
@@ -650,6 +699,7 @@ describe('Live search', () => {
const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
expect(listItemsShowing.length).toEqual(1);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
});
@@ -670,6 +720,7 @@ describe('Live search', () => {
// all items contain the "Add and edit templates" permission so would match if all text was matched against the search term
// only the text containing the label and email address is matched against
expect(listItemsShowing.length).toEqual(0);
+ expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(0));
});