diff --git a/tests/javascripts/templateFolderForm.test.js b/tests/javascripts/templateFolderForm.test.js new file mode 100644 index 000000000..d04be551c --- /dev/null +++ b/tests/javascripts/templateFolderForm.test.js @@ -0,0 +1,645 @@ +const helpers = require('./support/helpers'); + +function setFixtures (hierarchy) { + + function templatesAndFoldersCheckboxesHTML () { + let result = ''; + + hierarchy.forEach((node, idx) => { + + result += ` +
+
+ + +
+

+ + ${node.label} + +

+ ${node.meta} +
`; + + }); + + return result; + + }; + + const foldersCheckboxesHTML = function (filter) { + let count = 0; + + // use closure to give all calls access to count + return function (nodes) { + let result = ''; + + nodes + .filter(node => node.type === 'folder') + .forEach(node => { + result += `
  • + + + ${node.children ? foldersCheckboxesHTML(node.children) : ''} +
  • `; + count++; + }); + + return ``; + }; + + }(); + + function controlsHTML () { + + return `
    + +
    +
    +
    +
    + + Choose a folder + +
    + ${foldersCheckboxesHTML(hierarchy)} +
    +
    +
    +
    +
    + +
    +
    +
    +
    + Add to new folder +
    + + +
    + +
    +
    +
    +
    + Add new folder +
    + + +
    + +
    +
    +
    +
    +
    +
    + + New template + +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    + Nothing selected +
    +
    ` + }; + + document.body.innerHTML = ` +
    + ${templatesAndFoldersCheckboxesHTML()} + ${controlsHTML()} +
    `; + +}; + +beforeAll(() => { + require('../../app/assets/javascripts/templateFolderForm.js'); +}); + +afterAll(() => { + require('./support/teardown.js'); +}); + +describe('TemplateFolderForm', () => { + + const hierarchy = [ + { + 'label': 'Folder 1', + 'type': 'folder', + 'meta': '1 template, 1 folder', + 'children': [ + { + 'label': 'Template 3', + 'type': 'template', + 'meta': 'Email template' + }, + { + 'label': 'Folder 2', + 'type': 'folder', + 'meta': 'Empty', + 'children': [] + } + ] + }, + { + 'label': 'Template 1', + 'type': 'Email template', + 'meta': 'Email template' + }, + { + 'label': 'Template 2', + 'type': 'template', + 'meta': 'Email template' + } + ]; + + let templateFolderForm; + let formControls; + let visibleCounter; + let hiddenCounter; + + beforeAll(() => { + + // stub out calls to sticky JS + GOVUK.stickAtBottomWhenScrolling = { + setMode: jest.fn(), + recalculate: jest.fn() + }; + + }); + + afterAll(() => { + + GOVUK.stickAtBottomWhenScrolling = undefined; + + }); + + beforeEach(() => { + + setFixtures(hierarchy); + + templateFolderForm = document.querySelector('form[data-module=template-folder-form]'); + + }); + + afterEach(() => { + + document.body.innerHTML = ''; + + }); + + function getTemplateFolderCheckboxes () { + return templateFolderForm.querySelectorAll('input[type=checkbox]'); + }; + + function getVisibleCounter () { + return formControls.querySelector('.template-list-selected-counter__count'); + }; + + function getHiddenCounter () { + return formControls.querySelector('[role=status]'); + }; + + describe("When the page loads", () => { + + beforeEach(() => { + + // start module + window.GOVUK.modules.start(); + + formControls = templateFolderForm.querySelector('#sticky_template_forms'); + visibleCounter = getVisibleCounter(); + + }); + + test("the default controls and the counter should be showing", () => { + + expect(document.querySelector('button[value=add-new-template]')).not.toBeNull(); + expect(document.querySelector('button[value=add-new-folder]')).not.toBeNull(); + expect(visibleCounter).not.toBeNull(); + + }); + + // Our counter needs to be wrapped in an ARIA live region so changes to its content are + // communicated to assistive tech'. + // ARIA live regions need to be in the HTML before JS loads. + // Because of this, we have a counter, in a live region, in the page when it loads, and + // a duplicate, visible, one in the HTML the module adds to the page. + // We hide the one in the live region to avoid duplication of it's content. + describe("Selection counter", () => { + + beforeEach(() => { + + hiddenCounter = getHiddenCounter(); + + }) + + test("the visible counter should be hidden from assistive tech", () => { + + expect(visibleCounter.getAttribute('aria-hidden')).toEqual("true"); + + }); + + test("the content of both visible and hidden counters should match", () => { + + expect(visibleCounter.textContent.trim()).toEqual(hiddenCounter.textContent.trim()); + + }); + + }); + + }); + + describe("Clicking 'New template'", () => { + + beforeEach(() => { + + // start module + window.GOVUK.modules.start(); + + formControls = templateFolderForm.querySelector('#sticky_template_forms'); + + helpers.triggerEvent(formControls.querySelector('[value=add-new-template]'), 'click'); + + }); + + test("should show options for all the types of template", () => { + + const options = [ + 'Email', 'Text message', 'Letter', 'Copy an existing template' + ]; + + const labels = Array.from(formControls.querySelectorAll('label')); + const radios = Array.from(formControls.querySelectorAll('input[type=radio]')); + + options.forEach(option => { + let matchingLabels = labels.filter(label => label.textContent.trim() === option); + + expect(matchingLabels.length > 0).toBe(true); + + let matchingRadio = formControls.querySelector(`#${matchingLabels[0].getAttribute('for')}`) + + expect(matchingRadio).not.toBeNull(); + });; + + }); + + test("should show a 'Cancel' link", () => { + + const cancelLink = formControls.querySelector('.js-cancel'); + + expect(cancelLink).not.toBeNull(); + + }); + + test("should focus the fieldset", () => { + + expect(document.activeElement).toBe(formControls.querySelector('fieldset')); + + }); + + describe("When the 'Cancel' link is clicked", () => { + + let addNewTemplateButton; + + beforeEach(() => { + + helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click'); + + addNewTemplateButton = formControls.querySelector('[value=add-new-template]'); + + }); + + test("the controls should reset", () => { + + expect(addNewTemplateButton).not.toBeNull(); + + }); + + test("the add new template control should be focused", () => { + + expect(document.activeElement).toBe(addNewTemplateButton); + + }); + + }); + + }); + + describe("Clicking 'New folder'", () => { + + let textbox; + + beforeEach(() => { + + // start module + window.GOVUK.modules.start(); + + formControls = templateFolderForm.querySelector('#sticky_template_forms'); + + helpers.triggerEvent(formControls.querySelector('[value=add-new-folder]'), 'click'); + + textbox = formControls.querySelector('input[type=text]'); + + }); + + test("should show a textbox for the folder name", () => { + + expect(textbox).not.toBeNull(); + + // check textbox has a label + expect(formControls.querySelector(`label[for=${textbox.getAttribute('id')}]`)).not.toBeNull(); + + }); + + test("should focus the textbox", () => { + + expect(document.activeElement).toBe(textbox); + + }); + + describe("When the 'Cancel' link is clicked", () => { + + let addNewFolderButton; + + beforeEach(() => { + + helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click'); + + addNewFolderButton = formControls.querySelector('button[value=add-new-folder]'); + + }); + + test("the controls should reset", () => { + + expect(addNewFolderButton).not.toBeNull(); + + }); + + test("the control for adding a new folder should be focused", () => { + + expect(document.activeElement).toBe(addNewFolderButton); + + }); + + }); + + }); + + describe("When some templates/folders are selected", () => { + + let templateFolderCheckboxes; + + beforeEach(() => { + + // start module + window.GOVUK.modules.start(); + + templateFolderCheckboxes = getTemplateFolderCheckboxes(); + + formControls = templateFolderForm.querySelector('#sticky_template_forms'); + + helpers.triggerEvent(templateFolderCheckboxes[0], 'click'); + helpers.triggerEvent(templateFolderCheckboxes[2], 'click'); + + }); + + test("the buttons for moving to a new or existing folder are showing", () => { + + expect(formControls.querySelector('button[value=move-to-new-folder]')).not.toBeNull(); + expect(formControls.querySelector('button[value=move-to-existing-folder]')).not.toBeNull(); + + }); + + describe("Clear link", () => { + + let clearLink; + + beforeEach(() => { + + clearLink = formControls.querySelector('.js-cancel'); + + }); + + test("the link has been added with the right text", () => { + + expect(clearLink).not.toBeNull(); + expect(clearLink.textContent.trim()).toEqual('Clear selection'); + + }); + + test("clicking the link clears the selection", () => { + + helpers.triggerEvent(clearLink, 'click'); + + const checkedCheckboxes = Array.from(templateFolderCheckboxes).filter(checkbox => checkbox.checked); + + expect(checkedCheckboxes.length === 0).toBe(true); + + }); + + }); + + describe("Selection counter", () => { + + let visibleCounterText; + let hiddenCounterText; + + beforeEach(() => { + + visibleCounterText = getVisibleCounter().textContent.trim(); + hiddenCounterText = getHiddenCounter().textContent.trim(); + + }); + + test("the content of both visible and hidden counters should match", () => { + + expect(visibleCounterText).toEqual(hiddenCounterText); + + }); + + test("the content of the counter should reflect the selection", () => { + + expect(visibleCounterText).toEqual('2 selected'); + + }); + + }); + + describe("Clicking the 'Move' button", () => { + + beforeEach(() => { + + helpers.triggerEvent(formControls.querySelector('[value=move-to-existing-folder]'), 'click'); + + }); + + test("should show radios for all the folders in the hierarchy", () => { + + const foldersInHierarchy = []; + + function getFolders (nodes) { + + nodes.forEach(node => { + if (node.type === 'folder') { + + foldersInHierarchy.push(node.label); + if (node.children.length) { getFolders(node.children) } + + } + }); + + }; + + getFolders(hierarchy); + + const folderLabels = Array.from(formControls.querySelectorAll('#move_to label')) + .filter(label => label.textContent.trim() !== 'Templates'); + + expect(folderLabels.map(label => label.textContent.trim())).toEqual(foldersInHierarchy); + + const radiosForLabels = folderLabels + .map(label => formControls.querySelector(`#${label.getAttribute('for')}`)) + .filter(radio => radio !== null); + + expect(radiosForLabels.length).toEqual(foldersInHierarchy.length); + + }); + + test("focus the 'Choose a folder' fieldset", () => { + + expect(document.activeElement).toBe(formControls.querySelector('#move_to')); + + }); + + describe("When the 'Cancel' link is clicked", () => { + + let moveToFolderButton; + + beforeEach(() => { + + helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click'); + + moveToFolderButton = formControls.querySelector('button[value=move-to-existing-folder]'); + + }); + + test("the controls should reset", () => { + + expect(moveToFolderButton).not.toBeNull(); + + }); + + test("the control for moving to an existing folder should be focused", () => { + + expect(document.activeElement).toBe(moveToFolderButton); + + }); + + }); + + }); + + describe("Clicking the 'Add to new folder' button", () => { + + let textbox; + + beforeEach(() => { + + helpers.triggerEvent(formControls.querySelector('[value=move-to-new-folder]'), 'click'); + + textbox = formControls.querySelector('input[type=text]'); + + }); + + test("should show a textbox for the folder name", () => { + + expect(textbox).not.toBeNull(); + + // check textbox has a label + expect(formControls.querySelector(`label[for=${textbox.getAttribute('id')}]`)).not.toBeNull(); + + }); + + test("should focus the textbox", () => { + + expect(document.activeElement).toBe(textbox); + + }); + + describe("When the 'Cancel' link is clicked", () => { + + let moveToNewFolderButton; + + beforeEach(() => { + + helpers.triggerEvent(formControls.querySelector('.js-cancel'), 'click'); + + moveToNewFolderButton = formControls.querySelector('button[value=move-to-new-folder]'); + + }); + + test("the controls should reset", () => { + + expect(moveToNewFolderButton).not.toBeNull(); + + }); + + test("the control for adding a new folder should be focused", () => { + + expect(document.activeElement).toBe(moveToNewFolderButton); + + }); + + }); + + }); + + }); + +});