From 49f79368d09ece131c86799940891ae9e281e0bb Mon Sep 17 00:00:00 2001 From: George Goodall Date: Thu, 1 Aug 2024 15:16:49 +0100 Subject: [PATCH 01/17] added basic test file --- test/unit/findPage.test.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 test/unit/findPage.test.js diff --git a/test/unit/findPage.test.js b/test/unit/findPage.test.js new file mode 100644 index 00000000..24f088f1 --- /dev/null +++ b/test/unit/findPage.test.js @@ -0,0 +1,31 @@ +import { describe, it } from 'vitest' +import nunjucks from 'nunjucks' +import addFilters from '../../src/filters/filters' + +const nunjucksEnv = nunjucks.configure([ + 'src/views', + 'src/views/check', + 'src/views/submit', + 'node_modules/govuk-frontend/dist/', + 'node_modules/@x-govuk/govuk-prototype-components/' +], { + dev: true, + noCache: true, + watch: true +}) + +addFilters(nunjucksEnv, { dataSubjects: {} }) + +describe('find page', () => { + it.todo('renders all the organisations supplied', () => { + + }) + + it.todo('has the search box on screen', () => { + + }) + + it.todo('has the javascript file for the list-filter', () => { + + }) +}) From f522b3152590edb48c36db245d43a6fcda22b411 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 11:33:34 +0100 Subject: [PATCH 02/17] get the list-filter working --- src/assets/js/list-filter.js | 121 +++++++++++++++++++++ src/assets/scss/index.scss | 5 + src/controllers/OrganisationsController.js | 39 ++++++- src/views/organisations/find.html | 56 +++++++++- webpack.config.mjs | 3 +- 5 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 src/assets/js/list-filter.js diff --git a/src/assets/js/list-filter.js b/src/assets/js/list-filter.js new file mode 100644 index 00000000..9eebf6bf --- /dev/null +++ b/src/assets/js/list-filter.js @@ -0,0 +1,121 @@ +/* eslint-disable no-var */ +//= require govuk_publishing_components/vendor/polyfills/closest + +window.GOVUK = window.GOVUK || {} +window.GOVUK.Modules = window.GOVUK.Modules || {}; + +(function (Modules) { + function ListFilter ($module) { + this.$module = $module + this.filterTimeout = null + this.form = this.$module.querySelector('[data-filter="form"]') + this.searchResults = this.$module.querySelector('#search_results') + } + + ListFilter.prototype.init = function () { + this.$module.filterList = this.filterList.bind(this) + // Form should only appear if the JS is working + this.form.classList.add('filter-list__form--active') + this.results = document.createElement('div') + this.results.classList.add('filter-list__results', 'govuk-heading-m', 'js-search-results') + this.results.setAttribute('aria-live', 'polite') + this.results.innerHTML = this.countInitialItems() + ' results found' + this.searchResults.insertBefore(this.results, this.searchResults.firstChild) + + // We don't want the form to submit/refresh the page on enter key + this.form.onsubmit = function () { return false } + + this.form.addEventListener('keyup', function (e) { + var searchTerm = e.target.value + clearTimeout(this.filterTimeout) + this.filterTimeout = setTimeout(function () { + this.$module.filterList(searchTerm) + }.bind(this), 200) + }.bind(this)) + } + + ListFilter.prototype.filterList = function (searchTerm) { + var itemsToFilter = this.$module.querySelectorAll('[data-filter="item"]') + var blocksToFilter = this.$module.querySelectorAll('[data-filter="block"]') + for (var i = 0; i <= itemsToFilter.length - 1; i++) { + var currentItem = itemsToFilter[i] + if (!this.matchSearchTerm(currentItem, searchTerm)) { + currentItem.classList.add('js-hidden') + } + } + this.updateItemCount(blocksToFilter) + } + + ListFilter.prototype.matchSearchTerm = function (item, term) { + var normaliseWhitespace = function (string) { + return string + .trim() // Removes spaces at beginning and end of string. + .replace(/\r?\n|\r/g, ' ') // Replaces line breaks with one space. + .replace(/\s+/g, ' ') // Squashes multiple spaces to one space. + } + + var searchTerms = item.getAttribute('data-filter-terms') || '' + var normalisedTerms = normaliseWhitespace(searchTerms) + + item.classList.remove('js-hidden') + + var searchTermRegexp = new RegExp(term.trim().replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') + return searchTermRegexp.exec(normalisedTerms) !== null + } + + ListFilter.prototype.countInitialItems = function () { + return this.$module.querySelectorAll('[data-filter="item"]').length + } + + ListFilter.prototype.updateItemCount = function (blocksToFilter) { + var totalMatchingItems = 0 + + for (var i = 0; i < blocksToFilter.length; i++) { + var block = blocksToFilter[i].closest('[data-filter="block"]') + block.classList.remove('js-hidden') + + var matchingItems = block.querySelectorAll('[data-filter="item"]') + var matchingItemCount = 0 + + var innerBlocks = block.querySelectorAll('[data-filter="inner-block"]') + for (var r = 0; r < innerBlocks.length; r++) { + innerBlocks[r].classList.add('js-hidden') + } + + for (var j = 0; j < matchingItems.length; j++) { + if (!matchingItems[j].classList.contains('js-hidden')) { + matchingItemCount++ + + if (matchingItems[j].closest('[data-filter="inner-block"]') !== null) { matchingItems[j].closest('[data-filter="inner-block"]').classList.remove('js-hidden') } + } + } + + var itemCount = block.querySelectorAll('[data-item-count="true"]') + var accessibleItemCount = block.querySelectorAll('.js-accessible-item-count') + + if (matchingItemCount === 0) { + block.classList.toggle('js-hidden') + } + + if (matchingItemCount > 0) { + for (var l = 0; l < itemCount.length; l++) { + itemCount[l].textContent = matchingItemCount + } + + for (var k = 0; k < accessibleItemCount.length; k++) { + accessibleItemCount[k].textContent = matchingItemCount + } + } + + totalMatchingItems += matchingItemCount + } + + var text = ' results found' + if (totalMatchingItems === 1) { + text = ' result found' + } + this.results.innerHTML = totalMatchingItems + text + } + + Modules.ListFilter = ListFilter +})(window.GOVUK.Modules) diff --git a/src/assets/scss/index.scss b/src/assets/scss/index.scss index 14c1bd62..a10e9008 100644 --- a/src/assets/scss/index.scss +++ b/src/assets/scss/index.scss @@ -102,3 +102,8 @@ code, code * { font-family: monospace; } + +.js-hidden { + display: none; + visibility: none; +} \ No newline at end of file diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 05984dd0..cd03f662 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -71,7 +71,44 @@ const organisationsController = { }, async getOrganisations (req, res, next) { - res.render('organisations/find.html') + + const alphabetisedOrgs = { + A: [ + { + name: 'ant' + }, + { + name: 'aber' + }, + { + name: 'arrot' + } + ], + B: [ + { + name: 'barnsly' + }, + { + name: 'big ben' + }, + { + name: 'butter' + } + ], + C: [ + { + name: 'cec' + }, + { + name: 'cing' + }, + { + name: 'cheltnham' + } + ] + } + + res.render('organisations/find.html', { alphabetisedOrgs }) } } diff --git a/src/views/organisations/find.html b/src/views/organisations/find.html index c6d190d9..6affb0a6 100644 --- a/src/views/organisations/find.html +++ b/src/views/organisations/find.html @@ -1,9 +1,9 @@ {% extends "layouts/main.html" %} -{% from "govuk/components/breadcrumbs/macro.njk" import govukBreadcrumbs %} -{% from "govuk/components/tag/macro.njk" import govukTag %} +{% set pageName = "Find your organisation" %} {% block beforeContent %} + {{ super() }} {% endblock %} @@ -12,15 +12,59 @@
+

