From 3225e8c2144743113c696013cbca9aa38188344b Mon Sep 17 00:00:00 2001 From: Jack Brewer Date: Wed, 26 Oct 2022 13:03:03 +0100 Subject: [PATCH 1/5] Move formatting function to new file, pass error to shortSummary --- src/format.js | 103 ++++++++++++++++++++++ src/format.test.js | 215 +++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 82 +---------------- src/lighthouse.js | 3 - 4 files changed, 322 insertions(+), 81 deletions(-) create mode 100644 src/format.js create mode 100644 src/format.test.js diff --git a/src/format.js b/src/format.js new file mode 100644 index 00000000..7055dca9 --- /dev/null +++ b/src/format.js @@ -0,0 +1,103 @@ +const chalk = require('chalk'); +const { minify } = require('html-minifier'); +const { makeReplacements } = require('./replacements'); + +const belowThreshold = (id, expected, categories) => { + const category = categories.find((c) => c.id === id); + if (!category) { + console.warn(`Could not find category ${chalk.yellow(id)}`); + } + const actual = category ? category.score : Number.MAX_SAFE_INTEGER; + return actual < expected; +}; + +const getError = (id, expected, categories, audits) => { + const category = categories.find((c) => c.id === id); + + const categoryError = `Expected category ${chalk.cyan( + category.title, + )} to be greater or equal to ${chalk.green(expected)} but got ${chalk.red( + category.score !== null ? category.score : 'unknown', + )}`; + + const categoryAudits = category.auditRefs + .filter(({ weight, id }) => weight > 0 && audits[id].score < 1) + .map((ref) => { + const audit = audits[ref.id]; + return ` '${chalk.cyan( + audit.title, + )}' received a score of ${chalk.yellow(audit.score)}`; + }) + .join('\n'); + + return { message: categoryError, details: categoryAudits }; +}; + +const formatShortSummary = ({ categories, runtimeError }) => { + if (runtimeError) { + return runtimeError.message; + } + return categories + .map(({ title, score }) => `${title}: ${Math.round(score * 100)}`) + .join(', '); +}; + +const formatResults = ({ results, thresholds }) => { + const hasScores = !results.lhr.runtimeError; + + const categories = Object.values(results.lhr.categories).map( + ({ title, score, id, auditRefs }) => ({ title, score, id, auditRefs }), + ); + + const categoriesBelowThreshold = + hasScores && + Object.entries(thresholds).filter(([id, expected]) => + belowThreshold(id, expected, categories), + ); + + const errors = + hasScores && + categoriesBelowThreshold.map(([id, expected]) => + getError(id, expected, categories, results.lhr.audits), + ); + + const summary = + hasScores && + categories.map(({ title, score, id }) => ({ + title, + score, + id, + ...(thresholds[id] ? { threshold: thresholds[id] } : {}), + })); + + const shortSummary = formatShortSummary({ + categories, + runtimeError: results.lhr.runtimeError, + }); + + const formattedReport = makeReplacements(results.report); + + // Pull some additional details to pass to App + const { formFactor, locale } = results.lhr.configSettings; + const installable = results.lhr.audits['installable-manifest']?.score === 1; + const details = { installable, formFactor, locale }; + + const report = minify(formattedReport, { + removeAttributeQuotes: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + removeOptionalTags: true, + removeEmptyElements: true, + minifyCSS: true, + minifyJS: true, + }); + + return { summary, shortSummary, details, report, errors }; +}; + +module.exports = { + belowThreshold, + getError, + formatShortSummary, + formatResults, +}; diff --git a/src/format.test.js b/src/format.test.js new file mode 100644 index 00000000..2c469380 --- /dev/null +++ b/src/format.test.js @@ -0,0 +1,215 @@ +const { + belowThreshold, + getError, + formatShortSummary, + formatResults, +} = require('./format'); + +describe('format', () => { + const getCategories = ({ score }) => [ + { + title: 'Performance', + score, + id: 'performance', + auditRefs: [ + { weight: 1, id: 'is-crawlable' }, + { weight: 1, id: 'robots-txt' }, + { weight: 1, id: 'tap-targets' }, + ], + }, + ]; + const audits = { + 'is-crawlable': { + id: 'is-crawlable', + title: 'Page isn’t blocked from indexing', + description: + "Search engines are unable to include your pages in search results if they don't have permission to crawl them. [Learn more](https://web.dev/is-crawable/).", + score: 1, + }, + 'robots-txt': { + id: 'robots-txt', + title: 'robots.txt is valid', + description: + 'If your robots.txt file is malformed, crawlers may not be able to understand how you want your website to be crawled or indexed. [Learn more](https://web.dev/robots-txt/).', + score: 0, + }, + 'tap-targets': { + id: 'tap-targets', + title: 'Tap targets are sized appropriately', + description: + 'Interactive elements like buttons and links should be large enough (48x48px), and have enough space around them, to be easy enough to tap without overlapping onto other elements. [Learn more](https://web.dev/tap-targets/).', + score: 0.5, + }, + }; + + // Awkward formatting as the strings contain ANSI escape codes for colours. + const formattedError = { + details: + " '\x1B[36mrobots.txt is valid\x1B[39m' received a score of \x1B[33m0\x1B[39m\n" + + " '\x1B[36mTap targets are sized appropriately\x1B[39m' received a score of \x1B[33m0.5\x1B[39m", + message: + 'Expected category \x1B[36mPerformance\x1B[39m to be greater or equal to \x1B[32m1\x1B[39m but got \x1B[31m0.5\x1B[39m', + }; + + it('returns an expected error message and list of details with valid score', () => { + const errorMessage = getError( + 'performance', + 1, + getCategories({ score: 0.5 }), + audits, + ); + expect(errorMessage).toEqual(formattedError); + }); + + describe('belowThreshold', () => { + const categories = [ + { title: 'Performance', score: 0.9, id: 'performance' }, + { title: 'Accessibility', score: 0.8, id: 'accessibility' }, + ]; + + it('returns false when the score is above the threshold', () => { + expect(belowThreshold('performance', 0.8, categories)).toBe(false); + }); + + it('returns false when the category is not found', () => { + console.warn = jest.fn(); + const result = belowThreshold('seo', 0.8, categories); + expect(console.warn).toHaveBeenCalled(); + expect(result).toBe(false); + }); + + it('returns true when the score is below the threshold', () => { + expect(belowThreshold('performance', 1, categories)).toBe(true); + }); + }); + + describe('getError', () => { + it('returns an expected error message and list of details without valid score', () => { + const errorMessage = getError( + 'performance', + 1, + getCategories({ score: null }), + audits, + ); + // Matching is awkward as the strings contain ANSI escape codes for colours. + expect(errorMessage.message).toContain( + 'to be greater or equal to \x1B[32m1\x1B[39m but got \x1B[31munknown\x1B[39m', + ); + }); + }); + + describe('formatShortSummary', () => { + const categories = [ + { title: 'Performance', score: 1, id: 'performance' }, + { title: 'Accessibility', score: 0.9, id: 'accessibility' }, + { title: 'Best Practices', score: 0.8, id: 'best-practices' }, + { title: 'SEO', score: 0.7, id: 'seo' }, + { title: 'PWA', score: 0.6, id: 'pwa' }, + ]; + const runtimeError = { + code: 'NO_FCP', + message: + 'The page did not paint any content. Please ensure you keep the browser window in the foreground during the load and try again. (NO_FCP)', + }; + + it('should return a shortSummary containing scores if available', () => { + const shortSummary = formatShortSummary({ categories }); + expect(shortSummary).toEqual( + 'Performance: 100, Accessibility: 90, Best Practices: 80, SEO: 70, PWA: 60', + ); + }); + + it('should return a shortSummary error message', () => { + const shortSummary = formatShortSummary({ + runtimeError, + }); + expect(shortSummary).toEqual(runtimeError.message); + }); + }); + + describe('formatResults', () => { + const getResults = () => ({ + lhr: { + lighthouseVersion: '9.6.3', + requestedUrl: 'http://localhost:5100/404.html', + finalUrl: 'http://localhost:5100/404.html', + audits, + configSettings: {}, + categories: getCategories({ score: 0.5 }), + }, + artifacts: {}, + report: '\n' + 'Hi\n', + }); + + it('should return formatted results', () => { + expect(formatResults({ results: getResults(), thresholds: {} })).toEqual({ + details: { + formFactor: undefined, + installable: false, + locale: undefined, + }, + errors: [], + report: 'Hi', + shortSummary: 'Performance: 50', + summary: [{ id: 'performance', score: 0.5, title: 'Performance' }], + }); + }); + + it('should return formatted results with passing thresholds', () => { + const thresholds = { + performance: 0.1, + }; + const formattedResults = formatResults({ + results: getResults(), + thresholds, + }); + expect(formattedResults.errors).toEqual([]); + expect(formattedResults.summary).toEqual([ + { + id: 'performance', + score: 0.5, + title: 'Performance', + threshold: 0.1, + }, + ]); + }); + + it('should return formatted results with failing thresholds', () => { + const thresholds = { + performance: 1, + }; + const formattedResults = formatResults({ + results: getResults(), + thresholds, + }); + expect(formattedResults.errors).toEqual([formattedError]); + expect(formattedResults.summary).toEqual([ + { + id: 'performance', + score: 0.5, + title: 'Performance', + threshold: 1, + }, + ]); + }); + + it('should use supplied config settings and data to populate `details`', () => { + const results = getResults(); + results.lhr.configSettings = { + locale: 'es', + formFactor: 'desktop', + }; + results.lhr.audits['installable-manifest'] = { + id: 'installable-manifest', + score: 1, + }; + + const formattedResults = formatResults({ results, thresholds: {} }); + expect(formattedResults.details).toEqual({ + formFactor: 'desktop', + installable: true, + locale: 'es', + }); + }); + }); +}); diff --git a/src/index.js b/src/index.js index dcf14451..a45bd64e 100644 --- a/src/index.js +++ b/src/index.js @@ -4,11 +4,10 @@ const express = require('express'); const compression = require('compression'); const chalk = require('chalk'); const fs = require('fs').promises; -const minify = require('html-minifier').minify; const { getConfiguration } = require('./config'); const { getSettings } = require('./settings'); const { getBrowserPath, runLighthouse } = require('./lighthouse'); -const { makeReplacements } = require('./replacements'); +const { formatResults } = require('./format'); const getServer = ({ serveDir, auditUrl }) => { if (auditUrl) { @@ -47,81 +46,6 @@ const getServer = ({ serveDir, auditUrl }) => { return { server }; }; -const belowThreshold = (id, expected, categories) => { - const category = categories.find((c) => c.id === id); - if (!category) { - console.warn(`Could not find category ${chalk.yellow(id)}`); - } - const actual = category ? category.score : Number.MAX_SAFE_INTEGER; - return actual < expected; -}; - -const getError = (id, expected, categories, audits) => { - const category = categories.find((c) => c.id === id); - - const categoryError = `Expected category ${chalk.cyan( - category.title, - )} to be greater or equal to ${chalk.green(expected)} but got ${chalk.red( - category.score !== null ? category.score : 'unknown', - )}`; - - const categoryAudits = category.auditRefs - .filter(({ weight, id }) => weight > 0 && audits[id].score < 1) - .map((ref) => { - const audit = audits[ref.id]; - return ` '${chalk.cyan( - audit.title, - )}' received a score of ${chalk.yellow(audit.score)}`; - }) - .join('\n'); - - return { message: categoryError, details: categoryAudits }; -}; - -const formatResults = ({ results, thresholds }) => { - const categories = Object.values(results.lhr.categories).map( - ({ title, score, id, auditRefs }) => ({ title, score, id, auditRefs }), - ); - - const categoriesBelowThreshold = Object.entries(thresholds).filter( - ([id, expected]) => belowThreshold(id, expected, categories), - ); - - const errors = categoriesBelowThreshold.map(([id, expected]) => - getError(id, expected, categories, results.lhr.audits), - ); - - const summary = categories.map(({ title, score, id }) => ({ - title, - score, - id, - ...(thresholds[id] ? { threshold: thresholds[id] } : {}), - })); - - const shortSummary = categories - .map(({ title, score }) => `${title}: ${Math.round(score * 100)}`) - .join(', '); - - const formattedReport = makeReplacements(results.report); - - // Pull some additional details to pass to App - const { formFactor, locale } = results.lhr.configSettings; - const installable = results.lhr.audits['installable-manifest'].score === 1; - const details = { installable, formFactor, locale }; - - const report = minify(formattedReport, { - removeAttributeQuotes: true, - collapseWhitespace: true, - removeRedundantAttributes: true, - removeOptionalTags: true, - removeEmptyElements: true, - minifyCSS: true, - minifyJS: true, - }); - - return { summary, shortSummary, details, report, errors }; -}; - const persistResults = async ({ report, path }) => { await fs.mkdir(dirname(path), { recursive: true }); await fs.writeFile(path, report); @@ -299,7 +223,9 @@ module.exports = { console.log( `Report collected: audited_uri: '${chalk.magenta( url || fullPath, - )}', html_report_size: ${chalk.magenta(size / 1024)} KiB`, + )}', html_report_size: ${chalk.magenta( + +(size / 1024).toFixed(2), + )} KiB`, ); } diff --git a/src/lighthouse.js b/src/lighthouse.js index 56bb14ef..e009fd81 100644 --- a/src/lighthouse.js +++ b/src/lighthouse.js @@ -54,9 +54,6 @@ const runLighthouse = async (browserPath, url, settings) => { }, settings, ); - if (results.lhr.runtimeError) { - throw new Error(results.lhr.runtimeError.message); - } return results; } finally { if (chrome) { From 1109cb3bbbf1ecbda0cdcabf2644e8c64c2052fe Mon Sep 17 00:00:00 2001 From: Jack Brewer Date: Wed, 26 Oct 2022 14:16:40 +0100 Subject: [PATCH 2/5] keep runtimeError out of shortSummary --- src/format.js | 50 +++++++++++---------------- src/format.test.js | 14 +------- src/index.js | 84 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 78 insertions(+), 70 deletions(-) diff --git a/src/format.js b/src/format.js index 7055dca9..36ec7b8f 100644 --- a/src/format.js +++ b/src/format.js @@ -33,47 +33,35 @@ const getError = (id, expected, categories, audits) => { return { message: categoryError, details: categoryAudits }; }; -const formatShortSummary = ({ categories, runtimeError }) => { - if (runtimeError) { - return runtimeError.message; - } +const formatShortSummary = (categories) => { return categories .map(({ title, score }) => `${title}: ${Math.round(score * 100)}`) .join(', '); }; const formatResults = ({ results, thresholds }) => { - const hasScores = !results.lhr.runtimeError; + const runtimeError = results.lhr.runtimeError; const categories = Object.values(results.lhr.categories).map( ({ title, score, id, auditRefs }) => ({ title, score, id, auditRefs }), ); - const categoriesBelowThreshold = - hasScores && - Object.entries(thresholds).filter(([id, expected]) => - belowThreshold(id, expected, categories), - ); - - const errors = - hasScores && - categoriesBelowThreshold.map(([id, expected]) => - getError(id, expected, categories, results.lhr.audits), - ); - - const summary = - hasScores && - categories.map(({ title, score, id }) => ({ - title, - score, - id, - ...(thresholds[id] ? { threshold: thresholds[id] } : {}), - })); - - const shortSummary = formatShortSummary({ - categories, - runtimeError: results.lhr.runtimeError, - }); + const categoriesBelowThreshold = Object.entries(thresholds).filter( + ([id, expected]) => belowThreshold(id, expected, categories), + ); + + const errors = categoriesBelowThreshold.map(([id, expected]) => + getError(id, expected, categories, results.lhr.audits), + ); + + const summary = categories.map(({ title, score, id }) => ({ + title, + score, + id, + ...(thresholds[id] ? { threshold: thresholds[id] } : {}), + })); + + const shortSummary = formatShortSummary(categories); const formattedReport = makeReplacements(results.report); @@ -92,7 +80,7 @@ const formatResults = ({ results, thresholds }) => { minifyJS: true, }); - return { summary, shortSummary, details, report, errors }; + return { summary, shortSummary, details, report, errors, runtimeError }; }; module.exports = { diff --git a/src/format.test.js b/src/format.test.js index 2c469380..b1590c91 100644 --- a/src/format.test.js +++ b/src/format.test.js @@ -106,25 +106,13 @@ describe('format', () => { { title: 'SEO', score: 0.7, id: 'seo' }, { title: 'PWA', score: 0.6, id: 'pwa' }, ]; - const runtimeError = { - code: 'NO_FCP', - message: - 'The page did not paint any content. Please ensure you keep the browser window in the foreground during the load and try again. (NO_FCP)', - }; it('should return a shortSummary containing scores if available', () => { - const shortSummary = formatShortSummary({ categories }); + const shortSummary = formatShortSummary(categories); expect(shortSummary).toEqual( 'Performance: 100, Accessibility: 90, Best Practices: 80, SEO: 70, PWA: 60', ); }); - - it('should return a shortSummary error message', () => { - const shortSummary = formatShortSummary({ - runtimeError, - }); - expect(shortSummary).toEqual(runtimeError.message); - }); }); describe('formatResults', () => { diff --git a/src/index.js b/src/index.js index a45bd64e..2d6b7989 100644 --- a/src/index.js +++ b/src/index.js @@ -52,6 +52,12 @@ const persistResults = async ({ report, path }) => { }; const getUtils = ({ utils }) => { + // This function checks to see if we're running within the Netlify Build system, + // and if so, we use the util functions. If not, we're likely running locally + // so fall back using console.log to emulate the output. + + // If available, fails the Netlify build with the supplied message + // https://docs.netlify.com/integrations/build-plugins/create-plugins/#error-reporting const failBuild = (utils && utils.build && utils.build.failBuild) || ((message, { error } = {}) => { @@ -59,6 +65,8 @@ const getUtils = ({ utils }) => { process.exitCode = 1; }); + // If available, displays the summary in the Netlify UI Deploy Summary section + // https://docs.netlify.com/integrations/build-plugins/create-plugins/#logging const show = (utils && utils.status && utils.status.show) || (({ summary }) => console.log(summary)); @@ -94,10 +102,11 @@ const runAudit = async ({ if (error) { return { error }; } else { - const { summary, shortSummary, details, report, errors } = formatResults({ - results, - thresholds, - }); + const { summary, shortSummary, details, report, errors, runtimeError } = + formatResults({ + results, + thresholds, + }); if (output_path) { await persistResults({ report, path: join(serveDir, output_path) }); @@ -109,6 +118,7 @@ const runAudit = async ({ details, report, errors, + runtimeError, }; } } catch (error) { @@ -161,28 +171,45 @@ const processResults = ({ data, errors }) => { return { error: err, summary: data - .map(({ path, url, summary, shortSummary, details, report }) => { - const obj = { report, details }; + .map( + ({ + path, + url, + summary, + shortSummary, + details, + report, + runtimeError, + }) => { + const obj = { report, details }; - if (summary) { - obj.summary = summary.reduce((acc, item) => { - acc[item.id] = Math.round(item.score * 100); - return acc; - }, {}); - } + if (!runtimeError && summary) { + obj.summary = summary.reduce((acc, item) => { + acc[item.id] = Math.round(item.score * 100); + return acc; + }, {}); + } - if (path) { - obj.path = path; - reports.push(obj); - return `Summary for path '${chalk.magenta(path)}': ${shortSummary}`; - } - if (url) { - obj.url = url; - reports.push(obj); - return `Summary for url '${chalk.magenta(url)}': ${shortSummary}`; - } - return `${shortSummary}`; - }) + if (runtimeError) { + reports.push(obj); + return `Error testing '${chalk.magenta(path || url)}': ${ + runtimeError.message + }`; + } + + if (path) { + obj.path = path; + reports.push(obj); + return `Summary for path '${chalk.magenta(path)}': ${shortSummary}`; + } + if (url) { + obj.url = url; + reports.push(obj); + return `Summary for url '${chalk.magenta(url)}': ${shortSummary}`; + } + return `${shortSummary}`; + }, + ) .join('\n'), extraData: reports, }; @@ -204,7 +231,7 @@ module.exports = { const allErrors = []; const data = []; for (const { serveDir, path, url, thresholds, output_path } of audits) { - const { errors, summary, shortSummary, details, report } = + const { errors, summary, shortSummary, details, report, runtimeError } = await runAudit({ serveDir, path, @@ -213,9 +240,13 @@ module.exports = { output_path, settings, }); - if (summary) { + + if (summary && !runtimeError) { console.log({ results: summary }); } + if (runtimeError) { + console.log({ runtimeError }); + } const fullPath = [serveDir, path].join('/'); if (report) { @@ -239,6 +270,7 @@ module.exports = { shortSummary, details, report, + runtimeError, }); } From f7d63d1df19ed355bf68276396f900b18a7c18d1 Mon Sep 17 00:00:00 2001 From: Jack Brewer Date: Thu, 27 Oct 2022 15:38:47 +0100 Subject: [PATCH 3/5] Remove colours from strings during tests --- src/format.test.js | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/format.test.js b/src/format.test.js index b1590c91..7cfa326f 100644 --- a/src/format.test.js +++ b/src/format.test.js @@ -5,6 +5,16 @@ const { formatResults, } = require('./format'); +// Strip ANSI color codes from strings, as they make CI sad. +const sanitizeString = (str) => { + return str + .replaceAll('\x1B[31m', '') + .replaceAll('\x1B[32m', '') + .replaceAll('\x1B[33m', '') + .replaceAll('\x1B[36m', '') + .replaceAll('\x1B[39m', ''); +}; + describe('format', () => { const getCategories = ({ score }) => [ { @@ -42,13 +52,12 @@ describe('format', () => { }, }; - // Awkward formatting as the strings contain ANSI escape codes for colours. const formattedError = { details: - " '\x1B[36mrobots.txt is valid\x1B[39m' received a score of \x1B[33m0\x1B[39m\n" + - " '\x1B[36mTap targets are sized appropriately\x1B[39m' received a score of \x1B[33m0.5\x1B[39m", + " 'robots.txt is valid' received a score of 0\n" + + " 'Tap targets are sized appropriately' received a score of 0.5", message: - 'Expected category \x1B[36mPerformance\x1B[39m to be greater or equal to \x1B[32m1\x1B[39m but got \x1B[31m0.5\x1B[39m', + 'Expected category Performance to be greater or equal to 1 but got 0.5', }; it('returns an expected error message and list of details with valid score', () => { @@ -58,7 +67,12 @@ describe('format', () => { getCategories({ score: 0.5 }), audits, ); - expect(errorMessage).toEqual(formattedError); + expect(sanitizeString(errorMessage.details)).toEqual( + formattedError.details, + ); + expect(sanitizeString(errorMessage.message)).toEqual( + formattedError.message, + ); }); describe('belowThreshold', () => { @@ -92,8 +106,8 @@ describe('format', () => { audits, ); // Matching is awkward as the strings contain ANSI escape codes for colours. - expect(errorMessage.message).toContain( - 'to be greater or equal to \x1B[32m1\x1B[39m but got \x1B[31munknown\x1B[39m', + expect(sanitizeString(errorMessage.message)).toContain( + 'to be greater or equal to 1 but got unknown', ); }); }); @@ -170,7 +184,12 @@ describe('format', () => { results: getResults(), thresholds, }); - expect(formattedResults.errors).toEqual([formattedError]); + expect(sanitizeString(formattedResults.errors[0].message)).toEqual( + formattedError.message, + ); + expect(sanitizeString(formattedResults.errors[0].details)).toEqual( + formattedError.details, + ); expect(formattedResults.summary).toEqual([ { id: 'performance', From dbd8f9a490b160df298c969dcd089db2f56e797c Mon Sep 17 00:00:00 2001 From: Jack Brewer Date: Thu, 27 Oct 2022 16:05:15 +0100 Subject: [PATCH 4/5] Now with added node 14 support --- src/format.test.js | 50 +++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/format.test.js b/src/format.test.js index 7cfa326f..617885e4 100644 --- a/src/format.test.js +++ b/src/format.test.js @@ -6,14 +6,11 @@ const { } = require('./format'); // Strip ANSI color codes from strings, as they make CI sad. -const sanitizeString = (str) => { - return str - .replaceAll('\x1B[31m', '') - .replaceAll('\x1B[32m', '') - .replaceAll('\x1B[33m', '') - .replaceAll('\x1B[36m', '') - .replaceAll('\x1B[39m', ''); -}; +const stripAnsiCodes = (str) => + str.replace( + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, + '', + ); describe('format', () => { const getCategories = ({ score }) => [ @@ -60,21 +57,6 @@ describe('format', () => { 'Expected category Performance to be greater or equal to 1 but got 0.5', }; - it('returns an expected error message and list of details with valid score', () => { - const errorMessage = getError( - 'performance', - 1, - getCategories({ score: 0.5 }), - audits, - ); - expect(sanitizeString(errorMessage.details)).toEqual( - formattedError.details, - ); - expect(sanitizeString(errorMessage.message)).toEqual( - formattedError.message, - ); - }); - describe('belowThreshold', () => { const categories = [ { title: 'Performance', score: 0.9, id: 'performance' }, @@ -98,6 +80,21 @@ describe('format', () => { }); describe('getError', () => { + it('returns an expected error message and list of details with valid score', () => { + const errorMessage = getError( + 'performance', + 1, + getCategories({ score: 0.5 }), + audits, + ); + expect(stripAnsiCodes(errorMessage.details)).toEqual( + formattedError.details, + ); + expect(stripAnsiCodes(errorMessage.message)).toEqual( + formattedError.message, + ); + }); + it('returns an expected error message and list of details without valid score', () => { const errorMessage = getError( 'performance', @@ -105,8 +102,7 @@ describe('format', () => { getCategories({ score: null }), audits, ); - // Matching is awkward as the strings contain ANSI escape codes for colours. - expect(sanitizeString(errorMessage.message)).toContain( + expect(stripAnsiCodes(errorMessage.message)).toContain( 'to be greater or equal to 1 but got unknown', ); }); @@ -184,10 +180,10 @@ describe('format', () => { results: getResults(), thresholds, }); - expect(sanitizeString(formattedResults.errors[0].message)).toEqual( + expect(stripAnsiCodes(formattedResults.errors[0].message)).toEqual( formattedError.message, ); - expect(sanitizeString(formattedResults.errors[0].details)).toEqual( + expect(stripAnsiCodes(formattedResults.errors[0].details)).toEqual( formattedError.details, ); expect(formattedResults.summary).toEqual([ From f190323a740685b8fdc6715943b369eafb19f03d Mon Sep 17 00:00:00 2001 From: Jack Brewer Date: Thu, 27 Oct 2022 16:07:39 +0100 Subject: [PATCH 5/5] =?UTF-8?q?linting=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/format.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/format.test.js b/src/format.test.js index 617885e4..59afb1fe 100644 --- a/src/format.test.js +++ b/src/format.test.js @@ -8,6 +8,7 @@ const { // Strip ANSI color codes from strings, as they make CI sad. const stripAnsiCodes = (str) => str.replace( + // eslint-disable-next-line no-control-regex /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '', );