UNPKG

pa11y-ci-reporter-cli-summary

Version:

Pa11y CI reporter that outputs the URL and a count of the errors/warning/notices for each page to stdout

187 lines (166 loc) 6.2 kB
'use strict'; /** * Factory for creating a formatter object, used to format * titles, results, errors, and results summary. * * @module formatter */ const tailUrl = require('./tail-url'); const { bold, red, yellow, cyan } = require('kleur'); const formats = { errors: { colorFunction: red, label: 'error' }, notices: { colorFunction: cyan, label: 'notice' }, pageErrors: { colorFunction: red, label: 'page error' }, warnings: { colorFunction: yellow, label: 'warning' } }; const defaultPadding = 3; const urlPadding = 6; const maxUrlMaxLength = 100; const padString = (value, length, start = 0) => String(value).padStart(start).padEnd(length); const formatCount = (count, type, trimEnd = false) => { // Length requires 1 added since labels are missing "s" for pluralize. // Pad start is type.label.length since labels are missing "s", so offsets left 1 character let formattedCount = padString( count, type.label.length + 1 + defaultPadding, type.label.length ); if (trimEnd) { formattedCount = formattedCount.trimEnd(); } if (typeof count === 'number' && count > 0) { formattedCount = type.colorFunction(formattedCount); } return formattedCount; }; const pluralize = (label, count) => (count === 1 ? label : `${label}s`); const formatTypeSummary = (count, type) => type.colorFunction(`${count} ${pluralize(type.label, count)}`); const getNormalizedMaxUrlLength = (maxUrlLength) => Math.min(maxUrlLength + urlPadding, maxUrlMaxLength); /** * Factory function for creating a formatter object, used to format * titles, results, errors, and results summary. * * @param {number} maxUrlLength The maximum length of a URL, used to format * the length of the first column. * @returns {object} An object with functions for formatting output. * @throws {TypeError} Throws if maxUrlLength is not a number. * @static * @public */ // eslint-disable-next-line max-lines-per-function -- factory function with state const formatterFactory = (maxUrlLength) => { if (typeof maxUrlLength !== 'number') { throw new TypeError('maxUrlLength must be a number'); } const normalizedMaxUrlLength = getNormalizedMaxUrlLength(maxUrlLength); const formatUrl = (url) => padString( tailUrl(url, normalizedMaxUrlLength), normalizedMaxUrlLength + defaultPadding ); /** * Generates a formatted string with the error thrown while testing a URL. * Puppeteer errors are returned in the format * "net::ERR_NAME_NOT_RESOLVED at https://this.url.does.not.exist/", * so extract error only and remove URL, which is already included (if applicable). * * @instance * @param {string} url The URL being tested. * @param {Error} error The Error generated during test execution. * @returns {string} The formatted URL with error message. * @public */ const formatError = (url, error) => ` ${formatUrl(url)} ${red( error.message.replace(/^(?<message>.*) at .*$/, '$1') )}`; /** * Generates a formatted header string. * * @instance * @param {number} count Count of URLs being analyzed. * @returns {string} Formatted header string. * @public */ // eslint-disable-next-line unicorn/consistent-function-scoping -- jsdoc const formatHeader = (count) => `\nAnalyzing ${count} ${pluralize('URL', count)}:`; /** * Generates a formatted string with the results after testing a URL. * * @instance * @param {string} url The URL being tested. * @param {(number|string)} errors The text to be displayed in the errors column * (number of errors or the label for the column). * @param {(number|string)} warnings The text to be displayed in the warning column * (number of warnings or the label for the column). * @param {(number|string)} notices The text to be displayed in the notices column * (number of notices or the label for the column). * @returns {string} The formatted URL with results. * @public */ const formatResult = (url, errors, warnings, notices) => ` ${formatUrl(url)} ${formatCount(errors, formats.errors)} ` + `${formatCount(warnings, formats.warnings)} ${formatCount( notices, formats.notices, true )}`; /** * Generates a formatted title string. * * @instance * @param {string} title The title string. * @returns {string} The formatted title string. * @public */ // eslint-disable-next-line unicorn/consistent-function-scoping -- keep in scope for jsdoc comments const formatTitle = (title) => bold(title); /** * Generates a formatted string summarizing errors, warning, notices, * and page errors from pa11y-ci results. * * @instance * @param {object} runResults The pa11y-ci results for all URLs. * @returns {string} The formatted summary of issues. * @public */ const formatSummary = (runResults) => { let totalsString = `\n${formatTitle(' Totals:')} ` + `${formatTypeSummary(runResults.errors, formats.errors)}, ` + `${formatTypeSummary(runResults.warnings, formats.warnings)}, ` + `${formatTypeSummary(runResults.notices, formats.notices)}`; if (runResults.pageErrors) { totalsString += `, ${formatTypeSummary( runResults.pageErrors, formats.pageErrors )}`; } totalsString += '\n'; return totalsString; }; return { formatError, formatHeader, formatResult, formatSummary, formatTitle }; }; module.exports = formatterFactory;