@skyux-sdk/testing
Version:
This library was generated with [Nx](https://nx.dev).
352 lines (335 loc) • 12.5 kB
JavaScript
import axe from 'axe-core';
import { TestBed } from '@angular/core/testing';
import { SkyAppResourcesService, SkyLibResourcesService } from '@skyux/i18n';
import { firstValueFrom } from 'rxjs';
function parseMessage(violations) {
let message = 'Expected element to pass accessibility checks.\n\n';
violations.forEach((violation) => {
const wcagTags = violation.tags
.filter((tag) => tag.match(/wcag\d{3}|^best*/gi))
.join(', ');
const nodeResults = violation.nodes.filter(filterViolationNodeResults(violation));
const html = nodeResults.reduce((accumulator, node) => {
const related = [...node.all, ...node.none, ...node.any]
.map((checkResult) => {
const relatedNodes = checkResult.relatedNodes || [];
let relatedHtml = relatedNodes
.map((relatedNode) => relatedNode.html.split(`\n`).join(`\n `))
.join(`\n\n `);
if (relatedHtml) {
relatedHtml = `\n Related Nodes:\n ${relatedHtml}`;
}
return ` - [${checkResult.id}] ${checkResult.message}${relatedHtml}`;
})
.join(`\n`);
const newInformation = [
node.failureSummary
? `[${node.impact?.toUpperCase()}] ${node.failureSummary
.split(/\n */g)
.join(`\n - `)}`
: '',
node.ancestry ? `Ancestry: ${node.ancestry.join(', ')}` : '',
`Target: ${node.target.join(', ')}`,
node.html ? `HTML: ${node.html}` : '',
related,
].filter((info) => !!info);
return `${accumulator}\n\n${newInformation.join(`\n`)}`;
}, '');
const error = [
`aXe - [Rule: '${violation.id}'] ${violation.help} - WCAG: ${wcagTags}`,
` Get help at: ${violation.helpUrl}\n`,
`${html}\n\n`,
].join('\n');
message += `${error}\n`;
});
return message;
}
function filterViolationNodeResults(result) {
if ([
'aria-dialog-name', // AG Grid adds role="dialog" to its popup editor container, but doesn't set title, aria-label, or aria-labelledby
'aria-hidden-focus', // AG Grid uses aria-hidden on elements before they are ready
'aria-required-children', // AG Grid uses some aria-hidden elements that axe doesn't like
'aria-valid-attr', // AG Grid uses aria-description, which is still in draft
'scrollable-region-focusable', // AG Grid handles scrolling
].includes(result.id)) {
return (node) => !node.html.includes('class="ag-');
}
else if (result.id === 'aria-allowed-role') {
const fieldsetRadiogroupRegex = new RegExp(/<fieldset[^>]+role="radiogroup"/);
return (node) => !fieldsetRadiogroupRegex.test(node.html);
}
else {
return () => true;
}
}
/**
* @internal
*/
class _SkyA11yAnalyzer {
static { this.analyzer = axe; }
static run(element, config) {
if (element === undefined) {
throw new Error('No element was specified for accessibility checking.');
}
_SkyA11yAnalyzer.analyzer.reset();
const defaults = {
rules: {},
};
// Disable autocomplete-valid
// Chrome browsers ignore autocomplete="off", which forces us to use non-standard values
// to disable the browser's native autofill.
// https://bugs.chromium.org/p/chromium/issues/detail?id=468153#c164
defaults.rules['autocomplete-valid'] = { enabled: false };
return new Promise((resolve, reject) => {
const callback = (error, results) => {
if (error?.message) {
reject(error);
return;
}
const violations = results.violations.filter((violation) => violation.nodes.some(filterViolationNodeResults(violation)));
if (violations.length > 0) {
const message = parseMessage(violations);
reject(new Error(message));
return;
}
resolve();
};
_SkyA11yAnalyzer.analyzer.run(element, { ...defaults, ...config }, callback);
});
}
}
/**
* @internal
*/
async function _skyTestingCheckAccessibility(el, options) {
const target = el instanceof Document ? el.documentElement : el;
if (!(target instanceof Element)) {
throw new Error('toBeAccessible expects an Element or Document.');
}
try {
await _SkyA11yAnalyzer.run(target, options);
return {
pass: true,
message: 'Expected accessibility violations, but none were found.',
};
}
catch (err) {
return {
pass: false,
message: err.message,
};
}
}
/**
* @internal
*/
function _skyTestingCheckExistence(el) {
const pass = !!el;
return {
pass,
message: pass
? 'Expected element not to exist'
: 'Expected element to exist',
};
}
async function getResourceString(resourceKey, resourceArgs = []) {
const resourcesSvc = TestBed.inject(SkyAppResourcesService);
return await firstValueFrom(resourcesSvc.getString(resourceKey, ...resourceArgs));
}
async function getLibResourceString(resourceKey, resourceArgs = []) {
const resourcesSvc = TestBed.inject(SkyLibResourcesService);
return await firstValueFrom(resourcesSvc.getString(resourceKey, ...resourceArgs));
}
function isTemplateMatch(sample, template) {
let matches = true;
const templateTokens = template.split(new RegExp('{\\d+}')).reverse();
let currentToken = templateTokens.pop();
let lastPosition = 0;
while (currentToken !== undefined && matches) {
const tokenPosition = sample.indexOf(currentToken, lastPosition);
matches = tokenPosition >= lastPosition;
lastPosition = tokenPosition + currentToken.length;
currentToken = templateTokens.pop();
}
return matches;
}
/**
* @internal
*/
async function _skyTestingCheckLibResourceTemplate(el, resourceKey) {
const actualText = el.textContent ?? '';
const expectedText = await getLibResourceString(resourceKey);
const pass = isTemplateMatch(actualText, expectedText);
return {
pass,
message: pass
? `Expected element's text "${actualText}" not to match "${expectedText}"`
: `Expected element's text "${actualText}" to match "${expectedText}"`,
};
}
/**
* @internal
*/
async function _skyTestingCheckLibResourceText(actualText, resourceKey, resourceArgs) {
const expectedText = await getLibResourceString(resourceKey, resourceArgs);
const pass = actualText === expectedText;
return {
pass,
message: pass
? `Expected "${actualText}" not to equal "${expectedText}"`
: `Expected "${actualText}" to equal "${expectedText}"`,
};
}
/**
* @internal
*/
async function _skyTestingCheckResourceTemplate(el, resourceKey) {
const actualText = el.textContent ?? '';
const expectedText = await getResourceString(resourceKey);
const pass = isTemplateMatch(actualText, expectedText);
return {
pass,
message: pass
? `Expected element's text "${actualText}" not to match "${expectedText}"`
: `Expected element's text "${actualText}" to match "${expectedText}"`,
};
}
/**
* @internal
*/
async function _skyTestingCheckResourceText(actualText, resourceKey, resourceArgs) {
const expectedText = await getResourceString(resourceKey, resourceArgs);
const pass = actualText === expectedText;
return {
pass,
message: pass
? `Expected "${actualText}" not to equal "${expectedText}"`
: `Expected "${actualText}" to equal "${expectedText}"`,
};
}
const DEFAULTS = {
checkCssDisplay: true,
checkCssVisibility: false,
checkDimensions: false,
checkExists: false,
};
/**
* @internal
*/
function _skyTestingCheckVisibility(el, options) {
const settings = { ...DEFAULTS, ...options };
let pass = true;
if (settings.checkExists) {
pass = !!el;
}
if (pass) {
const computedStyle = window.getComputedStyle(el);
if (settings.checkCssDisplay) {
pass = computedStyle.display !== 'none';
}
if (settings.checkCssVisibility) {
pass = computedStyle.visibility !== 'hidden';
}
if (settings.checkDimensions) {
const box = el.getBoundingClientRect();
pass = box.width > 0 && box.height > 0;
}
}
return {
pass,
message: pass
? 'Expected element to not be visible'
: 'Expected element to be visible',
};
}
function _skyTestingHasCssClass(el, expectedClassName) {
if (expectedClassName.indexOf('.') === 0) {
throw new Error('Please remove the leading dot from your class name.');
}
const pass = el.classList.contains(expectedClassName);
return {
pass,
message: pass
? `Expected element not to have CSS class "${expectedClassName}"`
: `Expected element to have CSS class "${expectedClassName}"`,
};
}
/**
* @internal
*/
async function _skyTestingHasLibResourceText(el, resourceKey, resourceArgs = [], trimWhitespace) {
const expectedText = await getLibResourceString(resourceKey, resourceArgs);
let actualText = el.textContent ?? '';
if (trimWhitespace) {
actualText = actualText.trim();
}
const pass = actualText === expectedText;
return {
pass,
message: pass
? `Expected element's inner text "${actualText}" not to be "${expectedText}"`
: `Expected element's inner text "${actualText}" to be "${expectedText}"`,
};
}
/**
* @internal
*/
async function _skyTestingHasResourceText(el, resourceKey, resourceArgs = [], trimWhitespace) {
const expectedText = await getResourceString(resourceKey, resourceArgs);
let actualText = el.textContent ?? '';
if (trimWhitespace) {
actualText = actualText.trim();
}
const pass = actualText === expectedText;
return {
pass,
message: pass
? `Expected element's inner text "${actualText}" not to be "${expectedText}"`
: `Expected element's inner text "${actualText}" to be "${expectedText}"`,
};
}
/**
* @internal
*/
function _skyTestingHasStyle(el, expectedStyles) {
const messages = [];
let hasFailure = false;
const styles = window.getComputedStyle(el);
for (const styleName of Object.keys(expectedStyles)) {
const actualStyle = styles.getPropertyValue(styleName);
const expectedStyle = expectedStyles[styleName];
if (actualStyle !== expectedStyle) {
if (!hasFailure) {
hasFailure = true;
}
messages.push(`Expected element to have CSS style "${styleName}: ${expectedStyle}"`);
}
else {
messages.push(`Expected element not to have CSS style "${styleName}: ${expectedStyle}"`);
}
messages.push(`Actual styles are: "${styleName}: ${actualStyle}"`);
}
return {
pass: !hasFailure,
message: messages.join('\n'),
};
}
function _skyTestingHasText(el, expectedText, trimWhitespace) {
let actualText = el.textContent ?? '';
if (trimWhitespace) {
actualText = actualText.trim();
}
const pass = actualText === expectedText;
return {
pass,
message: pass
? `Expected element's inner text "${actualText}" not to be: "${expectedText}"`
: `Expected element's inner text to be: "${expectedText}"\n` +
`Actual element's inner text was: "${actualText}"`,
};
}
/**
* Generated bundle index. Do not edit.
*/
export { _SkyA11yAnalyzer, _skyTestingCheckAccessibility, _skyTestingCheckExistence, _skyTestingCheckLibResourceTemplate, _skyTestingCheckLibResourceText, _skyTestingCheckResourceTemplate, _skyTestingCheckResourceText, _skyTestingCheckVisibility, _skyTestingHasCssClass, _skyTestingHasLibResourceText, _skyTestingHasResourceText, _skyTestingHasStyle, _skyTestingHasText };
//# sourceMappingURL=skyux-sdk-testing-private.mjs.map