|
| 1 | +// src/coverage-reporter.ts |
| 2 | +import fs from "fs"; |
| 3 | +import { ReportBase } from "istanbul-lib-report"; |
| 4 | +function isFull(metrics) { |
| 5 | + return metrics.statements.pct === 100 && metrics.branches.pct === 100 && metrics.functions.pct === 100 && metrics.lines.pct === 100; |
| 6 | +} |
| 7 | +function getUncoveredLines(node) { |
| 8 | + if (node.isSummary()) { |
| 9 | + return []; |
| 10 | + } |
| 11 | + const metrics = node.getCoverageSummary(false); |
| 12 | + const isEmpty = metrics.isEmpty(); |
| 13 | + const lines = isEmpty ? 0 : metrics.lines.pct; |
| 14 | + let coveredLines; |
| 15 | + const fileCoverage = node.getFileCoverage(); |
| 16 | + if (lines === 100) { |
| 17 | + const branches = fileCoverage.getBranchCoverageByLine(); |
| 18 | + coveredLines = Object.entries(branches).map(([key, { coverage }]) => [key, coverage === 100]); |
| 19 | + } else { |
| 20 | + coveredLines = Object.entries(fileCoverage.getLineCoverage()); |
| 21 | + } |
| 22 | + let newRange = true; |
| 23 | + const ranges = coveredLines.reduce((acum, [line, hit]) => { |
| 24 | + if (hit) { |
| 25 | + newRange = true; |
| 26 | + } else { |
| 27 | + const linenum = parseInt(line); |
| 28 | + if (newRange) { |
| 29 | + acum.push([linenum]); |
| 30 | + newRange = false; |
| 31 | + } else { |
| 32 | + acum[acum.length - 1][1] = linenum; |
| 33 | + } |
| 34 | + } |
| 35 | + return acum; |
| 36 | + }, []); |
| 37 | + return ranges; |
| 38 | +} |
| 39 | +var headers = ["Statements", "Branches", "Functions", "Lines"]; |
| 40 | +var GithubActionsCoverageReporter = class extends ReportBase { |
| 41 | + skipEmpty; |
| 42 | + skipFull; |
| 43 | + results = {}; |
| 44 | + cw = null; |
| 45 | + watermarks = null; |
| 46 | + constructor(opts) { |
| 47 | + super(opts); |
| 48 | + this.skipEmpty = Boolean(opts.skipEmpty); |
| 49 | + this.skipFull = Boolean(opts.skipFull); |
| 50 | + } |
| 51 | + onStart(_node, context) { |
| 52 | + if (!process.env.GITHUB_STEP_SUMMARY) { |
| 53 | + console.log("Reporter not being executed in Github Actions environment"); |
| 54 | + return; |
| 55 | + } |
| 56 | + this.cw = fs.createWriteStream(process.env.GITHUB_STEP_SUMMARY, { encoding: "utf-8", flags: "a" }); |
| 57 | + this.watermarks = context.watermarks; |
| 58 | + this.cw.write("<h2>Test Coverage</h2>"); |
| 59 | + this.cw.write("<table><thead><tr>"); |
| 60 | + for (const heading of ["File", ...headers, "Uncovered Lines"]) { |
| 61 | + this.cw.write(`<th>${heading}</th>`); |
| 62 | + } |
| 63 | + this.cw.write("</tr></thead><tbody>"); |
| 64 | + } |
| 65 | + onSummary(node) { |
| 66 | + const nodeName = node.getRelativeName() || "All Files"; |
| 67 | + const rawMetrics = node.getCoverageSummary(false); |
| 68 | + const isEmpty = rawMetrics.isEmpty(); |
| 69 | + if (this.skipEmpty && isEmpty) { |
| 70 | + return; |
| 71 | + } |
| 72 | + if (this.skipFull && isFull(rawMetrics)) { |
| 73 | + return; |
| 74 | + } |
| 75 | + this.results[nodeName] = { |
| 76 | + statements: isEmpty ? 0 : rawMetrics.statements.pct, |
| 77 | + branches: isEmpty ? 0 : rawMetrics.branches.pct, |
| 78 | + functions: isEmpty ? 0 : rawMetrics.functions.pct, |
| 79 | + lines: isEmpty ? 0 : rawMetrics.lines.pct, |
| 80 | + uncoveredLines: getUncoveredLines(node) |
| 81 | + }; |
| 82 | + } |
| 83 | + onDetail(node) { |
| 84 | + return this.onSummary(node); |
| 85 | + } |
| 86 | + formatter(pct, watermark) { |
| 87 | + if (!this.watermarks || this.watermarks[watermark] === void 0) return `<td>${pct}%</td>`; |
| 88 | + const [low, high] = this.watermarks[watermark]; |
| 89 | + if (pct < low) { |
| 90 | + return `<td><p style="color:red">${pct}%</p></td>`; |
| 91 | + } |
| 92 | + if (pct > high) { |
| 93 | + return `<td><p style="color:green">${pct}%</p></td>`; |
| 94 | + } |
| 95 | + return `<td><p style="color:yellow">${pct}%</p></td>`; |
| 96 | + } |
| 97 | + onEnd() { |
| 98 | + if (!this.cw) return; |
| 99 | + const fileNames = Object.keys(this.results).sort(); |
| 100 | + for (const fileName of fileNames) { |
| 101 | + const metrics = this.results[fileName]; |
| 102 | + this.cw.write(`<tr><td><code>${fileName}</code></td>`); |
| 103 | + this.cw.write(this.formatter(metrics.statements, "statements")); |
| 104 | + this.cw.write(this.formatter(metrics.branches, "branches")); |
| 105 | + this.cw.write(this.formatter(metrics.functions, "functions")); |
| 106 | + this.cw.write(this.formatter(metrics.lines, "lines")); |
| 107 | + if (metrics.uncoveredLines.length > 0) { |
| 108 | + this.cw.write("<td><details><summary>Expand</summary><ul>"); |
| 109 | + for (const range of metrics.uncoveredLines) { |
| 110 | + if (range.length === 1) { |
| 111 | + this.cw.write(`<li>${range[0]}</li>`); |
| 112 | + } else { |
| 113 | + this.cw.write(`<li>${range[0]}-${range[1]}</li>`); |
| 114 | + } |
| 115 | + } |
| 116 | + this.cw.write("</ul></details></td>"); |
| 117 | + } else { |
| 118 | + this.cw.write("<td></td>"); |
| 119 | + } |
| 120 | + this.cw.write("</tr>"); |
| 121 | + } |
| 122 | + this.cw.write("</tbody></table>"); |
| 123 | + this.cw.close(); |
| 124 | + } |
| 125 | +}; |
| 126 | +export { |
| 127 | + GithubActionsCoverageReporter as default |
| 128 | +}; |
0 commit comments