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
JavaScript
;
/**
* 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;