{{ pageName }}

+
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+ + {% for letter, orgs in alphabetisedOrgs %} +
+
+

{{ letter }}

+
+
+ +
+
+
+
+
+ {% endfor %} + -

- {{ pageName }} -

+ +
+
-

Find page placeholder

+{% endblock %} +{% block scripts %} + {{ super() }} + + {% endblock %} \ No newline at end of file diff --git a/webpack.config.mjs b/webpack.config.mjs index 0a01a9e9..3b30f334 100644 --- a/webpack.config.mjs +++ b/webpack.config.mjs @@ -23,7 +23,8 @@ export default { entry: { map: '/src/assets/js/map.js', application: '/src/assets/js/application.js', - statusPage: '/src/assets/js/statusPage.js' + statusPage: '/src/assets/js/statusPage.js', + 'list-filter': '/src/assets/js/list-filter.js' }, output: { filename: '[name].bundle.js', From 9b987c6162702fe5eaf415e7a9292eccd83852cb Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 11:33:50 +0100 Subject: [PATCH 03/17] linting --- src/controllers/OrganisationsController.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index cd03f662..76f2c814 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -71,15 +71,14 @@ const organisationsController = { }, async getOrganisations (req, res, next) { - const alphabetisedOrgs = { A: [ { name: 'ant' - }, + }, { name: 'aber' - }, + }, { name: 'arrot' } @@ -87,10 +86,10 @@ const organisationsController = { B: [ { name: 'barnsly' - }, + }, { name: 'big ben' - }, + }, { name: 'butter' } @@ -98,10 +97,10 @@ const organisationsController = { C: [ { name: 'cec' - }, + }, { name: 'cing' - }, + }, { name: 'cheltnham' } From 7acb9f99ee3fc29c8bacab27578b4623be15df3b Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 11:39:06 +0100 Subject: [PATCH 04/17] add comment to list-filter pointing to source repo --- src/assets/js/list-filter.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/js/list-filter.js b/src/assets/js/list-filter.js index 9eebf6bf..edc18066 100644 --- a/src/assets/js/list-filter.js +++ b/src/assets/js/list-filter.js @@ -1,3 +1,7 @@ +/** + * This file is taken from https://github.com/alphagov/collections/blob/main/app/assets/javascripts/modules/list-filter.js + */ + /* eslint-disable no-var */ //= require govuk_publishing_components/vendor/polyfills/closest From f160492176fb29ec7254be5018268c1bb2989ad4 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 11:42:42 +0100 Subject: [PATCH 05/17] update example orgs --- src/controllers/OrganisationsController.js | 58 ++++++++++++++++++---- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 76f2c814..5b65911c 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -74,35 +74,75 @@ const organisationsController = { const alphabetisedOrgs = { A: [ { - name: 'ant' + name: 'Aberdeen' }, { - name: 'aber' + name: 'Aylesbury' }, { - name: 'arrot' + name: 'Ashford' } ], B: [ { - name: 'barnsly' + name: 'Bath' }, { - name: 'big ben' + name: 'Birmingham' }, { - name: 'butter' + name: 'Brighton' } ], C: [ { - name: 'cec' + name: 'Cambridge' }, { - name: 'cing' + name: 'Cardiff' }, { - name: 'cheltnham' + name: 'Cheltenham' + }, + { + name: 'Chester' + } + ], + D: [ + { + name: 'Derby' + }, + { + name: 'Dundee' + } + ], + E: [ + { + name: 'Edinburgh' + }, + { + name: 'Epsom' + } + ], + G: [ + { + name: 'Glasgow' + }, + { + name: 'Gloucester' + } + ], + H: [ + { + name: 'Hull' + } + ], + L: [ + { + name: 'Leeds' + }, + { + name: 'London' } ] } From b1b6a2f0d22ab5519af3ef021bedbe3d4ff357df Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 13:52:21 +0100 Subject: [PATCH 06/17] working on tests --- test/unit/findPage.test.js | 71 ++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/test/unit/findPage.test.js b/test/unit/findPage.test.js index 24f088f1..7f1ec01e 100644 --- a/test/unit/findPage.test.js +++ b/test/unit/findPage.test.js @@ -1,6 +1,8 @@ -import { describe, it } from 'vitest' +import { describe, it, expect } from 'vitest' import nunjucks from 'nunjucks' import addFilters from '../../src/filters/filters' +import jsdom from 'jsdom' +import { runGenericPageTests } from './generic-page.js' const nunjucksEnv = nunjucks.configure([ 'src/views', @@ -14,18 +16,73 @@ const nunjucksEnv = nunjucks.configure([ watch: true }) -addFilters(nunjucksEnv, { dataSubjects: {} }) +addFilters(nunjucksEnv, {}) -describe('find page', () => { - it.todo('renders all the organisations supplied', () => { +describe('Organisations Find Page', () => { + const params = { + alphabetisedOrgs: { + A: [ + { + name: 'Aberdeen' + }, + { + name: 'Aylesbury' + }, + { + name: 'Ashford' + } + ], + B: [ + { + name: 'Bath' + }, + { + name: 'Birmingham' + }, + { + name: 'Brighton' + } + ], + }, + serviceName: "mock service name" + } + const html = nunjucks.render('organisations/find.html', params) + + const dom = new jsdom.JSDOM(html) + const document = dom.window.document + + runGenericPageTests(html, { + pageTitle: 'Find your organisation - mock service name', + serviceName: "mock service name" }) - it.todo('has the search box on screen', () => { + it('correct has a form element with the correct data-filter attribute', () => { + const formElement = document.querySelector('form') + expect(formElement.getAttribute('data-filter')).toBe('form') + }) + it('correctly has elements with the data-filter=block and data-filter=inner block attributes', () => { + const blockElements = document.querySelectorAll('[data-filter="block"]'); + expect(blockElements.length).toBeGreaterThan(0); + + const innerBlockElements = document.querySelectorAll('[data-filter="inner-block"]'); + expect(innerBlockElements.length).toBeGreaterThan(0); + + expect(blockElements.length).toEqual(innerBlockElements.length) }) - it.todo('has the javascript file for the list-filter', () => { + it('Renders the correct organisation list with appropriate attributes', () => { + const organisationList = document.querySelector('.') + expect(organisationList.children.length).toBe(Object.keys(params.alphabetisedOrgs).length) + Object.keys(params.alphabetisedOrgs).forEach((letter, i) => { + const organisationSection = organisationList.children[i] + expect(organisationSection.querySelector('.govuk-accordion__header').textContent).toBe(letter) + const organisationListItems = organisationSection.querySelector('.govuk-list').children + params.alphabetisedOrgs[letter].forEach((organisation, j) => { + expect(organisationListItems[j].textContent).toContain(organisation.name) + }) + }) }) -}) +}) \ No newline at end of file From 50a243f56c81834f319d574b7076e596084b86d0 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 14:22:31 +0100 Subject: [PATCH 07/17] finish of tests for find.html --- src/views/organisations/find.html | 2 +- test/unit/findPage.test.js | 26 ++++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/views/organisations/find.html b/src/views/organisations/find.html index 6affb0a6..8f169529 100644 --- a/src/views/organisations/find.html +++ b/src/views/organisations/find.html @@ -32,7 +32,7 @@

{{ pageName }}

{% for letter, orgs in alphabetisedOrgs %}
-

{{ letter }}

+

{{ letter }}

    diff --git a/test/unit/findPage.test.js b/test/unit/findPage.test.js index 7f1ec01e..0eba8e95 100644 --- a/test/unit/findPage.test.js +++ b/test/unit/findPage.test.js @@ -42,9 +42,9 @@ describe('Organisations Find Page', () => { { name: 'Brighton' } - ], + ] }, - serviceName: "mock service name" + serviceName: 'mock service name' } const html = nunjucks.render('organisations/find.html', params) @@ -54,35 +54,37 @@ describe('Organisations Find Page', () => { runGenericPageTests(html, { pageTitle: 'Find your organisation - mock service name', - serviceName: "mock service name" + serviceName: 'mock service name' }) it('correct has a form element with the correct data-filter attribute', () => { const formElement = document.querySelector('form') - expect(formElement.getAttribute('data-filter')).toBe('form') + expect(formElement.getAttribute('data-filter')).toBe('form') }) it('correctly has elements with the data-filter=block and data-filter=inner block attributes', () => { - const blockElements = document.querySelectorAll('[data-filter="block"]'); - expect(blockElements.length).toBeGreaterThan(0); - - const innerBlockElements = document.querySelectorAll('[data-filter="inner-block"]'); - expect(innerBlockElements.length).toBeGreaterThan(0); + const blockElements = document.querySelectorAll('[data-filter="block"]') + expect(blockElements.length).toBeGreaterThan(0) + + const innerBlockElements = document.querySelectorAll('[data-filter="inner-block"]') + expect(innerBlockElements.length).toBeGreaterThan(0) expect(blockElements.length).toEqual(innerBlockElements.length) }) it('Renders the correct organisation list with appropriate attributes', () => { - const organisationList = document.querySelector('.') + const organisationList = document.querySelector('#search_results') expect(organisationList.children.length).toBe(Object.keys(params.alphabetisedOrgs).length) Object.keys(params.alphabetisedOrgs).forEach((letter, i) => { const organisationSection = organisationList.children[i] - expect(organisationSection.querySelector('.govuk-accordion__header').textContent).toBe(letter) + expect(organisationSection.querySelector('.blockHeading').textContent).toBe(letter) const organisationListItems = organisationSection.querySelector('.govuk-list').children params.alphabetisedOrgs[letter].forEach((organisation, j) => { expect(organisationListItems[j].textContent).toContain(organisation.name) + expect(organisationListItems[j].getAttribute('data-filter')).toEqual('item') + expect(organisationListItems[j].getAttribute('data-filter-terms')).toEqual(organisation.name) }) }) }) -}) \ No newline at end of file +}) From 6b789d2ca288070fe2e1bc93445b1fcf16d2fe87 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 15:37:02 +0100 Subject: [PATCH 08/17] reduce wait time between updating list filter --- src/assets/js/list-filter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/assets/js/list-filter.js b/src/assets/js/list-filter.js index edc18066..02b0a2d9 100644 --- a/src/assets/js/list-filter.js +++ b/src/assets/js/list-filter.js @@ -5,6 +5,8 @@ /* eslint-disable no-var */ //= require govuk_publishing_components/vendor/polyfills/closest +const keyPauseTime = 20 + window.GOVUK = window.GOVUK || {} window.GOVUK.Modules = window.GOVUK.Modules || {}; @@ -34,7 +36,7 @@ window.GOVUK.Modules = window.GOVUK.Modules || {}; clearTimeout(this.filterTimeout) this.filterTimeout = setTimeout(function () { this.$module.filterList(searchTerm) - }.bind(this), 200) + }.bind(this), keyPauseTime) }.bind(this)) } From 6c75e2e282ed007c121eb58b83eea006a1662983 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 15:37:19 +0100 Subject: [PATCH 09/17] remove line --- src/views/organisations/find.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/views/organisations/find.html b/src/views/organisations/find.html index 8f169529..15609fd2 100644 --- a/src/views/organisations/find.html +++ b/src/views/organisations/find.html @@ -28,7 +28,6 @@

    {{ pageName }}

    - {% for letter, orgs in alphabetisedOrgs %}
    From d8b66fb6e492c29ac263430c04a42be73ba39200 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 15:39:01 +0100 Subject: [PATCH 10/17] get organisations from datasette --- src/controllers/OrganisationsController.js | 97 +++++----------------- 1 file changed, 22 insertions(+), 75 deletions(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 5b65911c..301c717c 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -1,3 +1,4 @@ +import datasette from '../services/datasette.js' import performanceDbApi from '../services/performanceDbApi.js' // Assume you have an API service module import logger from '../utils/logger.js' import { dataSubjects } from '../utils/utils.js' @@ -70,85 +71,31 @@ const organisationsController = { } }, + /** + * Handles the GET /organisations request + * + * @param {Request} req + * @param {Response} res + * @param {NextFunction} next + */ async getOrganisations (req, res, next) { - const alphabetisedOrgs = { - A: [ - { - name: 'Aberdeen' - }, - { - name: 'Aylesbury' - }, - { - name: 'Ashford' - } - ], - B: [ - { - name: 'Bath' - }, - { - name: 'Birmingham' - }, - { - name: 'Brighton' - } - ], - C: [ - { - name: 'Cambridge' - }, - { - name: 'Cardiff' - }, - { - name: 'Cheltenham' - }, - { - name: 'Chester' - } - ], - D: [ - { - name: 'Derby' - }, - { - name: 'Dundee' - } - ], - E: [ - { - name: 'Edinburgh' - }, - { - name: 'Epsom' - } - ], - G: [ - { - name: 'Glasgow' - }, - { - name: 'Gloucester' - } - ], - H: [ - { - name: 'Hull' - } - ], - L: [ - { - name: 'Leeds' - }, - { - name: 'London' - } - ] - } + const sql = 'select name, organisation from organisation' + const result = await datasette.runQuery(sql) + + const sortedResults = result.formattedData.sort((a, b) => { + return a.name.localeCompare(b.name) + }) + + const alphabetisedOrgs = sortedResults.reduce((acc, current) => { + const firstLetter = current.name.charAt(0).toUpperCase() + acc[firstLetter] = acc[firstLetter] || [] + acc[firstLetter].push(current) + return acc + }, {}) res.render('organisations/find.html', { alphabetisedOrgs }) } + } export default organisationsController From f626da0388b83cf3742632bf662100e221c1e635 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 15:58:01 +0100 Subject: [PATCH 11/17] updated tests --- src/controllers/OrganisationsController.js | 29 ++++---- test/unit/organisationsController.test.js | 77 ++++++++++++++++++++-- 2 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 301c717c..5c3c16fe 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -79,21 +79,26 @@ const organisationsController = { * @param {NextFunction} next */ async getOrganisations (req, res, next) { - const sql = 'select name, organisation from organisation' - const result = await datasette.runQuery(sql) + try { + const sql = 'select name, organisation from organisation' + const result = await datasette.runQuery(sql) - const sortedResults = result.formattedData.sort((a, b) => { - return a.name.localeCompare(b.name) - }) + const sortedResults = result.formattedData.sort((a, b) => { + return a.name.localeCompare(b.name) + }) - const alphabetisedOrgs = sortedResults.reduce((acc, current) => { - const firstLetter = current.name.charAt(0).toUpperCase() - acc[firstLetter] = acc[firstLetter] || [] - acc[firstLetter].push(current) - return acc - }, {}) + const alphabetisedOrgs = sortedResults.reduce((acc, current) => { + const firstLetter = current.name.charAt(0).toUpperCase() + acc[firstLetter] = acc[firstLetter] || [] + acc[firstLetter].push(current) + return acc + }, {}) - res.render('organisations/find.html', { alphabetisedOrgs }) + res.render('organisations/find.html', { alphabetisedOrgs }) + } catch (err) { + logger.error(err) + next(err) + } } } diff --git a/test/unit/organisationsController.test.js b/test/unit/organisationsController.test.js index a36c3be7..67dbc892 100644 --- a/test/unit/organisationsController.test.js +++ b/test/unit/organisationsController.test.js @@ -1,6 +1,7 @@ import { describe, it, vi, expect, beforeEach } from 'vitest' -import LpaOverviewController from '../../src/controllers/OrganisationsController.js' +import organisationsController from '../../src/controllers/OrganisationsController.js' import performanceDbApi from '../../src/services/performanceDbApi.js' +import datasette from '../../src/services/datasette.js' vi.mock('../../src/services/performanceDbApi.js') vi.mock('../../src/utils/utils.js', () => { @@ -8,6 +9,7 @@ vi.mock('../../src/utils/utils.js', () => { dataSubjects: {} } }) +vi.mock('../../src/services/datasette.js') describe('OrganisationsController.js', () => { beforeEach(() => { @@ -31,7 +33,7 @@ describe('OrganisationsController.js', () => { performanceDbApi.getLpaOverview = vi.fn().mockResolvedValue(expectedResponse) - await LpaOverviewController.getOverview(req, res, next) + await organisationsController.getOverview(req, res, next) expect(res.render).toHaveBeenCalledTimes(1) expect(res.render).toHaveBeenCalledWith('organisations/overview.html', expect.objectContaining({ @@ -57,7 +59,7 @@ describe('OrganisationsController.js', () => { vi.mocked(performanceDbApi.getLpaOverview).mockRejectedValue(error) - await LpaOverviewController.getOverview(req, res, next) + await organisationsController.getOverview(req, res, next) expect(next).toHaveBeenCalledTimes(1) expect(next).toHaveBeenCalledWith(error) @@ -65,10 +67,75 @@ describe('OrganisationsController.js', () => { }) describe('find', () => { - it.todo('should render the find page', () => { + it('should call render with the find page', async () => { + const req = {} + const res = { render: vi.fn() } + const next = vi.fn() + + vi.mocked(datasette.runQuery).mockResolvedValue({ formattedData: [] }) + + await organisationsController.getOrganisations(req, res, next) + + expect(res.render).toHaveBeenCalledTimes(1) + expect(res.render).toHaveBeenCalledWith('organisations/find.html', expect.objectContaining({ + alphabetisedOrgs: {} + })) + }) + + it('should correctly sort and restructure the data recieved from datasette, then pass it on to the template', async () => { + const req = {} + const res = { render: vi.fn() } + const next = vi.fn() + + const datasetteResponse = [ + { name: 'Aardvark Healthcare', organisation: 'Aardvark Healthcare' }, + { name: 'Bath NHS Trust', organisation: 'Bath NHS Trust' }, + { name: 'Bristol Hospital', organisation: 'Bristol Hospital' }, + { name: 'Cardiff Health Board', organisation: 'Cardiff Health Board' }, + { name: 'Derbyshire Healthcare', organisation: 'Derbyshire Healthcare' }, + { name: 'East Sussex NHS Trust', organisation: 'East Sussex NHS Trust' } + ] + + vi.mocked(datasette.runQuery).mockResolvedValue({ formattedData: datasetteResponse }) + await organisationsController.getOrganisations(req, res, next) + + expect(res.render).toHaveBeenCalledTimes(1) + expect(res.render).toHaveBeenCalledWith('organisations/find.html', expect.objectContaining({ + alphabetisedOrgs: { + A: [ + { name: 'Aardvark Healthcare', organisation: 'Aardvark Healthcare' } + ], + B: [ + { name: 'Bath NHS Trust', organisation: 'Bath NHS Trust' }, + { name: 'Bristol Hospital', organisation: 'Bristol Hospital' } + ], + C: [ + { name: 'Cardiff Health Board', organisation: 'Cardiff Health Board' } + ], + D: [ + { name: 'Derbyshire Healthcare', organisation: 'Derbyshire Healthcare' } + ], + E: [ + { name: 'East Sussex NHS Trust', organisation: 'East Sussex NHS Trust' } + ] + } + })) }) - it.todo('should catch errors and pass them onto the next function') + it('should catch errors and pass them onto the next function', async () => { + const req = {} + const res = {} + const next = vi.fn() + + const error = new Error('Test error') + + vi.mocked(datasette.runQuery).mockRejectedValue(error) + + await organisationsController.getOrganisations(req, res, next) + + expect(next).toHaveBeenCalledTimes(1) + expect(next).toHaveBeenCalledWith(error) + }) }) }) From c798a1e632888d9a28dcf1fcf14263c2938adeed Mon Sep 17 00:00:00 2001 From: George Goodall Date: Fri, 2 Aug 2024 16:05:22 +0100 Subject: [PATCH 12/17] make sure feature deploy workflow is correct --- .github/workflows/featureDeploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/featureDeploy.yml b/.github/workflows/featureDeploy.yml index acd59b32..7e77d25c 100644 --- a/.github/workflows/featureDeploy.yml +++ b/.github/workflows/featureDeploy.yml @@ -30,10 +30,10 @@ jobs: strategy: matrix: environment: ${{ fromJSON(needs.detect-environments.outputs.environments) }} - if: ${{ matrix.environment != 'production' }} + if: ${{ inputs.environment != 'production' }} uses: ./.github/workflows/deploy.yml with: - environment: '${{ matrix.environment }}' + environment: '${{ inputs.environment }}' secrets: inherit From 295e6a9850c7c0db840fde8119651bc4436d50e4 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 6 Aug 2024 10:47:56 +0100 Subject: [PATCH 13/17] remove incorrect js-hidden class --- src/assets/js/js-hidden.js | 3 ++- src/assets/scss/index.scss | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/assets/js/js-hidden.js b/src/assets/js/js-hidden.js index d099bc5e..d33915be 100644 --- a/src/assets/js/js-hidden.js +++ b/src/assets/js/js-hidden.js @@ -1,7 +1,8 @@ const hideElementsWithJsHidden = () => { document.querySelectorAll('.js-hidden').forEach((el, i) => { - console.log(el) + console.log('Hiding element', el) el.style.display = 'none' + el.style.visibility = 'none' }) } diff --git a/src/assets/scss/index.scss b/src/assets/scss/index.scss index a10e9008..bd86cf6d 100644 --- a/src/assets/scss/index.scss +++ b/src/assets/scss/index.scss @@ -101,9 +101,4 @@ $govuk-global-styles: true; code, code * { font-family: monospace; -} - -.js-hidden { - display: none; - visibility: none; } \ No newline at end of file From b24621b0bc35b639f624b8f49963486a263e9900 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 6 Aug 2024 10:58:35 +0100 Subject: [PATCH 14/17] updated js hidden to use a mutation observer to update element style --- src/assets/js/application.js | 4 ++-- src/assets/js/js-hidden.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/assets/js/application.js b/src/assets/js/application.js index 2a2e5d48..a207f019 100644 --- a/src/assets/js/application.js +++ b/src/assets/js/application.js @@ -3,8 +3,8 @@ as it will be loaded into the base nunjucks template. */ -import hideElementsWithJsHidden from './js-hidden.js' +import initiateJsHiddenChecks from './js-hidden.js' window.addEventListener('load', () => { - hideElementsWithJsHidden() + initiateJsHiddenChecks() }) diff --git a/src/assets/js/js-hidden.js b/src/assets/js/js-hidden.js index d33915be..add3c7f9 100644 --- a/src/assets/js/js-hidden.js +++ b/src/assets/js/js-hidden.js @@ -1,4 +1,30 @@ -const hideElementsWithJsHidden = () => { +/* globals MutationObserver, document */ + +const initiateJsHiddenChecks = () => { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + const target = mutation.target + const classList = target.classList + if (classList.contains('js-hidden')) { + // Class js-hidden was added + target.style.display = 'none' + target.style.visibility = 'hidden' + } else { + // Class js-hidden was removed + target.style.display = '' + target.style.visibility = '' + } + } + }) + }) + + observer.observe(document.body, { + attributes: true, + attributeFilter: ['class'], + subtree: true + }) + document.querySelectorAll('.js-hidden').forEach((el, i) => { console.log('Hiding element', el) el.style.display = 'none' @@ -6,4 +32,4 @@ const hideElementsWithJsHidden = () => { }) } -export default hideElementsWithJsHidden +export default initiateJsHiddenChecks From ae916ff650ef1fb1ae3e43afd04287b3398f25ae Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 6 Aug 2024 10:59:14 +0100 Subject: [PATCH 15/17] add js doc --- src/assets/js/js-hidden.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/assets/js/js-hidden.js b/src/assets/js/js-hidden.js index add3c7f9..280bad12 100644 --- a/src/assets/js/js-hidden.js +++ b/src/assets/js/js-hidden.js @@ -1,5 +1,13 @@ /* globals MutationObserver, document */ +/** + * Initiates checks for elements with the class 'js-hidden' and updates their display and visibility styles accordingly. + * + * When an element gains the 'js-hidden' class, its display and visibility styles are set to 'none' and 'hidden', respectively. + * When an element loses the 'js-hidden' class, its display and visibility styles are reset to their default values. + * + * This function also hides any elements that already have the 'js-hidden' class when it is called. + */ const initiateJsHiddenChecks = () => { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { From b17ff60250d10310f6ff61101f37ae58c95d0cf6 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 6 Aug 2024 11:01:46 +0100 Subject: [PATCH 16/17] update form label --- src/views/organisations/find.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/organisations/find.html b/src/views/organisations/find.html index 8f169529..6db1c7ff 100644 --- a/src/views/organisations/find.html +++ b/src/views/organisations/find.html @@ -21,7 +21,8 @@

    {{ pageName }}

    - + +
    From 52e23fa423107f28bd04156f6021b67f4a1ae594 Mon Sep 17 00:00:00 2001 From: George Goodall Date: Tue, 6 Aug 2024 12:29:58 +0100 Subject: [PATCH 17/17] Update OrganisationsController.js error->warn --- src/controllers/OrganisationsController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/OrganisationsController.js b/src/controllers/OrganisationsController.js index 5c3c16fe..c4d388fc 100644 --- a/src/controllers/OrganisationsController.js +++ b/src/controllers/OrganisationsController.js @@ -96,7 +96,7 @@ const organisationsController = { res.render('organisations/find.html', { alphabetisedOrgs }) } catch (err) { - logger.error(err) + logger.warn(err) next(err) } }