mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-09 10:54:11 -04:00
Removed jquery from a few simple javascript files
This commit is contained in:
@@ -1 +1 @@
|
||||
{"esversion": 6, "esnext": false}
|
||||
{"esversion": 8, "esnext": false}
|
||||
|
||||
@@ -1,94 +1,70 @@
|
||||
(function(window) {
|
||||
"use strict";
|
||||
|
||||
if (!document.queryCommandSupported('copy')) return;
|
||||
// Only initialize if modern Clipboard API is available
|
||||
if (!navigator.clipboard) return;
|
||||
|
||||
window.NotifyModules['copy-to-clipboard'] = function() {
|
||||
|
||||
const states = {
|
||||
'valueVisible': (options) => `
|
||||
<span class="copy-to-clipboard__value margin-bottom-1">${options.valueLabel ? '<span class="usa-sr-only">' + options.thing + ': </span>' : ''}${options.value}</span>
|
||||
<span class="copy-to-clipboard__notice" aria-live="assertive">
|
||||
${options.onload ? '' : options.thing + ' returned to page, press button to copy to clipboard'}
|
||||
</span>
|
||||
<button class="usa-button usa-button--outline copy-to-clipboard__button--copy">
|
||||
Copy ${options.thing} to clipboard${options.name ? '<span class="usa-sr-only"> for ' + options.name + '</span>' : ''}
|
||||
</button>
|
||||
`,
|
||||
'valueCopied': (options) => `
|
||||
<span class="copy-to-clipboard__notice" aria-live="assertive">
|
||||
<span class="usa-sr-only">${options.thing} </span>Copied to clipboard<span class="usa-sr-only">, press button to show in page</span>
|
||||
</span>
|
||||
<button class="usa-button copy-to-clipboard__button--show">
|
||||
Show ${options.thing}${options.name ? '<span class="usa-sr-only"> for ' + options.name + '</span>' : ''}
|
||||
</button>
|
||||
`
|
||||
};
|
||||
|
||||
this.getRangeFromElement = function (copyableElement) {
|
||||
const range = document.createRange();
|
||||
const childNodes = Array.prototype.slice.call(copyableElement.childNodes);
|
||||
let prefixIndex = -1;
|
||||
|
||||
childNodes.forEach((el, idx) => {
|
||||
if ((el.nodeType === 1) && el.classList.contains('usa-sr-only')) {
|
||||
prefixIndex = idx;
|
||||
}
|
||||
});
|
||||
|
||||
range.selectNodeContents(copyableElement);
|
||||
if (prefixIndex !== -1) { range.setStart(copyableElement, prefixIndex + 1); }
|
||||
|
||||
return range;
|
||||
};
|
||||
|
||||
this.copyValueToClipboard = function(copyableElement, callback) {
|
||||
var selection = window.getSelection ? window.getSelection() : document.selection,
|
||||
range = this.getRangeFromElement(copyableElement);
|
||||
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
selection.removeAllRanges();
|
||||
callback();
|
||||
/**
|
||||
* Copy text to clipboard using modern Clipboard API
|
||||
* @param {string} text - The text to copy
|
||||
* @param {Function} callback - Called after successful copy
|
||||
*/
|
||||
this.copyValueToClipboard = async function(text, callback) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.error('Failed to copy to clipboard:', err);
|
||||
}
|
||||
};
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
const $component = $(component),
|
||||
stateOptions = {
|
||||
value: $component.data('value'),
|
||||
thing: $component.data('thing')
|
||||
},
|
||||
name = $component.data('name');
|
||||
const value = component.dataset.value;
|
||||
const thing = component.dataset.thing;
|
||||
const name = component.dataset.name;
|
||||
|
||||
// if the name is distinct from the thing:
|
||||
// - it will be used in the rendering
|
||||
// - the value won't be identified by a heading so needs its own label
|
||||
if (name !== stateOptions.thing) {
|
||||
stateOptions.name = name;
|
||||
stateOptions.valueLabel = true;
|
||||
}
|
||||
// Determine button label
|
||||
const isMultiple = name !== thing;
|
||||
const buttonLabel = isMultiple
|
||||
? `Copy ${thing}`
|
||||
: `Copy ${thing} to clipboard`;
|
||||
const srSuffix = isMultiple ? ` for ${name}` : '';
|
||||
|
||||
$component
|
||||
.addClass('copy-to-clipboard')
|
||||
.css('min-height', $component.height())
|
||||
.html(states.valueVisible($.extend({ 'onload': true }, stateOptions)))
|
||||
.on(
|
||||
'click', '.copy-to-clipboard__button--copy', () =>
|
||||
this.copyValueToClipboard(
|
||||
$('.copy-to-clipboard__value', component)[0], () =>
|
||||
$component
|
||||
.html(states.valueCopied(stateOptions))
|
||||
.find('.usa-button').focus()
|
||||
)
|
||||
)
|
||||
.on(
|
||||
'click', '.copy-to-clipboard__button--show', () =>
|
||||
$component
|
||||
.html(states.valueVisible(stateOptions))
|
||||
.find('.usa-button').focus()
|
||||
);
|
||||
// Create simple HTML structure
|
||||
component.classList.add('copy-to-clipboard');
|
||||
component.innerHTML = `
|
||||
<div class="copy-to-clipboard__value">${value}</div>
|
||||
<button class="usa-button usa-button--outline copy-to-clipboard__button" type="button">
|
||||
${buttonLabel}<span class="usa-sr-only">${srSuffix}</span>
|
||||
</button>
|
||||
<span class="usa-sr-only" aria-live="polite" aria-atomic="true"></span>
|
||||
`;
|
||||
|
||||
const button = component.querySelector('.copy-to-clipboard__button');
|
||||
const srAnnouncement = component.querySelector('[aria-live]');
|
||||
|
||||
// Handle copy button click
|
||||
button.addEventListener('click', () => {
|
||||
this.copyValueToClipboard(value, () => {
|
||||
// Change button text to "Copied!"
|
||||
const originalText = button.innerHTML;
|
||||
button.innerHTML = `Copied!<span class="usa-sr-only">${srSuffix}</span>`;
|
||||
button.disabled = true;
|
||||
|
||||
// Announce to screen readers
|
||||
srAnnouncement.textContent = `${thing} copied to clipboard`;
|
||||
|
||||
// Reset button after 2 seconds
|
||||
setTimeout(() => {
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
srAnnouncement.textContent = '';
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
if ('stickAtBottomWhenScrolling' in window.NotifyModules) {
|
||||
window.NotifyModules.stickAtBottomWhenScrolling.recalculate();
|
||||
|
||||
@@ -4,15 +4,13 @@
|
||||
window.NotifyModules['track-error'] = function() {
|
||||
|
||||
this.start = function(element) {
|
||||
var component = $(element);
|
||||
|
||||
// Track error to analytics if available
|
||||
if (window.NotifyModules && window.NotifyModules.analytics && window.NotifyModules.analytics.trackEvent) {
|
||||
window.NotifyModules.analytics.trackEvent(
|
||||
'Error',
|
||||
component.data('error-type'),
|
||||
element.dataset.errorType,
|
||||
{
|
||||
'label': component.data('error-label')
|
||||
'label': element.dataset.errorLabel
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,6 @@ import './fileUpload.js';
|
||||
import './errorTracking.js';
|
||||
import './templateFolderForm.js';
|
||||
import './collapsibleCheckboxes.js';
|
||||
import './radioSlider.js';
|
||||
import './updateStatus.js';
|
||||
import './main.js';
|
||||
import './listEntry.js';
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
(function (window) {
|
||||
'use strict';
|
||||
|
||||
var $ = window.jQuery;
|
||||
|
||||
window.NotifyModules.moduleSystem = {
|
||||
find: function (container) {
|
||||
container = container || $('body');
|
||||
container = container || document.body;
|
||||
|
||||
var modules;
|
||||
var moduleSelector = '[data-module]';
|
||||
|
||||
modules = container.find(moduleSelector);
|
||||
// If container is already an element, use it directly
|
||||
if (container instanceof Element) {
|
||||
modules = Array.from(container.querySelectorAll(moduleSelector));
|
||||
|
||||
if (container.is(moduleSelector)) {
|
||||
modules = modules.add(container);
|
||||
// If the container itself is a module, include it
|
||||
if (container.matches && container.matches(moduleSelector)) {
|
||||
modules.unshift(container);
|
||||
}
|
||||
} else {
|
||||
modules = Array.from(document.querySelectorAll(moduleSelector));
|
||||
}
|
||||
|
||||
return modules;
|
||||
@@ -25,16 +29,16 @@
|
||||
for (var i = 0, l = modules.length; i < l; i++) {
|
||||
try {
|
||||
var module;
|
||||
var element = $(modules[i]);
|
||||
var type = this.camelCaseAndCapitalise(element.data('module'));
|
||||
var started = element.data('module-started');
|
||||
var element = modules[i];
|
||||
var type = this.camelCaseAndCapitalise(element.dataset.module);
|
||||
var started = element.dataset.moduleStarted;
|
||||
|
||||
if (typeof window.NotifyModules[type] === 'function' && !started) {
|
||||
module = new window.NotifyModules[type]();
|
||||
if (module.start) {
|
||||
module.start(element);
|
||||
}
|
||||
element.data('module-started', true);
|
||||
element.dataset.moduleStarted = 'true';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize module:', type || 'unknown', error);
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
(function(window) {
|
||||
|
||||
"use strict";
|
||||
|
||||
window.NotifyModules['radio-slider'] = function() {
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
$(component)
|
||||
.on('click', function() {
|
||||
|
||||
valuesInLabel = $(this).find(':checked').next('label').text().split('/');
|
||||
|
||||
if (valuesInLabel.length === 2) {
|
||||
leftValue = valuesInLabel[0];
|
||||
rightValue = valuesInLabel[1];
|
||||
$(this).find('.radio-slider-left-value').text(leftValue);
|
||||
$(this).find('.radio-slider-right-value').text(rightValue);
|
||||
}
|
||||
|
||||
})
|
||||
.trigger('click');
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
})(window);
|
||||
@@ -58,7 +58,6 @@ const javascripts = () => {
|
||||
|
||||
const notifyModules2 = src([
|
||||
paths.src + 'javascripts/collapsibleCheckboxes.js',
|
||||
paths.src + 'javascripts/radioSlider.js',
|
||||
paths.src + 'javascripts/updateStatus.js',
|
||||
paths.src + 'javascripts/timeoutPopup.js',
|
||||
paths.src + 'javascripts/main.js',
|
||||
|
||||
@@ -153,7 +153,9 @@ def _cleanup_test_service(page: Page, service_name: str):
|
||||
|
||||
print(f"Successfully cleaned up test service: {service_name}") # noqa: T201
|
||||
else:
|
||||
print(f"Service '{service_name}' not found, may have been deleted already") # noqa: T201
|
||||
print( # noqa: T201
|
||||
f"Service '{service_name}' not found, may have been deleted already"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to cleanup service '{service_name}': {str(e)}")
|
||||
|
||||
@@ -5,45 +5,30 @@ afterAll(() => {
|
||||
require('./support/teardown.js');
|
||||
|
||||
// clear up methods in the global space
|
||||
document.queryCommandSupported = undefined;
|
||||
navigator.clipboard = undefined;
|
||||
|
||||
});
|
||||
|
||||
describe('copy to clipboard', () => {
|
||||
|
||||
let apiKey = '00000000-0000-0000-0000-000000000000';
|
||||
let thing;
|
||||
let component;
|
||||
let selectionMock;
|
||||
let rangeMock;
|
||||
let screenMock;
|
||||
let clipboardWriteTextMock;
|
||||
|
||||
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="usa-sr-only">' + options.thing + ': </span>' : ''}${apiKey}</span>
|
||||
<span class="copy-to-clipboard__notice" aria-live="assertive"></span>
|
||||
</div>`;
|
||||
<div data-module="copy-to-clipboard" data-value="${apiKey}" data-thing="${options.thing}" data-name="${options.name}"></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(() => {});
|
||||
// Reset the clipboard mock for each test
|
||||
if (clipboardWriteTextMock) {
|
||||
clipboardWriteTextMock.mockClear();
|
||||
}
|
||||
|
||||
// mock sticky JS
|
||||
window.NotifyModules.stickAtBottomWhenScrolling = {
|
||||
@@ -52,10 +37,10 @@ describe('copy to clipboard', () => {
|
||||
|
||||
});
|
||||
|
||||
test("If copy command isn't available, nothing should happen", () => {
|
||||
test("If Clipboard API isn't available, nothing should happen", () => {
|
||||
|
||||
// fake support for the copy command not being available
|
||||
document.queryCommandSupported = jest.fn(command => false);
|
||||
// Remove clipboard API
|
||||
navigator.clipboard = undefined;
|
||||
|
||||
require('../../app/assets/javascripts/copyToClipboard.js');
|
||||
|
||||
@@ -70,14 +55,15 @@ describe('copy to clipboard', () => {
|
||||
|
||||
});
|
||||
|
||||
describe("If copy command is available", () => {
|
||||
|
||||
let componentHeightOnLoad;
|
||||
describe("If Clipboard API is available", () => {
|
||||
|
||||
beforeAll(() => {
|
||||
|
||||
// assume copy command is available
|
||||
document.queryCommandSupported = jest.fn(command => command === 'copy');
|
||||
// Mock modern Clipboard API BEFORE loading the module
|
||||
clipboardWriteTextMock = jest.fn(() => Promise.resolve());
|
||||
navigator.clipboard = {
|
||||
writeText: clipboardWriteTextMock
|
||||
};
|
||||
|
||||
// force module require to not come from cache
|
||||
jest.resetModules();
|
||||
@@ -86,10 +72,6 @@ describe('copy to clipboard', () => {
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
screenMock.reset();
|
||||
});
|
||||
|
||||
describe("On page load", () => {
|
||||
|
||||
describe("For all variations of the initial HTML", () => {
|
||||
@@ -100,26 +82,6 @@ describe('copy to clipboard', () => {
|
||||
|
||||
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.NotifyModules.start();
|
||||
|
||||
@@ -128,6 +90,7 @@ describe('copy to clipboard', () => {
|
||||
test("It should add a button for copying the thing to the clipboard", () => {
|
||||
|
||||
expect(component.querySelector('button')).not.toBeNull();
|
||||
expect(component.querySelector('button').textContent.trim()).toContain('Copy Some Thing');
|
||||
|
||||
});
|
||||
|
||||
@@ -139,21 +102,23 @@ describe('copy to clipboard', () => {
|
||||
|
||||
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.NotifyModules.stickAtBottomWhenScrolling.recalculate).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
test("It should set the component's minimum height based on its height when the page loads", () => {
|
||||
test("It should display the value", () => {
|
||||
|
||||
// to prevent the position of the button moving when the state changes
|
||||
expect(window.getComputedStyle(component)['min-height']).toEqual(`${componentHeightOnLoad}px`);
|
||||
expect(component.querySelector('.copy-to-clipboard__value')).not.toBeNull();
|
||||
expect(component.querySelector('.copy-to-clipboard__value').textContent).toBe(apiKey);
|
||||
|
||||
});
|
||||
|
||||
test("It should render the 'thing' without extra whitespace", () => {
|
||||
test("It should have an aria-live region for screen reader announcements", () => {
|
||||
|
||||
expect(component.querySelector('.copy-to-clipboard__value').textContent).toBe('00000000-0000-0000-0000-000000000000');
|
||||
const liveRegion = component.querySelector('[aria-live]');
|
||||
expect(liveRegion).not.toBeNull();
|
||||
expect(liveRegion.getAttribute('aria-live')).toBe('polite');
|
||||
expect(liveRegion.getAttribute('aria-atomic')).toBe('true');
|
||||
|
||||
});
|
||||
|
||||
@@ -163,8 +128,6 @@ describe('copy to clipboard', () => {
|
||||
|
||||
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]');
|
||||
@@ -174,19 +137,12 @@ describe('copy to clipboard', () => {
|
||||
|
||||
});
|
||||
|
||||
// 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", () => {
|
||||
test("the button should have shorter label and hidden suffix", () => {
|
||||
|
||||
const value = component.querySelector('.copy-to-clipboard__value .usa-sr-only');
|
||||
expect(value).not.toBeNull();
|
||||
expect(value.textContent).toEqual('ID: ');
|
||||
const button = component.querySelector('button');
|
||||
expect(button.textContent).toContain('Copy ID');
|
||||
|
||||
});
|
||||
|
||||
test("the button should have a hidden suffix naming the id it is for", () => {
|
||||
|
||||
const buttonSuffix = component.querySelector('button .usa-sr-only');
|
||||
const buttonSuffix = button.querySelector('.usa-sr-only');
|
||||
expect(buttonSuffix).not.toBeNull();
|
||||
expect(buttonSuffix.textContent).toEqual(' for Default');
|
||||
|
||||
@@ -198,9 +154,6 @@ describe('copy to clipboard', () => {
|
||||
|
||||
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]');
|
||||
@@ -210,12 +163,11 @@ describe('copy to clipboard', () => {
|
||||
|
||||
});
|
||||
|
||||
test("Its button and id shouldn't have extra hidden text to identify them", () => {
|
||||
test("the button should have full label without extra suffix", () => {
|
||||
|
||||
const value = component.querySelector('.copy-to-clipboard__value .usa-sr-only');
|
||||
const buttonSuffix = component.querySelector('button .usa-sr-only');
|
||||
expect(value).toBeNull();
|
||||
expect(buttonSuffix).toBeNull();
|
||||
const button = component.querySelector('button');
|
||||
expect(button.textContent).toContain('Copy Some Thing to clipboard');
|
||||
expect(button.textContent).not.toContain(' for ');
|
||||
|
||||
})
|
||||
|
||||
@@ -223,187 +175,84 @@ describe('copy to clipboard', () => {
|
||||
|
||||
});
|
||||
|
||||
describe("If you click the 'Copy Some Thing to clipboard' button", () => {
|
||||
describe("If you click the 'Copy' button", () => {
|
||||
|
||||
describe("For all variations of the initial HTML", () => {
|
||||
beforeEach(async () => {
|
||||
|
||||
let keyEl;
|
||||
jest.useFakeTimers();
|
||||
|
||||
beforeEach(() => {
|
||||
setUpDOM({ 'thing': 'API key', 'name': 'API key' });
|
||||
|
||||
setUpDOM({ 'thing': 'Some Thing', 'name': 'Some Thing' });
|
||||
component = document.querySelector('[data-module=copy-to-clipboard]');
|
||||
|
||||
// start the module
|
||||
window.NotifyModules.start();
|
||||
// start the module
|
||||
window.NotifyModules.start();
|
||||
|
||||
component = document.querySelector('[data-module=copy-to-clipboard]');
|
||||
keyEl = component.querySelector('.copy-to-clipboard__value');
|
||||
const button = component.querySelector('button');
|
||||
helpers.triggerEvent(button, 'click');
|
||||
|
||||
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('usa-sr-only')).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 .usa-sr-only');
|
||||
|
||||
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')
|
||||
);
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
// Wait for async clipboard operation
|
||||
await Promise.resolve();
|
||||
|
||||
});
|
||||
|
||||
describe("If it's one of many in the page", () => {
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
test("The value should still be visible", () => {
|
||||
|
||||
// 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.NotifyModules.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 .usa-sr-only');
|
||||
expect(buttonSuffix).not.toBeNull();
|
||||
expect(buttonSuffix.textContent).toEqual(' for Default');
|
||||
|
||||
});
|
||||
|
||||
test("the copied selection (range) should start after visually hidden prefix", () => {
|
||||
|
||||
// that selection (a range) should have a startOffset of 1:
|
||||
// index 0: the visually hidden prefix node, for example "Template ID: " or "API key: "
|
||||
// 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;
|
||||
|
||||
});
|
||||
expect(component.querySelector('.copy-to-clipboard__value')).not.toBeNull();
|
||||
expect(component.querySelector('.copy-to-clipboard__value').textContent).toBe(apiKey);
|
||||
|
||||
});
|
||||
|
||||
describe("If it's the only one on the page", () => {
|
||||
test("The button text should change to 'Copied!'", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const button = component.querySelector('button');
|
||||
expect(button.textContent).toContain('Copied!');
|
||||
|
||||
// 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.NotifyModules.start();
|
||||
test("The button should be disabled", () => {
|
||||
|
||||
component = document.querySelector('[data-module=copy-to-clipboard]');
|
||||
const button = component.querySelector('button');
|
||||
expect(button.disabled).toBe(true);
|
||||
|
||||
helpers.triggerEvent(component.querySelector('button'), 'click');
|
||||
});
|
||||
|
||||
});
|
||||
test("Screen reader announcement should confirm the copy", () => {
|
||||
|
||||
test("Its button and id shouldn't have extra hidden text to identify them", () => {
|
||||
const liveRegion = component.querySelector('[aria-live]');
|
||||
expect(liveRegion.textContent).toBe('API key copied to clipboard');
|
||||
|
||||
const prefix = component.querySelector('.copy-to-clipboard__value .usa-sr-only');
|
||||
const buttonSuffix = component.querySelector('button .usa-sr-only');
|
||||
expect(prefix).toBeNull();
|
||||
expect(buttonSuffix).toBeNull();
|
||||
});
|
||||
|
||||
})
|
||||
test("It should copy the value to clipboard using Clipboard API", () => {
|
||||
|
||||
test("the copied selection (range) should start at the default position", () => {
|
||||
expect(clipboardWriteTextMock).toHaveBeenCalledWith(apiKey);
|
||||
|
||||
// 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;
|
||||
test("After 2 seconds, button should reset to original text", () => {
|
||||
|
||||
});
|
||||
const button = component.querySelector('button');
|
||||
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(button.textContent).toContain('Copy API key');
|
||||
expect(button.textContent).not.toContain('Copied!');
|
||||
expect(button.disabled).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
test("After 2 seconds, screen reader announcement should clear", () => {
|
||||
|
||||
const liveRegion = component.querySelector('[aria-live]');
|
||||
|
||||
jest.advanceTimersByTime(2000);
|
||||
|
||||
expect(liveRegion.textContent).toBe('');
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ module.exports = {
|
||||
collectCoverage: true,
|
||||
coverageDirectory: './coverage',
|
||||
coveragePathIgnorePatterns: [
|
||||
'support/polyfills.js'
|
||||
'support/polyfills.js',
|
||||
'support/helpers/'
|
||||
],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
|
||||
Reference in New Issue
Block a user