mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-06 03:13:42 -05:00
We removed whitespace in the HTML of the copy to clipboard component in https://github.com/alphagov/notifications-admin/pull/4236/files When the Javascript on the page loads it re-renders the component, using HTML which is embedded in the .js file. This means we also need to apply the same change to the .js file to remove any extraneous whitespace.
414 lines
13 KiB
JavaScript
414 lines
13 KiB
JavaScript
const helpers = require('./support/helpers');
|
|
|
|
afterAll(() => {
|
|
|
|
require('./support/teardown.js');
|
|
|
|
// clear up methods in the global space
|
|
document.queryCommandSupported = undefined;
|
|
|
|
});
|
|
|
|
describe('copy to clipboard', () => {
|
|
|
|
let apiKey = '00000000-0000-0000-0000-000000000000';
|
|
let thing;
|
|
let component;
|
|
let selectionMock;
|
|
let rangeMock;
|
|
|
|
const setUpDOM = function (options) {
|
|
|
|
// set up DOM
|
|
document.body.innerHTML =`
|
|
<h2 class="copy-to-clipboard__name">
|
|
${options.thing}
|
|
</h2>
|
|
<div data-module="copy-to-clipboard" data-value="${apiKey}" data-thing="${options.thing}" data-name="${options.name}">
|
|
<span class="copy-to-clipboard__value" aria-live="assertive">${(options.name === options.thing) ? '<span class="govuk-visually-hidden">' + options.thing + ': </span>' : ''}${apiKey}</span>
|
|
<span class="copy-to-clipboard__notice" aria-live="assertive"></span>
|
|
</div>`;
|
|
|
|
};
|
|
|
|
beforeEach(() => {
|
|
|
|
// mock objects used to manipulate the page selection
|
|
selectionMock = new helpers.SelectionMock(jest);
|
|
rangeMock = new helpers.RangeMock(jest);
|
|
|
|
// plug gaps in JSDOM's API for manipulation of selections
|
|
window.getSelection = jest.fn(() => selectionMock);
|
|
document.createRange = jest.fn(() => rangeMock);
|
|
|
|
// plug JSDOM not having execCommand
|
|
document.execCommand = jest.fn(() => {});
|
|
|
|
// mock sticky JS
|
|
window.GOVUK.stickAtBottomWhenScrolling = {
|
|
recalculate: jest.fn(() => {})
|
|
}
|
|
|
|
});
|
|
|
|
test("If copy command isn't available, nothing should happen", () => {
|
|
|
|
// fake support for the copy command not being available
|
|
document.queryCommandSupported = jest.fn(command => false);
|
|
|
|
require('../../app/assets/javascripts/copyToClipboard.js');
|
|
|
|
setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' });
|
|
|
|
component = document.querySelector('[data-module=copy-to-clipboard]');
|
|
|
|
// start the module
|
|
window.GOVUK.modules.start();
|
|
|
|
expect(component.querySelector('button')).toBeNull();
|
|
|
|
});
|
|
|
|
describe("If copy command is available", () => {
|
|
|
|
let componentHeightOnLoad;
|
|
|
|
beforeAll(() => {
|
|
|
|
// assume copy command is available
|
|
document.queryCommandSupported = jest.fn(command => command === 'copy');
|
|
|
|
// force module require to not come from cache
|
|
jest.resetModules();
|
|
|
|
require('../../app/assets/javascripts/copyToClipboard.js');
|
|
|
|
});
|
|
|
|
afterEach(() => {
|
|
screenMock.reset();
|
|
});
|
|
|
|
describe("On page load", () => {
|
|
|
|
describe("For all variations of the initial HTML", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' });
|
|
|
|
component = document.querySelector('[data-module=copy-to-clipboard]');
|
|
|
|
// set default style for component height (queried by jQuery before checking DOM APIs)
|
|
const stylesheet = document.createElement('style');
|
|
stylesheet.innerHTML = '[data-module=copy-to-clipboard] { height: auto; }'; // set to browser default
|
|
document.getElementsByTagName('head')[0].appendChild(stylesheet);
|
|
|
|
componentHeightOnLoad = 50;
|
|
|
|
// mock the DOM APIs called for the position & dimension of the component
|
|
screenMock = new helpers.ScreenMock(jest);
|
|
screenMock.setWindow({
|
|
width: 1990,
|
|
height: 940,
|
|
scrollTop: 0
|
|
});
|
|
screenMock.mockPositionAndDimension('component', component, {
|
|
offsetHeight: componentHeightOnLoad,
|
|
offsetWidth: 641,
|
|
offsetTop: 0
|
|
});
|
|
|
|
// start the module
|
|
window.GOVUK.modules.start();
|
|
|
|
});
|
|
|
|
test("It should add a button for copying the thing to the clipboard", () => {
|
|
|
|
expect(component.querySelector('button')).not.toBeNull();
|
|
|
|
});
|
|
|
|
test("It should add the 'copy-to-clipboard' class", () => {
|
|
|
|
expect(component.classList.contains('copy-to-clipboard')).toBe(true);
|
|
|
|
});
|
|
|
|
test("It should tell any sticky JS present the page has changed", () => {
|
|
|
|
// recalculate forces the sticky JS to recalculate any stored DOM position/dimensions
|
|
expect(window.GOVUK.stickAtBottomWhenScrolling.recalculate).toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
test("It should set the component's minimum height based on its height when the page loads", () => {
|
|
|
|
// to prevent the position of the button moving when the state changes
|
|
expect(window.getComputedStyle(component)['min-height']).toEqual(`${componentHeightOnLoad}px`);
|
|
|
|
});
|
|
|
|
test("It should render the 'thing' without extra whitespace", () => {
|
|
|
|
expect(component.querySelector('.copy-to-clipboard__value').textContent).toBe('00000000-0000-0000-0000-000000000000');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe("If it's one of many in the page", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
// If 'thing' (what the id is) and 'name' (its specific idenifier on the page) are
|
|
// different, it will be one of others called the same 'thing'.
|
|
setUpDOM({ 'thing': 'ID', 'name': 'Default' });
|
|
|
|
component = document.querySelector('[data-module=copy-to-clipboard]');
|
|
|
|
// start the module
|
|
window.GOVUK.modules.start();
|
|
|
|
});
|
|
|
|
// Because it is not the only 'thing' on the page, the id will not have a heading
|
|
// and so needs some prefix text to label it
|
|
test("The id should have a hidden prefix to label what it is", () => {
|
|
|
|
const value = component.querySelector('.copy-to-clipboard__value .govuk-visually-hidden');
|
|
expect(value).not.toBeNull();
|
|
expect(value.textContent).toEqual('ID: ');
|
|
|
|
});
|
|
|
|
test("the button should have a hidden suffix naming the id it is for", () => {
|
|
|
|
const buttonSuffix = component.querySelector('button .govuk-visually-hidden');
|
|
expect(buttonSuffix).not.toBeNull();
|
|
expect(buttonSuffix.textContent).toEqual(' for Default');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe("If it's the only one on the page", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
// The heading is added if 'thing' (what the id is) has the same value as 'name'
|
|
// (its specific identifier on the page) because this means it can assume it is
|
|
// the only one of its type there
|
|
setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' });
|
|
|
|
component = document.querySelector('[data-module=copy-to-clipboard]');
|
|
|
|
// start the module
|
|
window.GOVUK.modules.start();
|
|
|
|
});
|
|
|
|
test("Its button and id shouldn't have extra hidden text to identify them", () => {
|
|
|
|
const value = component.querySelector('.copy-to-clipboard__value .govuk-visually-hidden');
|
|
const buttonSuffix = component.querySelector('button .govuk-visually-hidden');
|
|
expect(value).toBeNull();
|
|
expect(buttonSuffix).toBeNull();
|
|
|
|
})
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe("If you click the 'Copy Some Thing to clipboard' button", () => {
|
|
|
|
describe("For all variations of the initial HTML", () => {
|
|
|
|
let keyEl;
|
|
|
|
beforeEach(() => {
|
|
|
|
setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' });
|
|
|
|
// start the module
|
|
window.GOVUK.modules.start();
|
|
|
|
component = document.querySelector('[data-module=copy-to-clipboard]');
|
|
keyEl = component.querySelector('.copy-to-clipboard__value');
|
|
|
|
helpers.triggerEvent(component.querySelector('button'), 'click');
|
|
|
|
});
|
|
|
|
test("The live-region should be shown and its text should confirm the copy action", () => {
|
|
|
|
const liveRegion = component.querySelector('.copy-to-clipboard__notice');
|
|
|
|
expect(liveRegion.classList.contains('govuk-visually-hidden')).toBe(false);
|
|
expect(liveRegion.textContent.trim()).toEqual(
|
|
expect.stringContaining('Copied to clipboard')
|
|
);
|
|
|
|
});
|
|
|
|
// The button also says this but its text after being changed is not announced due to being
|
|
// lower priority than the live-region
|
|
test("The live-region should contain some hidden text giving context to the statement shown", () => {
|
|
|
|
const liveRegionHiddenText = component.querySelectorAll('.copy-to-clipboard__notice .govuk-visually-hidden');
|
|
|
|
expect(liveRegionHiddenText.length).toEqual(2);
|
|
expect(liveRegionHiddenText[0].textContent).toEqual('Some Thing ');
|
|
expect(liveRegionHiddenText[1].textContent).toEqual(', press button to show in page');
|
|
|
|
});
|
|
|
|
test("It should swap the button for one to show the Some Thing", () => {
|
|
|
|
expect(component.querySelector('button').textContent.trim()).toEqual(
|
|
expect.stringContaining('Show Some Thing')
|
|
);
|
|
|
|
});
|
|
|
|
test("It should remove the id from the page", () => {
|
|
|
|
expect(component.querySelector('.copy-to-clipboard__value')).toBeNull();
|
|
|
|
});
|
|
|
|
test("It should copy the thing to the clipboard", () => {
|
|
|
|
// it should make a selection (a range) from the contents of the element containing the Some Thing
|
|
expect(rangeMock.selectNodeContents.mock.calls[0]).toEqual([keyEl]);
|
|
|
|
// that selection (a range) should be added to that for the page (a selection)
|
|
expect(selectionMock.addRange.mock.calls[0]).toEqual([rangeMock]);
|
|
|
|
expect(document.execCommand).toHaveBeenCalled();
|
|
expect(document.execCommand.mock.calls[0]).toEqual(['copy']);
|
|
|
|
// reset any methods in the global space
|
|
window.queryCommandSupported = undefined;
|
|
window.getSelection = undefined;
|
|
document.createRange = undefined;
|
|
|
|
});
|
|
|
|
describe("If you then click the 'Show Some Thing'", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
helpers.triggerEvent(component.querySelector('button'), 'click');
|
|
|
|
});
|
|
|
|
test("It should change the text to show the Some Thing", () => {
|
|
|
|
expect(component.querySelector('.copy-to-clipboard__value')).not.toBeNull();
|
|
|
|
});
|
|
|
|
test("It should swap the button for one to copy the thing to the clipboard", () => {
|
|
|
|
expect(component.querySelector('button').textContent.trim()).toEqual(
|
|
expect.stringContaining('Copy Some Thing to clipboard')
|
|
);
|
|
|
|
})
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe("If it's one of many in the page", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
// If 'thing' (what the id is) and 'name' (its specific idenifier on the page) are
|
|
// different, it will be one of others called the same 'thing'.
|
|
setUpDOM({ 'thing': 'ID', 'name': 'Default' });
|
|
|
|
// start the module
|
|
window.GOVUK.modules.start();
|
|
|
|
component = document.querySelector('[data-module=copy-to-clipboard]');
|
|
|
|
helpers.triggerEvent(component.querySelector('button'), 'click');
|
|
|
|
});
|
|
|
|
test("the button should have a hidden suffix naming the id it is for", () => {
|
|
|
|
const buttonSuffix = component.querySelector('button .govuk-visually-hidden');
|
|
expect(buttonSuffix).not.toBeNull();
|
|
expect(buttonSuffix.textContent).toEqual(' for Default');
|
|
|
|
});
|
|
|
|
test("the copied selection (range) should start after the prefix of the id", () => {
|
|
|
|
// that selection (a range) should have a startOffset of 1:
|
|
// index 0: the prefix node
|
|
// index 1: the value node
|
|
expect(rangeMock.setStart).toHaveBeenCalled();
|
|
expect(rangeMock.setStart.mock.calls[0][1]).toEqual(1);
|
|
|
|
// reset any methods in the global space
|
|
window.queryCommandSupported = undefined;
|
|
window.getSelection = undefined;
|
|
document.createRange = undefined;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
describe("If it's the only one on the page", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
// The heading is added if 'thing' (what the id is) has the same value as 'name'
|
|
// (its specific identifier on the page) because this means it can assume it is
|
|
// the only one of its type there
|
|
setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' });
|
|
|
|
// start the module
|
|
window.GOVUK.modules.start();
|
|
|
|
component = document.querySelector('[data-module=copy-to-clipboard]');
|
|
|
|
helpers.triggerEvent(component.querySelector('button'), 'click');
|
|
|
|
});
|
|
|
|
test("Its button and id shouldn't have extra hidden text to identify them", () => {
|
|
|
|
const prefix = component.querySelector('.copy-to-clipboard__value .govuk-visually-hidden');
|
|
const buttonSuffix = component.querySelector('button .govuk-visually-hidden');
|
|
expect(prefix).toBeNull();
|
|
expect(buttonSuffix).toBeNull();
|
|
|
|
})
|
|
|
|
test("the copied selection (range) should start at the default position", () => {
|
|
|
|
// that selection (a range) shouldn't call setStart to avoid the prefix:
|
|
expect(rangeMock.setStart).not.toHaveBeenCalled();
|
|
|
|
// reset any methods in the global space
|
|
window.queryCommandSupported = undefined;
|
|
window.getSelection = undefined;
|
|
document.createRange = undefined;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|