danger-plugin-coverage
Advanced tools
Comparing version
@@ -93,4 +93,3 @@ "use strict"; | ||
const getShortPath = filePath => { | ||
const maxChars = 40; | ||
const getShortPath = (filePath, maxChars) => { | ||
const parts = filePath.split('/').reverse().filter(x => x); | ||
@@ -104,10 +103,14 @@ | ||
let currentChars = 0; | ||
parts.forEach(part => { | ||
if (currentChars < maxChars) { | ||
parts.forEach((part, index) => { | ||
const isLastPart = parts.length - 1 === index; | ||
currentChars += part.length + 1; // +1 for the path seperator | ||
const prefixLength = !isLastPart ? 3 : 0; | ||
if (currentChars + prefixLength < maxChars) { | ||
shortParts.push(part); | ||
currentChars += part.length + 1; // +1 for the path seperator | ||
} | ||
}); | ||
if (shortParts.length < parts.length - 1) { | ||
if (shortParts.length < parts.length) { | ||
shortParts.push('..'); | ||
@@ -119,2 +122,26 @@ } | ||
/** | ||
* Insert whitespace into a path so that it wraps within the Markdown table. | ||
*/ | ||
const getWrappedPath = filePath => { | ||
const parts = filePath.split('/'); | ||
const maxPerLine = 25; | ||
let currentChars = 0; | ||
return parts.map((pathPart, i) => { | ||
var _parts; | ||
const isLastPart = parts.length - 1 === i; | ||
const newPart = isLastPart ? pathPart : `${pathPart}/`; | ||
currentChars += pathPart.length; | ||
if (currentChars + (((_parts = parts[i + 1]) === null || _parts === void 0 ? void 0 : _parts.length) || 0) > maxPerLine) { | ||
currentChars = 0; | ||
return `${newPart}<br>`; | ||
} | ||
return newPart; | ||
}).join(''); | ||
}; | ||
/** | ||
* Check if we have passed the thresholds for the given percentages. | ||
@@ -135,3 +162,8 @@ */ | ||
const buildRow = (file, threshold) => { | ||
const buildRow = (file, { | ||
threshold, | ||
maxChars, | ||
maxUncovered, | ||
wrapFilenames | ||
}) => { | ||
var _danger$git, _danger$git$commits; | ||
@@ -144,7 +176,8 @@ | ||
const filePath = _path.default.relative(process.cwd(), file.$.path); | ||
const longPath = _path.default.relative(process.cwd(), file.$.path); | ||
const shortPath = getShortPath(filePath); | ||
const fileLink = `../blob/${sha}/${filePath}`; | ||
const fileCell = sha ? `[${shortPath}](${fileLink})` : shortPath; | ||
const shortPath = getShortPath(longPath, maxChars); | ||
const readablePath = wrapFilenames ? getWrappedPath(shortPath) : shortPath; | ||
const fileLink = `../blob/${sha}/${longPath}`; | ||
const fileCell = sha ? `[${readablePath}](${fileLink})` : readablePath; | ||
const percentages = getMetricPercentages(fileMetrics); | ||
@@ -158,3 +191,2 @@ const noLines = !fileMetrics.lines; | ||
const maxUncovered = 3; | ||
let uncoveredCell = fileMetrics.uncoveredLines.slice(0, maxUncovered).map(line => { | ||
@@ -183,3 +215,7 @@ const lineNumber = line.$.num; | ||
const buildTable = (files, maxRows, threshold, showAllFiles) => { | ||
const buildTable = (files, opts) => { | ||
const { | ||
maxRows, | ||
showAllFiles | ||
} = opts; | ||
const headings = [`${showAllFiles ? '' : 'Impacted '}Files`, '% Stmts', '% Branch', '% Funcs', '% Lines', 'Uncovered Lines', '']; | ||
@@ -189,3 +225,3 @@ const headingRow = joinRow(headings); | ||
], [])); | ||
const allFileRows = files.map(file => buildRow(file, threshold)); | ||
const allFileRows = files.map(file => buildRow(file, opts)); | ||
const mainFileRows = allFileRows.slice(0, maxRows); | ||
@@ -220,3 +256,7 @@ const extraFileRows = allFileRows.slice(maxRows); | ||
const buildSummary = (metrics, successMessage, failureMessage, threshold) => { | ||
const buildSummary = (metrics, { | ||
successMessage, | ||
failureMessage, | ||
threshold | ||
}) => { | ||
const percentages = getMetricPercentages(metrics); | ||
@@ -249,3 +289,5 @@ const passed = hasPassed(threshold, percentages); | ||
const getRelevantFiles = (coverageXml, showAllFiles) => { | ||
const getRelevantFiles = (coverageXml, { | ||
showAllFiles | ||
}) => { | ||
var _danger$git2, _danger$git3; | ||
@@ -268,20 +310,25 @@ | ||
const coverage = async ({ | ||
successMessage = ':+1: Test coverage is looking good.', | ||
failureMessage = 'Test coverage is looking a little low for the files created ' + 'or modified in this PR, perhaps we need to improve this.', | ||
cloverReportPath, | ||
maxRows = 5, | ||
showAllFiles = false, | ||
warnOnNoReport = true, | ||
threshold = { | ||
statements: 80, | ||
branches: 80, | ||
functions: 80, | ||
lines: 80 | ||
} | ||
} = {}) => { | ||
const coverageXml = await (0, _report.getCoverageReport)(cloverReportPath); | ||
const coverage = async (initialOpts = {}) => { | ||
const opts = { | ||
successMessage: ':+1: Test coverage is looking good.', | ||
failureMessage: 'Test coverage is looking a little low for the files created ' + 'or modified in this PR, perhaps we need to improve this.', | ||
cloverReportPath: null, | ||
maxRows: 3, | ||
maxChars: 100, | ||
maxUncovered: 10, | ||
wrapFilenames: true, | ||
showAllFiles: false, | ||
warnOnNoReport: true, | ||
threshold: { | ||
statements: 80, | ||
branches: 80, | ||
functions: 80, | ||
lines: 80 | ||
}, | ||
...initialOpts | ||
}; | ||
const coverageXml = await (0, _report.getCoverageReport)(opts.cloverReportPath); | ||
if (!coverageXml) { | ||
if (warnOnNoReport) { | ||
if (opts.warnOnNoReport) { | ||
warn('No coverage report was detected. ' + 'Please output a report in the `clover.xml` format before running danger'); | ||
@@ -293,3 +340,3 @@ } | ||
const relevantFiles = getRelevantFiles(coverageXml, showAllFiles); | ||
const relevantFiles = getRelevantFiles(coverageXml, opts); | ||
@@ -301,4 +348,4 @@ if (!relevantFiles.length) { | ||
const combinedMetrics = getCombinedMetrics(relevantFiles); | ||
const table = buildTable(relevantFiles, maxRows, threshold, showAllFiles); | ||
const summary = buildSummary(combinedMetrics, successMessage, failureMessage, threshold); | ||
const table = buildTable(relevantFiles, opts); | ||
const summary = buildSummary(combinedMetrics, opts); | ||
const report = ['## Coverage Report', summary, table].join(newLine + newLine); | ||
@@ -305,0 +352,0 @@ markdown(report); |
{ | ||
"name": "danger-plugin-coverage", | ||
"version": "1.5.0", | ||
"version": "1.6.0", | ||
"description": "A Danger plugin to report code coverage.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -86,2 +86,5 @@ # danger-plugin-coverage | ||
| `maxRows` | The number of rows to show (additional rows will be collapsed within a `<details>` element). | | ||
| `maxChars` | The maximum number of characters to allow in a file name cell. | | ||
| `maxUncovered` | The maximum number of uncovered lines to show. | | ||
| `wrapFilenames` | Wrap long file names to help the table fit in a PR comment. | | ||
| `threshold` | The thresholds at which to show the failure messaging. | | ||
@@ -100,3 +103,6 @@ | `warnOnNoReport` | Show a warning if no coverage report was detected. | | ||
cloverReportPath: './coverage/clover.xml', | ||
maxRows: 5, | ||
maxRows: 3, | ||
maxChars: 100, | ||
maxUncovered: 10, | ||
wrapFilenames: true, | ||
warnOnNoReport: true, | ||
@@ -103,0 +109,0 @@ showAllFiles: false, |
@@ -76,4 +76,3 @@ import path from 'path'; | ||
*/ | ||
const getShortPath = (filePath) => { | ||
const maxChars = 40; | ||
const getShortPath = (filePath, maxChars) => { | ||
const parts = filePath.split('/').reverse().filter((x) => x); | ||
@@ -89,10 +88,13 @@ | ||
parts.forEach((part) => { | ||
if (currentChars < maxChars) { | ||
parts.forEach((part, index) => { | ||
const isLastPart = parts.length - 1 === index; | ||
currentChars += part.length + 1; // +1 for the path seperator | ||
const prefixLength = (!isLastPart ? 3 : 0); | ||
if (currentChars + prefixLength < maxChars) { | ||
shortParts.push(part); | ||
currentChars += part.length + 1; // +1 for the path seperator | ||
} | ||
}); | ||
if (shortParts.length < parts.length - 1) { | ||
if (shortParts.length < parts.length) { | ||
shortParts.push('..'); | ||
@@ -105,2 +107,24 @@ } | ||
/** | ||
* Insert whitespace into a path so that it wraps within the Markdown table. | ||
*/ | ||
const getWrappedPath = (filePath) => { | ||
const parts = filePath.split('/'); | ||
const maxPerLine = 25; | ||
let currentChars = 0; | ||
return parts.map((pathPart, i) => { | ||
const isLastPart = parts.length - 1 === i; | ||
const newPart = isLastPart ? pathPart : `${pathPart}/`; | ||
currentChars += pathPart.length; | ||
if (currentChars + (parts[i + 1]?.length || 0) > maxPerLine) { | ||
currentChars = 0; | ||
return `${newPart}<br>`; | ||
} | ||
return newPart; | ||
}).join(''); | ||
}; | ||
/** | ||
* Check if we have passed the thresholds for the given percentages. | ||
@@ -123,11 +147,19 @@ */ | ||
*/ | ||
const buildRow = (file, threshold) => { | ||
const buildRow = (file, { | ||
threshold, | ||
maxChars, | ||
maxUncovered, | ||
wrapFilenames, | ||
}) => { | ||
const fileMetrics = getFileMetrics(file); | ||
const { sha } = danger.git?.commits?.[danger.git.commits.length - 1] || {}; | ||
const filePath = path.relative(process.cwd(), file.$.path); | ||
const shortPath = getShortPath(filePath); | ||
const fileLink = `../blob/${sha}/${filePath}`; | ||
const fileCell = sha ? `[${shortPath}](${fileLink})` : shortPath; | ||
const longPath = path.relative(process.cwd(), file.$.path); | ||
const shortPath = getShortPath(longPath, maxChars); | ||
const readablePath = wrapFilenames ? getWrappedPath(shortPath) : shortPath; | ||
const fileLink = `../blob/${sha}/${longPath}`; | ||
const fileCell = sha ? `[${readablePath}](${fileLink})` : readablePath; | ||
const percentages = getMetricPercentages(fileMetrics); | ||
@@ -142,3 +174,2 @@ | ||
const maxUncovered = 3; | ||
let uncoveredCell = fileMetrics | ||
@@ -180,3 +211,8 @@ .uncoveredLines | ||
*/ | ||
const buildTable = (files, maxRows, threshold, showAllFiles) => { | ||
const buildTable = (files, opts) => { | ||
const { | ||
maxRows, | ||
showAllFiles, | ||
} = opts; | ||
const headings = [ | ||
@@ -200,3 +236,3 @@ `${showAllFiles ? '' : 'Impacted '}Files`, | ||
const allFileRows = files.map((file) => buildRow(file, threshold)); | ||
const allFileRows = files.map((file) => buildRow(file, opts)); | ||
const mainFileRows = allFileRows.slice(0, maxRows); | ||
@@ -245,3 +281,3 @@ const extraFileRows = allFileRows.slice(maxRows); | ||
*/ | ||
const buildSummary = (metrics, successMessage, failureMessage, threshold) => { | ||
const buildSummary = (metrics, { successMessage, failureMessage, threshold }) => { | ||
const percentages = getMetricPercentages(metrics); | ||
@@ -288,3 +324,3 @@ const passed = hasPassed(threshold, percentages); | ||
*/ | ||
const getRelevantFiles = (coverageXml, showAllFiles) => { | ||
const getRelevantFiles = (coverageXml, { showAllFiles }) => { | ||
const files = getFlatFiles(coverageXml); | ||
@@ -310,21 +346,27 @@ const allFiles = [ | ||
*/ | ||
export const coverage = async ({ | ||
successMessage = ':+1: Test coverage is looking good.', | ||
failureMessage = 'Test coverage is looking a little low for the files created ' | ||
+ 'or modified in this PR, perhaps we need to improve this.', | ||
cloverReportPath, | ||
maxRows = 5, | ||
showAllFiles = false, | ||
warnOnNoReport = true, | ||
threshold = { | ||
statements: 80, | ||
branches: 80, | ||
functions: 80, | ||
lines: 80, | ||
}, | ||
} = {}) => { | ||
const coverageXml = await getCoverageReport(cloverReportPath); | ||
export const coverage = async (initialOpts = {}) => { | ||
const opts = { | ||
successMessage: ':+1: Test coverage is looking good.', | ||
failureMessage: 'Test coverage is looking a little low for the files created ' | ||
+ 'or modified in this PR, perhaps we need to improve this.', | ||
cloverReportPath: null, | ||
maxRows: 3, | ||
maxChars: 100, | ||
maxUncovered: 10, | ||
wrapFilenames: true, | ||
showAllFiles: false, | ||
warnOnNoReport: true, | ||
threshold: { | ||
statements: 80, | ||
branches: 80, | ||
functions: 80, | ||
lines: 80, | ||
}, | ||
...initialOpts, | ||
}; | ||
const coverageXml = await getCoverageReport(opts.cloverReportPath); | ||
if (!coverageXml) { | ||
if (warnOnNoReport) { | ||
if (opts.warnOnNoReport) { | ||
warn('No coverage report was detected. ' | ||
@@ -336,3 +378,3 @@ + 'Please output a report in the `clover.xml` format before running danger'); | ||
const relevantFiles = getRelevantFiles(coverageXml, showAllFiles); | ||
const relevantFiles = getRelevantFiles(coverageXml, opts); | ||
@@ -344,4 +386,4 @@ if (!relevantFiles.length) { | ||
const combinedMetrics = getCombinedMetrics(relevantFiles); | ||
const table = buildTable(relevantFiles, maxRows, threshold, showAllFiles); | ||
const summary = buildSummary(combinedMetrics, successMessage, failureMessage, threshold); | ||
const table = buildTable(relevantFiles, opts); | ||
const summary = buildSummary(combinedMetrics, opts); | ||
const report = [ | ||
@@ -348,0 +390,0 @@ '## Coverage Report', |
@@ -228,3 +228,3 @@ import path from 'path'; | ||
expect(lines).toContain(successMessage); | ||
expect(lines).toContain('and 5 more...'); | ||
expect(lines).toContain('and 7 more...'); | ||
}); | ||
@@ -298,4 +298,4 @@ | ||
it('shortens long paths', async () => { | ||
const seg = 'xxxxxxxxxx'; | ||
it('shortens and wraps long paths', async () => { | ||
const seg = new Array(10).fill('x').join(''); | ||
const longPath = new Array(10).fill(seg).join('/'); | ||
@@ -325,3 +325,3 @@ const file = getFileXml(longPath, DEFAULT_METRICS, [DEFAULT_LINE]); | ||
expect(lines).toContain( | ||
`|../${seg}/${seg}/${seg}/${seg}|100|100|100|100||:white_check_mark:|`, | ||
`|../${seg}/${seg}/<br>${seg}/${seg}/<br>${seg}/${seg}/<br>${seg}/${seg}|100|100|100|100||:white_check_mark:|`, | ||
); | ||
@@ -452,33 +452,14 @@ }); | ||
it('reports uncovered lines', async () => { | ||
const fileOne = getFileXml(path.join(process.cwd(), '/src/one.js'), DEFAULT_METRICS, [ | ||
{ | ||
num: 1, | ||
count: 0, | ||
type: 'stmt', | ||
}, | ||
{ | ||
num: 2, | ||
count: 0, | ||
type: 'stmt', | ||
}, | ||
{ | ||
num: 3, | ||
count: 0, | ||
type: 'stmt', | ||
}, | ||
{ | ||
num: 4, | ||
count: 0, | ||
type: 'stmt', | ||
}, | ||
]); | ||
const getUncoveredLine = (num) => ({ | ||
num, | ||
count: 0, | ||
type: 'stmt', | ||
}); | ||
const fileTwo = getFileXml(path.join(process.cwd(), '/src/two.js'), DEFAULT_METRICS, [ | ||
{ | ||
num: 1, | ||
count: 0, | ||
type: 'stmt', | ||
}, | ||
]); | ||
const fileOneLines = new Array(11).fill().map((_, i) => getUncoveredLine(i + 1)); | ||
const fileOne = getFileXml(path.join(process.cwd(), '/src/one.js'), DEFAULT_METRICS, fileOneLines); | ||
const fileTwoLines = [getUncoveredLine(1)]; | ||
const fileTwo = getFileXml(path.join(process.cwd(), '/src/two.js'), DEFAULT_METRICS, fileTwoLines); | ||
const xmlReport = wrapXmlReport([ | ||
@@ -509,3 +490,3 @@ fileOne, | ||
expect(report).toMatchSnapshot(); | ||
expect(lines).toContain('|src/one.js|100|100|100|0|1, 2, 3...|:x:|'); | ||
expect(lines).toContain('|src/one.js|100|100|100|0|1, 2, 3, 4, 5, 6, 7, 8, 9, 10...|:x:|'); | ||
expect(lines).toContain('|src/two.js|100|100|100|0|1|:x:|'); | ||
@@ -512,0 +493,0 @@ }); |
@@ -201,2 +201,28 @@ import path from 'path'; | ||
}); | ||
it('makes paths even shorter', async () => { | ||
const longPath = 'ab/cd/ef/gh/ij/kl/mn'; // 20 chars | ||
const file = getFileXml(longPath, DEFAULT_METRICS, [DEFAULT_LINE]); | ||
const xmlReport = wrapXmlReport(file); | ||
mockFs({ | ||
[CLOVER_PATH]: xmlReport, | ||
}); | ||
Object.assign(danger, { | ||
git: { | ||
created_files: [longPath], | ||
modified_files: [], | ||
}, | ||
}); | ||
await coverage({ maxChars: 10 }); | ||
const report = getMarkdownReport(); | ||
const lines = report.split('\n'); | ||
expect(report).toMatchSnapshot(); | ||
expect(lines).toContain('|../kl/mn|100|100|100|100||:white_check_mark:|'); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
58353
5.89%1394
5.45%116
5.45%