From 2fdf8161d2fef9407b7f408dd4234727b23cc668 Mon Sep 17 00:00:00 2001 From: Tom Byers Date: Wed, 28 Aug 2019 11:06:27 +0100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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}`); + + }); + + }); + + }); + +});