UNPKG

ember-a11y-testing

Version:
205 lines (185 loc) 5.49 kB
import axeCore from 'axe-core'; import type { AxeResults, NodeResult, RelatedNode, Result } from 'axe-core'; /** * This file is heavily borrowed from https://github.com/dequelabs/react-axe/blob/d3245b32fc5ed19e3c7b2c43d2815fe63f5875cb/index.ts * * An issue (https://github.com/dequelabs/react-axe/issues/180) has been opened which * suggests extracting out the common pieces of axe formatting to a separate repository, * which would allow a lot of these functions to be removed. */ // @reference axe-core https://github.com/dequelabs/axe-core/blob/develop/lib/core/base/audit.js type AxeCoreNodeResultKey = 'any' | 'all' | 'none'; interface AxeWithAudit { _audit: { data: { failureSummaries: { any: { failureMessage: (args: string[]) => string; }; all: { failureMessage: (args: string[]) => string; }; none: { failureMessage: (args: string[]) => string; }; }; }; }; } // contrasted against Chrome default color of #ffffff const lightTheme = { serious: '#d93251', minor: '#d24700', text: 'black', }; // contrasted against Safari dark mode color of #535353 const darkTheme = { serious: '#ffb3b3', minor: '#ffd500', text: 'white', }; const theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? darkTheme : lightTheme; const boldCourier = 'font-weight:bold;font-family:Courier;'; const critical = `color:${theme.serious};font-weight:bold;`; const serious = `color:${theme.serious};font-weight:normal;`; const moderate = `color:${theme.minor};font-weight:bold;`; const minor = `color:${theme.minor};font-weight:normal;`; const defaultReset = `font-color:${theme.text};font-weight:normal;`; const testSuiteResults: Result[][] = []; const cache: { [key: string]: string } = {}; function deduplicateViolations(results: AxeResults) { return results.violations.filter((result) => { result.nodes = result.nodes.filter((node) => { const key: string = node.target.toString() + result.id; const retVal = !cache[key]; cache[key] = key; return retVal; }); return !!result.nodes.length; }); } /** * Log the axe result node to the console * * @param {NodeResult} node * @param {Function} logFn console log function to use (error, warn, log, etc.) */ function logElement( node: NodeResult | RelatedNode, logFn: (...args: any[]) => void, ): void { const el = document.querySelector(node.target.toString()); if (!el) { logFn('Selector: %c%s', boldCourier, node.target.toString()); } else { logFn('Element: %o', el); } } /** * Log the axe result node html to the console * * @param {NodeResult} node */ function logHtml(node: NodeResult | RelatedNode): void { console.log('HTML: %c%s', boldCourier, node.html); } /** * Log the failure message of a node result. * * @param {NodeResult} node * @param {String} key which check array to log from (any, all, none) */ function logFailureMessage(node: NodeResult, key: AxeCoreNodeResultKey): void { // this exists on axe but we don't export it as part of the typescript // namespace, so just let me use it as I need const message: string = ( axeCore as unknown as AxeWithAudit )._audit.data.failureSummaries[key].failureMessage( node[key].map((check) => check.message || ''), ); console.error(message); } /** * Log as a group the node result and failure message. * * @param {NodeResult} node * @param {String} key which check array to log from (any, all, none) */ function failureSummary(node: NodeResult, key: AxeCoreNodeResultKey): void { if (node[key].length > 0) { logElement(node, console.groupCollapsed); logHtml(node); logFailureMessage(node, key); let relatedNodes: RelatedNode[] = []; node[key].forEach((check) => { relatedNodes = relatedNodes.concat(check.relatedNodes || []); }); if (relatedNodes.length > 0) { console.groupCollapsed('Related nodes'); relatedNodes.forEach((relatedNode) => { logElement(relatedNode, console.log); logHtml(relatedNode); }); console.groupEnd(); } console.groupEnd(); } } /** * @public * @param results The axe results. */ export function storeResults(results: AxeResults) { if (results.violations.length > 0) { testSuiteResults.push(deduplicateViolations(results)); } } /** * Prints aggregated axe results to the console. * * @public */ export function printResults() { if (testSuiteResults.length) { console.group('%cAxe issues', serious); testSuiteResults.forEach((results) => { results.forEach((result) => { let fmt: string; switch (result.impact) { case 'critical': fmt = critical; break; case 'serious': fmt = serious; break; case 'moderate': fmt = moderate; break; case 'minor': fmt = minor; break; default: fmt = minor; break; } console.groupCollapsed( '%c%s: %c%s %s', fmt, result.impact, defaultReset, result.help, result.helpUrl, ); result.nodes.forEach((node) => { failureSummary(node, 'any'); failureSummary(node, 'none'); }); console.groupEnd(); }); }); console.groupEnd(); } }