From bbb1ca33e96df12979078e108f305d562893af69 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Mon, 2 Sep 2019 09:47:18 +0100 Subject: [PATCH 01/50] Add utility helper for making form data sendable Useful for assertions where the data you're comparing is already in this format. --- tests/javascripts/support/helpers.js | 2 ++ .../javascripts/support/helpers/utilities.js | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/javascripts/support/helpers/utilities.js diff --git a/tests/javascripts/support/helpers.js b/tests/javascripts/support/helpers.js index fe5adf08e..7fef27e98 100644 --- a/tests/javascripts/support/helpers.js +++ b/tests/javascripts/support/helpers.js @@ -4,6 +4,7 @@ const html = require('./helpers/html.js'); const elements = require('./helpers/elements.js'); const rendering = require('./helpers/rendering.js'); const forms = require('./helpers/forms.js'); +const utilities = require('./helpers/utilities.js'); exports.triggerEvent = events.triggerEvent; exports.clickElementWithMouse = events.clickElementWithMouse; @@ -18,3 +19,4 @@ exports.element = elements.element; exports.WindowMock = rendering.WindowMock; exports.ScreenMock = rendering.ScreenMock; exports.spyOnFormSubmit = forms.spyOnFormSubmit; +exports.getFormDataFromPairs = utilities.getFormDataFromPairs; diff --git a/tests/javascripts/support/helpers/utilities.js b/tests/javascripts/support/helpers/utilities.js new file mode 100644 index 000000000..9ec0f8015 --- /dev/null +++ b/tests/javascripts/support/helpers/utilities.js @@ -0,0 +1,22 @@ +// general helpers, not related to the DOM and usable in different contexts + +// turn a list of key=value pairs (like tuples) into data that can be sent via AJAX +// taken from https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript +// but requiring an array as input rather than a hash, to preserve order of pairs +function getFormDataFromPairs (pairs) { + + const urlEncodedDataPairs = []; + + pairs.forEach(pair => { + + urlEncodedDataPairs.push(`${window.encodeURIComponent(pair[0])}=${window.encodeURIComponent(pair[1])}`); + + }); + + // Combine the pairs into a single string and replace all %-encoded spaces to + // the '+' character; matches the behaviour of browser form submissions. + return urlEncodedDataPairs.join('&').replace(/%20/g, '+'); + +}; + +exports.getFormDataFromPairs = getFormDataFromPairs; From a0d39496b9eff59658f20d5a3e8f6ff80f27167c Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Mon, 2 Sep 2019 14:24:02 +0100 Subject: [PATCH 02/50] Make global explicit in module scope This is mainly because tests don't inferr that global variables are just properties of the window object, as browsers do, but it also makes this more explicit. --- app/assets/javascripts/updateContent.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/updateContent.js b/app/assets/javascripts/updateContent.js index 204e1c9ce..6297e3854 100644 --- a/app/assets/javascripts/updateContent.js +++ b/app/assets/javascripts/updateContent.js @@ -1,8 +1,8 @@ -(function(Modules) { +(function(global) { "use strict"; var queues = {}; - var dd = new diffDOM(); + var dd = new global.diffDOM(); var getRenderer = $component => response => dd.apply( $component.get(0), @@ -43,7 +43,7 @@ ); }; - Modules.UpdateContent = function() { + global.GOVUK.Modules.UpdateContent = function() { this.start = component => poll( getRenderer($(component)), @@ -55,4 +55,4 @@ }; -})(window.GOVUK.Modules); +})(window); From c241d1eb5eb445bea234f05a5b194d264c030865 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Mon, 2 Sep 2019 14:27:46 +0100 Subject: [PATCH 03/50] Add tests for updateContent module --- tests/javascripts/updateContent.test.js | 223 ++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 tests/javascripts/updateContent.test.js diff --git a/tests/javascripts/updateContent.test.js b/tests/javascripts/updateContent.test.js new file mode 100644 index 000000000..fdc620b74 --- /dev/null +++ b/tests/javascripts/updateContent.test.js @@ -0,0 +1,223 @@ +const helpers = require('./support/helpers.js'); + +const serviceNumber = '6658542f-0cad-491f-bec8-ab8457700ead'; +const resourceURL = `/services/${serviceNumber}/notifications/email.json?status=sending%2Cdelivered%2Cfailed`; +const updateKey = 'counts'; + +let responseObj = {}; +let jqueryAJAXReturnObj; + +beforeAll(() => { + + // ensure all timers go through Jest + jest.useFakeTimers(); + + // mock the bits of jQuery used + jest.spyOn(window.$, 'ajax'); + + // set up the object returned from $.ajax so it responds with whatever responseObj is set to + jqueryAJAXReturnObj = { + done: callback => { + callback(responseObj); + return jqueryAJAXReturnObj; + }, + fail: () => {} + }; + + $.ajax.mockImplementation(() => jqueryAJAXReturnObj); + + // because we're running in node, diffDOM executes as a module + // in the normal browser environment it will attach to window so we replicate that here + window.diffDOM = require('../../node_modules/diff-dom/diffDOM.js'); + require('../../app/assets/javascripts/updateContent.js'); + +}); + +afterAll(() => { + require('./support/teardown.js'); +}); + +describe('Update content', () => { + + beforeEach(() => { + + // store HTML in string to allow use in AJAX responses + HTMLString = ` +
+
+ +
+
`; + + document.body.innerHTML = HTMLString; + + // default the response to match the existing content + responseObj[updateKey] = HTMLString; + + }); + + afterEach(() => { + + document.body.innerHTML = ''; + + // tidy up record of mocked AJAX calls + $.ajax.mockClear(); + + // ensure any timers set by continually starting the module are cleared + jest.clearAllTimers(); + + }); + + test("It should make requests to the URL specified in the data-resource attribute", () => { + + // start the module + window.GOVUK.modules.start(); + + expect($.ajax.mock.calls[0][0]).toEqual(resourceURL); + + }); + + test("If the response contains no changes, the DOM should stay the same", () => { + + // send the done callback a response with updates included + responseObj[updateKey] = HTMLString; + + // start the module + window.GOVUK.modules.start(); + + // check the right DOM node is updated + expect(document.querySelectorAll('.big-number-number')[0].textContent.trim()).toEqual("0"); + + }); + + test("If the response contains changes, it should update the DOM with them", () => { + + // send the done callback a response with updates included + responseObj[updateKey] = HTMLString.replace(/
0<\/div>{1}/, '
1
'); + + // start the module + window.GOVUK.modules.start(); + + // check the right DOM node is updated + expect(document.querySelectorAll('.big-number-number')[0].textContent.trim()).toEqual("1"); + + }); + + test("If an interval between requests is specified, using the data-interval-seconds attribute, requests should happen at that frequency", () => { + + document.querySelector('[data-module=update-content]').setAttribute('data-interval-seconds', '0.5'); + + // start the module + window.GOVUK.modules.start(); + + expect($.ajax).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(500); + jest.advanceTimersByTime(500); + jest.advanceTimersByTime(500); + + expect($.ajax).toHaveBeenCalledTimes(4); + + }); + + describe("By default", () => { + + beforeEach(() => { + + // start the module + window.GOVUK.modules.start(); + + }); + + test("It should use the GET HTTP method", () => { + + expect($.ajax.mock.calls[0][1].method).toEqual('get'); + + }); + + test("It shouldn't send any data as part of the requests", () => { + + expect($.ajax.mock.calls[0][1].data).toEqual({}); + + }); + + test("It should request updates every 1.5 seconds", () => { + + expect($.ajax).toHaveBeenCalledTimes(1); + + jest.advanceTimersByTime(1500); + + expect($.ajax).toHaveBeenCalledTimes(2); + + }); + + }); + + describe("If a form is used as a source for data, referenced in the data-form attribute", () => { + + beforeEach(() => { + + document.body.innerHTML += ` +
+ + +
`; + + document.querySelector('[data-module=update-content]').setAttribute('data-form', 'service'); + + // start the module + window.GOVUK.modules.start(); + + }); + + test("requests should use the same HTTP method as the form", () => { + + expect($.ajax.mock.calls[0][1].method).toEqual('post'); + + }) + + test("requests should use the data from the form", () => { + + expect($.ajax.mock.calls[0][1].data).toEqual(helpers.getFormDataFromPairs([ + ['serviceName', 'Buckhurst surgery'], + ['serviceNumber', serviceNumber] + ])); + + }) + + }); + +}); From 5df4864743178c8fdebb0465fa0ff73a864041df Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 5 Sep 2019 09:43:18 +0100 Subject: [PATCH 04/50] Add note about `advanceTimersByTime` units --- tests/javascripts/updateContent.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/javascripts/updateContent.test.js b/tests/javascripts/updateContent.test.js index fdc620b74..0ea2d8d2d 100644 --- a/tests/javascripts/updateContent.test.js +++ b/tests/javascripts/updateContent.test.js @@ -145,6 +145,7 @@ describe('Update content', () => { expect($.ajax).toHaveBeenCalledTimes(1); + // units are milliseconds jest.advanceTimersByTime(500); jest.advanceTimersByTime(500); jest.advanceTimersByTime(500); @@ -178,6 +179,7 @@ describe('Update content', () => { expect($.ajax).toHaveBeenCalledTimes(1); + // units are milliseconds jest.advanceTimersByTime(1500); expect($.ajax).toHaveBeenCalledTimes(2); From 2fdf8161d2fef9407b7f408dd4234727b23cc668 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Wed, 28 Aug 2019 11:06:27 +0100 Subject: [PATCH 05/50] Fix radios helpers They were using a 'name' property which wasn't being set in the data. Radios share the same name attribute so they can get it from the parent group. Also includes fixes for tests where the original API is used. --- .../stick-to-window-when-scrolling.test.js | 12 ++++++------ tests/javascripts/support/helpers/html.js | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/javascripts/stick-to-window-when-scrolling.test.js b/tests/javascripts/stick-to-window-when-scrolling.test.js index fec68663f..32bacaf9f 100644 --- a/tests/javascripts/stick-to-window-when-scrolling.test.js +++ b/tests/javascripts/stick-to-window-when-scrolling.test.js @@ -472,8 +472,8 @@ describe("Stick to top/bottom of window when scrolling", () => { // add another sticky element before the form footer radios = helpers.getRadioGroup({ cssClasses: ['js-stick-at-top-when-scrolling'], - name: 'send-time', - label: 'send time', + name: 'choose-send-time', + label: 'Choose send time', fields: [ { label: 'Now', @@ -604,7 +604,7 @@ describe("Stick to top/bottom of window when scrolling", () => { } }); - radios.querySelector('fieldset').insertAdjacentHTML('beforeend', helpers.getRadios(fields)); + radios.querySelector('fieldset').insertAdjacentHTML('beforeend', helpers.getRadios(fields, 'days')); radios.offsetHeight = 475; @@ -1146,8 +1146,8 @@ describe("Stick to top/bottom of window when scrolling", () => { // add another sticky element before the form footer radios = helpers.getRadioGroup({ cssClasses: ['js-stick-at-bottom-when-scrolling'], - name: 'send-time', - label: 'Send time', + name: 'choose-send-time', + label: 'Choose send time', fields: [ { label: 'Now', @@ -1280,7 +1280,7 @@ describe("Stick to top/bottom of window when scrolling", () => { } }); - radios.querySelector('fieldset').insertAdjacentHTML('beforeend', helpers.getRadios(fields)); + radios.querySelector('fieldset').insertAdjacentHTML('beforeend', helpers.getRadios(fields, 'days')); radios.offsetHeight = 475; diff --git a/tests/javascripts/support/helpers/html.js b/tests/javascripts/support/helpers/html.js index 7b3d78eb4..e0b8aa22e 100644 --- a/tests/javascripts/support/helpers/html.js +++ b/tests/javascripts/support/helpers/html.js @@ -1,6 +1,6 @@ // helpers for generating patterns of HTML -function getRadios (fields) { +function getRadios (fields, name) { const result = ''; return fields.map((field, idx) => { @@ -8,8 +8,8 @@ function getRadios (fields) { return `
- -
`; @@ -22,11 +22,11 @@ function getRadioGroup (data) { data.cssClasses.forEach(cssClass => radioGroup.classList.add(cssClass)); radioGroup.innerHTML = `
-
+
- Choose ${data.label} + ${data.label} - ${getRadios(data.fields)} + ${getRadios(data.fields, data.name)}
`; From a67d1901c052fd3edcb0d44a7d9d5df7f296390e Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 29 Aug 2019 10:58:27 +0100 Subject: [PATCH 06/50] Add mock for window.location --- tests/javascripts/support/helpers.js | 2 + tests/javascripts/support/helpers/globals.js | 89 ++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 tests/javascripts/support/helpers/globals.js diff --git a/tests/javascripts/support/helpers.js b/tests/javascripts/support/helpers.js index fe5adf08e..c86772626 100644 --- a/tests/javascripts/support/helpers.js +++ b/tests/javascripts/support/helpers.js @@ -1,3 +1,4 @@ +const globals = require('./helpers/globals.js'); const events = require('./helpers/events.js'); const domInterfaces = require('./helpers/dom_interfaces.js'); const html = require('./helpers/html.js'); @@ -5,6 +6,7 @@ const elements = require('./helpers/elements.js'); const rendering = require('./helpers/rendering.js'); const forms = require('./helpers/forms.js'); +exports.LocationMock = globals.LocationMock; exports.triggerEvent = events.triggerEvent; exports.clickElementWithMouse = events.clickElementWithMouse; exports.moveSelectionToRadio = events.moveSelectionToRadio; diff --git a/tests/javascripts/support/helpers/globals.js b/tests/javascripts/support/helpers/globals.js new file mode 100644 index 000000000..89fa44e08 --- /dev/null +++ b/tests/javascripts/support/helpers/globals.js @@ -0,0 +1,89 @@ +// helpers for mocking objects attached to the global space as properties, ie. window.location + +class LocationMock { + + constructor (URL) { + + this._location = window.location; + + // setting href sets all sub-properties + this.href = URL; + + // JSDOM sets window.location as non-configurable + // the only way to mock it, currently, is to replace it completely + delete window.location; + window.location = this; + + } + + get href () { + + return `${this.protocol}://${this.host}${this.pathname}${this.search}${this.hash}` + + } + + set href (value) { + + const partNames = ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash']; + + const protocol = '(https:|http:)'; + const hostname = '[^\\/]+'; + const port = '(:\\d)'; + const pathname = '([^?]+)'; + const search = '([^#])'; + const hash = '(#[\\x00-\\x7F])'; // match any ASCII character + + const re = new RegExp(`^${protocol}{0,1}(?:\\/\\/){0,1}(${hostname}${port}{0,1}){0,1}${pathname}{0,1}${search}{0,1}${hash}{0,1}$`); + const match = value.match(re) + + if (match === null) { throw Error(`${value} is not a valid URL`); } + + match.forEach((part, idx) => { + + let partName; + + // 0 index is whole match, we want the groups + if (idx > 0) { + partName = partNames[idx - 1]; + + if (part !== undefined) { + this[partName] = part; + } else if (!(partName in this)) { // only get value from window.location if property not set + this[partName] = this._location[partName]; + } + } + + }); + + } + + get host () { + + return `${this.hostname}:${this.port}`; + + } + + set host (value) { + + const parts = value.split(':'); + + this.hostname = parts[0]; + this.protocol = parts[1]; + + } + + // origin is read-only + get origin () { + + return `${this.protol}://${this.hostname}`; + + } + + reset () { + + window.location = this._location; + + } +} + +exports.LocationMock = LocationMock; From af2be185b9bea891856a694ddec3f5808d51be74 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 29 Aug 2019 12:23:34 +0100 Subject: [PATCH 07/50] Make window as global explicit in previewPane.js --- app/assets/javascripts/previewPane.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/previewPane.js b/app/assets/javascripts/previewPane.js index 7a8172861..514b02274 100644 --- a/app/assets/javascripts/previewPane.js +++ b/app/assets/javascripts/previewPane.js @@ -1,9 +1,8 @@ -(function () { +(function (global) { 'use strict'; - const root = this, - $ = this.jQuery; + $ = global.jQuery; let branding_style = $('.multiple-choice input[name="branding_style"]:checked'); @@ -34,4 +33,4 @@ $form.find('button[type="submit"]').text('Save'); $('fieldset').on('change', 'input[name="branding_style"]', setPreviewPane); -})(); +})(window); From 125243addc10668a473669329cd780f56a9941be Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Thu, 29 Aug 2019 12:24:42 +0100 Subject: [PATCH 08/50] Add tests for preview pane JS --- tests/javascripts/previewPane.test.js | 234 ++++++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 tests/javascripts/previewPane.test.js diff --git a/tests/javascripts/previewPane.test.js b/tests/javascripts/previewPane.test.js new file mode 100644 index 000000000..dd25031dc --- /dev/null +++ b/tests/javascripts/previewPane.test.js @@ -0,0 +1,234 @@ +const helpers = require('./support/helpers.js'); + +const emailPageURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/set-email-branding'; +const emailPreviewConfirmationURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/preview-email-branding'; +const letterPageURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/set-letter-branding'; +const letterPreviewConfirmationURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/preview-letter-branding'; + +let locationMock; + +beforeAll(() => { + + // mock calls to window.location + // default to the email page, the pathname can be changed inside specific tests + locationMock = new helpers.LocationMock(emailPageURL); + +}); + +afterAll(() => { + + // reset window.location to its original state + locationMock.reset(); + require('./support/teardown.js'); + +}); + +describe('Preview pane', () => { + + let form; + let radios; + + beforeEach(() => { + + const brands = { + "name": "branding_style", + "label": "Branding style", + "cssClasses": [], + "fields": [ + { + "label": "Department for Education", + "value": "dfe", + "checked": true + }, + { + "label": "Home Office", + "value": "ho", + "checked": false + }, + { + "label": "Her Majesty's Revenue and Customs", + "value": "hmrc", + "checked": false + }, + { + "label": "Department for Work and Pensions", + "value": "dwp", + "checked": false + } + ] + }; + + // set up DOM + document.body.innerHTML = + `
+
+
+
+
+ +
+
+
+ +
`; + + document.querySelector('.column-full').appendChild(helpers.getRadioGroup(brands)); + form = document.querySelector('form'); + radios = form.querySelector('fieldset'); + + }); + + afterEach(() => { + + document.body.innerHTML = ''; + + // we run the previewPane.js script every test + // the module cache needs resetting each time for the script to execute + jest.resetModules(); + + }); + + describe("If the page type is 'email'", () => { + + describe("When the page loads", () => { + + test("it should add the preview pane", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + expect(document.querySelector('iframe')).not.toBeNull(); + + }); + + test("it should change the form to submit the selection instead of posting to a preview page", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + expect(form.getAttribute('action')).toEqual(emailPreviewConfirmationURL); + + }); + + test("the preview pane should show the page for the selected brand", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + const selectedValue = Array.from(radios.querySelectorAll('input[type=radio]')).filter(radio => radio.checked)[0].value; + + expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_email?branding_style=${selectedValue}`); + + }); + + test("the submit button should change from 'Preview' to 'Save'", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + expect(document.querySelector('button[type=submit]').textContent).toEqual('Save'); + + }); + + }); + + describe("If the selection changes", () => { + + test("the page shown should match the selected brand", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + const newSelection = radios.querySelectorAll('input[type=radio]')[1]; + + helpers.moveSelectionToRadio(newSelection); + + expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_email?branding_style=${newSelection.value}`); + + }); + + }); + + }); + + describe("If the page type is 'letter'", () => { + + beforeEach(() => { + + // set page URL and page type to 'letter' + window.location.pathname = letterPreviewConfirmationURL; + form.setAttribute('data-preview-type', 'letter'); + + }); + + describe("When the page loads", () => { + + test("it should add the preview pane", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + expect(document.querySelector('iframe')).not.toBeNull(); + + }); + + test("it should change the form to submit the selection instead of posting to a preview page", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + expect(form.getAttribute('action')).toEqual(letterPreviewConfirmationURL); + + }); + + test("the preview pane should show the page for the selected brand", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + const selectedValue = Array.from(radios.querySelectorAll('input[type=radio]')).filter(radio => radio.checked)[0].value; + + expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_letter?branding_style=${selectedValue}`); + + }); + + test("the submit button should change from 'Preview' to 'Save'", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + expect(document.querySelector('button[type=submit]').textContent).toEqual('Save'); + + }); + + }); + + describe("If the selection changes", () => { + + test("the page shown should match the selected brand", () => { + + // run preview pane script + require('../../app/assets/javascripts/previewPane.js'); + + const newSelection = radios.querySelectorAll('input[type=radio]')[1]; + + helpers.moveSelectionToRadio(newSelection); + + expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_letter?branding_style=${newSelection.value}`); + + }); + + }); + + }); + +}); From 2b3bfc109a290e4575c2ec93e6019bfbcb194691 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Tue, 20 Aug 2019 15:59:24 +0100 Subject: [PATCH 09/50] Add tests for live-search module --- tests/javascripts/liveSearch.test.js | 680 +++++++++++++++++++++++++++ 1 file changed, 680 insertions(+) create mode 100644 tests/javascripts/liveSearch.test.js diff --git a/tests/javascripts/liveSearch.test.js b/tests/javascripts/liveSearch.test.js new file mode 100644 index 000000000..4343f68b8 --- /dev/null +++ b/tests/javascripts/liveSearch.test.js @@ -0,0 +1,680 @@ +const helpers = require('./support/helpers.js'); + +beforeAll(() => { + require('../../app/assets/javascripts/liveSearch.js'); +}); + +afterAll(() => { + require('./support/teardown.js'); +}); + +describe('Live search', () => { + + let searchTextbox; + let list; + + describe("With a list of radios", () => { + + function getRadiosHTML (departments) { + + let result = ''; + + departments.forEach((department, idx) => result += ` +
+ + +
+ `); + + return result; + + }; + + beforeEach(() => { + + const departments = [ + { + 'label': 'NHS', + 'id': 'nhs', + 'name': 'branding' + }, + { + 'label': 'Department for Work and Pensions', + 'id': 'dwp', + 'name': 'branding' + }, + { + 'label': 'Department for Education', + 'id': 'dfe', + 'name': 'branding' + }, + { + 'label': 'Home Office', + 'id': 'home-office', + 'name': 'branding' + } + ]; + + // set up DOM + document.body.innerHTML = ` + +
+ ${getRadiosHTML(departments)} +
`; + + searchTextbox = document.getElementById('search'); + list = document.querySelector('form'); + + }); + + describe("When the page loads", () => { + + test("If there is no search term, the results should be unchanged", () => { + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.multiple-choice'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(listItems.length); + + }); + + test("If there is a single word search term, only the results that match should show", () => { + + searchTextbox.value = 'Department'; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.multiple-choice'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(2); + + }); + + test("If there is a search term made of several words, only the results that match should show", () => { + + searchTextbox.value = 'Department for Work'; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.multiple-choice'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If an item doesn't match the search term but is selected, it should still show in the results", () => { + + searchTextbox.value = 'Department for Work'; + + // mark an item as selected + checkedItem = list.querySelector('input[id=nhs]'); + checkedItem.checked = true; + + // start the module + window.GOVUK.modules.start(); + + expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); + + }); + + }); + + describe("When the search text changes", () => { + + test("If there is no search term, the results should be unchanged", () => { + + searchTextbox.value = 'Department'; + + // start the module + window.GOVUK.modules.start(); + + // simulate the input of new search text + searchTextbox.value = ''; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.multiple-choice'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(listItems.length); + + }); + + test("If there is a single word search term, only the results that match should show", () => { + + searchTextbox.value = 'Department'; + + // start the module + window.GOVUK.modules.start(); + + // simulate the input of new search text + searchTextbox.value = 'Home'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.multiple-choice'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If there is a search term made of several words, only the results that match should show", () => { + + searchTextbox.value = 'Department'; + + // start the module + window.GOVUK.modules.start(); + + // simulate the input of new search text + searchTextbox.value = 'Department for'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.multiple-choice'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(2); + + }); + + test("If an item doesn't match the search term but is selected, it should still show in the results", () => { + + searchTextbox.value = 'Department'; + + // mark an item as selected + checkedItem = list.querySelector('input[id=nhs]'); + checkedItem.checked = true; + + // start the module + window.GOVUK.modules.start(); + + // simulate the input of new search text + searchTextbox.value = 'Home Office'; + helpers.triggerEvent(searchTextbox, 'input'); + + expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); + + }); + + }); + + }); + + describe("With a list of checkboxes", () => { + + beforeEach(() => { + + const templatesAndFolders = [ + { + "label": "Appointments", + "type": "folder", + "meta": "2 templates" + }, + { + "label": "New patient", + "type": "template", + "meta": "Email template" + }, + { + "label": "Prescriptions", + "type": "folder", + "meta": "1 template, 1 folder" + }, + { + "label": "New doctor", + "type": "template", + "meta": "Email template" + } + ]; + + // set up DOM + document.body.innerHTML = ` + +
+ +
`; + + searchTextbox = document.getElementById('search'); + list = document.querySelector('form'); + + }); + + describe("When the page loads", () => { + + test("If there is no search term, the results should be unchanged", () => { + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(listItems.length); + + }); + + test("If there is a single word search term, only the results that match should show", () => { + + searchTextbox.value = 'New'; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + // should match 'New patient' and 'New doctor' + expect(listItemsShowing.length).toEqual(2); + + }); + + test("If there is a search term made of several words, only the results that match should show", () => { + + searchTextbox.value = 'New patient'; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If an item doesn't match the search term but is selected, it should still show in the results", () => { + + searchTextbox.value = 'New patient'; + + // mark 'Appointments' item as selected + checkedItem = list.querySelector('input[id=templates-or-folder-0]'); + checkedItem.checked = true; + + // start the module + window.GOVUK.modules.start(); + + // should show despite not matching + expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); + + }); + + test("If the items have a block of text to match against, only results that match it should show", () => { + + searchTextbox.value = 'Email template'; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + // 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); + + }); + + }); + + describe("When the search text changes", () => { + + test("If there is no search term, the results should be unchanged", () => { + + searchTextbox.value = 'Appointments'; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = ''; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(listItems.length); + + }); + + test("If there is a single word search term, only the results that match should show", () => { + + searchTextbox.value = 'Appointments'; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = 'Prescriptions'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If there is a search term made of several words, only the results that match should show", () => { + + searchTextbox.value = 'Appointments'; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = 'New doctor'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If an item doesn't match the search term but is selected, it should still show in the results", () => { + + searchTextbox.value = 'Appointments'; + + // mark 'Appointments' item as selected + checkedItem = list.querySelector('input[id=templates-or-folder-0]'); + checkedItem.checked = true; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = 'Prescriptions'; + helpers.triggerEvent(searchTextbox, 'input'); + + // should show despite not matching + expect(window.getComputedStyle(checkedItem).display).not.toEqual('none'); + + }); + + test("If the items have a block of text to match against, only results that match it should show", () => { + + searchTextbox.value = 'Appointments'; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = 'Email template'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.template-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + // 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); + + }); + + }); + + }) + + describe("With a list of content items", () => { + + function getContentItems (users) { + + function getPermissionsHTML (permissions) { + + const PERMISSIONS = ["Send messages", "Add and edit templates", "Manage settings, team and usage", "API integration"]; + let permissionsHTML = ''; + + PERMISSIONS.forEach(permission => { + let can = permissions.includes(permission); + + permissionsHTML += ` +
  • + + ${can ? "Can" : "Can't"} + ${permission} + +
  • `; + + }); + + return `
      + ${permissionsHTML} +
    `; + + }; + + let result = ''; + + users.forEach(user => result += ` +
    +

    + + ${user.label} (${user.email}) (invited) + +

    +
      +
      + ${getPermissionsHTML(user.permissions)} +
      + Can see 15 folders +
      +
      + +
    +
    `); + + return result; + + }; + + beforeEach(() => { + + const users = [ + { + "label": "Template editor", + "email": "template-editor@nhs.uk", + "permissions" : ["Add and edit templates"] + }, + { + "label": "Software Developer", + "email": "software-developer@nhs.uk", + "permissions" : ["Send messages", "Add and edit templates", "team and usage", "API integration"] + }, + { + "label": "Team member", + "email": "team-member@nhs.uk", + "permissions" : ["Send messages", "Add and edit templates"] + }, + { + "label": "Administrator", + "email": "admin@nhs.uk", + "permissions" : ["Send messages", "Add and edit templates", "Manage settings, team and usage", "API integration"] + } + ]; + + // set up DOM + document.body.innerHTML = ` + +
    + ${getContentItems(users)} +
    `; + + searchTextbox = document.getElementById('search'); + list = document.querySelector('form'); + + }); + + describe("When the page loads", () => { + + test("If there is no search term, the results should be unchanged", () => { + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(listItems.length); + + }); + + test("If there is a single word search term, only the results that match should show", () => { + + searchTextbox.value = 'admin'; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If there is a search term made of several words, only the results that match should show", () => { + + searchTextbox.value = 'Administrator (admin@nhs.uk)'; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If the items have a block of text to match against, only results that match it should show", () => { + + searchTextbox.value = "Add and edit templates"; + + // start the module + window.GOVUK.modules.start(); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + // 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); + + }); + + }); + + describe("When the search text changes", () => { + + test("If there is no search term, the results should be unchanged", () => { + + searchTextbox.value = 'Admin'; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = ''; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(listItems.length); + + }); + + test("If there is a single word search term, only the results that match should show", () => { + + searchTextbox.value = 'Admin'; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = 'Administrator'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If there is a search term made of several words, only the results that match should show", () => { + + searchTextbox.value = 'Admin'; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = 'Administrator (admin@nhs.uk)'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + expect(listItemsShowing.length).toEqual(1); + + }); + + test("If the items have a block of text to match against, only results that match it should show", () => { + + searchTextbox.value = "Admin"; + + // start the module + window.GOVUK.modules.start(); + + // simulate input of new search text + searchTextbox.value = 'Add and edit templates'; + helpers.triggerEvent(searchTextbox, 'input'); + + const listItems = list.querySelectorAll('.user-list-item'); + const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none'); + + // 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); + + }); + + }); + + }); + +}); From 605f027c61a54ed5856f0af0563f8498f5b8d7e5 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Fri, 16 Aug 2019 18:32:16 +0100 Subject: [PATCH 10/50] Add tests for highlight tags module --- tests/javascripts/highlightTags.test.js | 449 ++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 tests/javascripts/highlightTags.test.js diff --git a/tests/javascripts/highlightTags.test.js b/tests/javascripts/highlightTags.test.js new file mode 100644 index 000000000..cadac0b9b --- /dev/null +++ b/tests/javascripts/highlightTags.test.js @@ -0,0 +1,449 @@ +const helpers = require('./support/helpers.js'); + +beforeAll(() => { + require('../../app/assets/javascripts/highlightTags.js'); +}); + +afterAll(() => { + require('./support/teardown.js'); +}); + +describe('Highlight tags', () => { + + let input; + let textarea; + let backgroundEl; + + beforeAll(() => { + + // set some default styling + const stylesheet = document.createElement('style'); + + stylesheet.innerHTML = ".textbox-highlight-textbox { padding: 2px; width: 576px; border-width: 1px; }"; + stylesheet.innerHTML += "textarea.textbox-highlight-textbox { height: 224px; }"; + + document.getElementsByTagName('head')[0].appendChild(stylesheet); + + }); + + afterAll(() => { + + stylesheet.parentNode.removeChild(stylesheet); + + }); + + beforeEach(() => { + + // set up DOM + document.body.innerHTML = ` +
    + + +
    +
    + + +
    `; + + input = document.querySelector('input'); + textarea = document.querySelector('textarea'); + + }); + + afterEach(() => { + + document.body.innerHTML = ''; + + }); + + describe("When the page loads", () => { + + describe("An element should be added as a layer below the textbox to hold the highlights", () => { + + beforeEach(() => { + + // start module + window.GOVUK.modules.start(); + + }); + + test("If the textbox is a