diff --git a/src/index.ts b/src/index.ts index 26aedf6..a92ee6d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,7 @@ import { parseVitestJsonFinal, parseVitestJsonSummary, } from "./inputs/parseJsonReports.js"; -import { createOctokit, type Octokit } from "./octokit.js"; +import { type Octokit, createOctokit } from "./octokit.js"; import { generateCommitSHAUrl } from "./report/generateCommitSHAUrl.js"; import { generateFileCoverageHtml } from "./report/generateFileCoverageHtml.js"; import { generateHeadline } from "./report/generateHeadline.js"; diff --git a/src/inputs/getCommentOn.test.ts b/src/inputs/getCommentOn.test.ts index 936ed9c..e687592 100644 --- a/src/inputs/getCommentOn.test.ts +++ b/src/inputs/getCommentOn.test.ts @@ -1,6 +1,6 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import * as core from "@actions/core"; -import { getCommentOn, type CommentOn } from "./getCommentOn"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { type CommentOn, getCommentOn } from "./getCommentOn"; vi.mock("@actions/core"); diff --git a/src/inputs/getPullChanges.test.ts b/src/inputs/getPullChanges.test.ts index 5e873a3..a378e68 100644 --- a/src/inputs/getPullChanges.test.ts +++ b/src/inputs/getPullChanges.test.ts @@ -1,6 +1,6 @@ +import { RequestError } from "@octokit/request-error"; import { Mock, beforeEach, describe, expect, it, vi } from "vitest"; import type { Octokit } from "../octokit"; -import { RequestError } from "@octokit/request-error"; import { FileCoverageMode } from "./FileCoverageMode"; import { getPullChanges } from "./getPullChanges"; @@ -68,48 +68,48 @@ describe("getPullChanges()", () => { expect(result).toEqual(["file1.ts", "file2.ts"]); }); - it("handles RequestError with status 404 gracefully", async () => { - mockOctokit.paginate.iterator = vi.fn().mockImplementation(async function* () { - throw new RequestError("Not Found", 404, { - request: { headers: {}, method: "GET", url: "" }, - }); + it("handles RequestError with status 404 gracefully", async () => { + mockOctokit.paginate.iterator = vi.fn().mockImplementation(async () => { + throw new RequestError("Not Found", 404, { + request: { headers: {}, method: "GET", url: "" }, }); - - const result = await getPullChanges({ - fileCoverageMode: FileCoverageMode.Changes, - prNumber: 123, - octokit: mockOctokit, - }); - - expect(result).toEqual([]); }); - it("handles RequestError with status 403 gracefully", async () => { - mockOctokit.paginate.iterator = vi.fn().mockImplementation(async function* () { - throw new RequestError("Forbidden", 403, { - request: { headers: {}, method: "GET", url: "" }, - }); - }); - - const result = await getPullChanges({ - fileCoverageMode: FileCoverageMode.Changes, - prNumber: 123, - octokit: mockOctokit, - }); - - expect(result).toEqual([]); + const result = await getPullChanges({ + fileCoverageMode: FileCoverageMode.Changes, + prNumber: 123, + octokit: mockOctokit, }); - it("throws an error for other exceptions", async () => { - mockOctokit.paginate.iterator = vi.fn().mockImplementation(async function* () { - throw new Error("Unexpected error"); + expect(result).toEqual([]); + }); + + it("handles RequestError with status 403 gracefully", async () => { + mockOctokit.paginate.iterator = vi.fn().mockImplementation(async () => { + throw new RequestError("Forbidden", 403, { + request: { headers: {}, method: "GET", url: "" }, }); - await expect( - getPullChanges({ - fileCoverageMode: FileCoverageMode.Changes, - prNumber: 123, - octokit: mockOctokit, - }), - ).rejects.toThrow("Unexpected error"); }); + + const result = await getPullChanges({ + fileCoverageMode: FileCoverageMode.Changes, + prNumber: 123, + octokit: mockOctokit, + }); + + expect(result).toEqual([]); + }); + + it("throws an error for other exceptions", async () => { + mockOctokit.paginate.iterator = vi.fn().mockImplementation(async () => { + throw new Error("Unexpected error"); + }); + await expect( + getPullChanges({ + fileCoverageMode: FileCoverageMode.Changes, + prNumber: 123, + octokit: mockOctokit, + }), + ).rejects.toThrow("Unexpected error"); + }); }); diff --git a/src/inputs/options.ts b/src/inputs/options.ts index 12948f2..e1aa355 100644 --- a/src/inputs/options.ts +++ b/src/inputs/options.ts @@ -3,11 +3,11 @@ import * as core from "@actions/core"; import type { Octokit } from "../octokit"; import type { Thresholds } from "../types/Threshold"; import { type FileCoverageMode, getCoverageModeFrom } from "./FileCoverageMode"; +import { type CommentOn, getCommentOn } from "./getCommentOn"; import { getCommitSHA } from "./getCommitSHA"; import { getPullRequestNumber } from "./getPullRequestNumber"; import { getViteConfigPath } from "./getViteConfigPath"; import { parseCoverageThresholds } from "./parseCoverageThresholds"; -import { getCommentOn, type CommentOn } from "./getCommentOn"; type Options = { fileCoverageMode: FileCoverageMode; diff --git a/src/report/generateFileCoverageHtml.test.ts b/src/report/generateFileCoverageHtml.test.ts index fce7ef4..a92e437 100644 --- a/src/report/generateFileCoverageHtml.test.ts +++ b/src/report/generateFileCoverageHtml.test.ts @@ -1,6 +1,7 @@ import * as path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { getTableLine } from "../../test/queryHelper"; +import { icons } from "../icons"; import { FileCoverageMode } from "../inputs/FileCoverageMode"; import type { JsonFinal } from "../types/JsonFinal"; import { createJsonFinalEntry } from "../types/JsonFinalMockFactory"; @@ -11,7 +12,6 @@ import { createMockReportNumbers, } from "../types/JsonSummaryMockFactory"; import { generateFileCoverageHtml } from "./generateFileCoverageHtml"; -import { icons } from "../icons"; const workspacePath = process.cwd(); describe("generateFileCoverageHtml()", () => { diff --git a/src/report/generateFileCoverageHtml.ts b/src/report/generateFileCoverageHtml.ts index a45e5a8..3025726 100644 --- a/src/report/generateFileCoverageHtml.ts +++ b/src/report/generateFileCoverageHtml.ts @@ -4,12 +4,11 @@ import { FileCoverageMode } from "../inputs/FileCoverageMode"; import type { JsonFinal } from "../types/JsonFinal"; import type { CoverageReport, JsonSummary } from "../types/JsonSummary"; import { generateBlobFileUrl } from "./generateFileUrl"; +import { getCompareString } from "./getCompareString"; import { type LineRange, getUncoveredLinesFromStatements, } from "./getUncoveredLinesFromStatements"; -import { icons } from "../icons"; -import { getCompareString } from "./getCompareString"; type FileCoverageInputs = { jsonSummary: JsonSummary; @@ -21,6 +20,7 @@ type FileCoverageInputs = { }; const workspacePath = process.cwd(); + const generateFileCoverageHtml = ({ jsonSummary, jsonSummaryCompare, @@ -31,31 +31,6 @@ const generateFileCoverageHtml = ({ }: FileCoverageInputs) => { const filePaths = Object.keys(jsonSummary).filter((key) => key !== "total"); - const formatFileLine = (filePath: string) => { - const coverageSummary = jsonSummary[filePath]; - const coverageSummaryCompare = jsonSummaryCompare - ? jsonSummaryCompare[filePath] - : undefined; - const lineCoverage = jsonFinal[filePath]; - - // LineCoverage might be empty if coverage-final.json was not provided. - const uncoveredLines = lineCoverage - ? getUncoveredLinesFromStatements(jsonFinal[filePath]) - : []; - const relativeFilePath = path.relative(workspacePath, filePath); - const url = generateBlobFileUrl(relativeFilePath, commitSHA); - - return ` - - ${relativeFilePath} - ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "statements")} - ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "branches")} - ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "functions")} - ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "lines")} - ${createRangeURLs(uncoveredLines, url)} - `; - }; - let reportData = ""; const [changedFiles, unchangedFiles] = splitFilesByChangeStatus( @@ -72,37 +47,88 @@ const generateFileCoverageHtml = ({ if (changedFiles.length > 0) { reportData += ` - ${formatGroupLine("Changed Files")} - ${changedFiles.map(formatFileLine).join("")} - `; + ${formatGroupLine("Changed Files")} + ${changedFiles + .map((filePath) => + generateRow( + filePath, + jsonSummary, + jsonSummaryCompare, + jsonFinal, + commitSHA, + ), + ) + .join("")} + `; } if (fileCoverageMode === FileCoverageMode.All && unchangedFiles.length > 0) { reportData += ` - ${formatGroupLine("Unchanged Files")} - ${unchangedFiles.map(formatFileLine).join("")} - `; + ${formatGroupLine("Unchanged Files")} + ${unchangedFiles + .map((filePath) => + generateRow( + filePath, + jsonSummary, + undefined, + jsonFinal, + commitSHA, + ), + ) + .join("")} + `; } return oneLine` - - - - - - - - - - - - - ${reportData} - -
FileStmts% Branch% Funcs% LinesUncovered Lines
- `; + + + + + + + + + + + + + ${reportData} + +
FileStmts% Branch% Funcs% LinesUncovered Lines
+ `; }; +function generateRow( + filePath: string, + jsonSummary: JsonSummary, + jsonSummaryCompare: JsonSummary | undefined, + jsonFinal: JsonFinal, + commitSHA: string, +): string { + const coverageSummary = jsonSummary[filePath]; + const coverageSummaryCompare = jsonSummaryCompare + ? jsonSummaryCompare[filePath] + : undefined; + const lineCoverage = jsonFinal[filePath]; + + // LineCoverage might be empty if coverage-final.json was not provided. + const uncoveredLines = lineCoverage + ? getUncoveredLinesFromStatements(jsonFinal[filePath]) + : []; + const relativeFilePath = path.relative(workspacePath, filePath); + const url = generateBlobFileUrl(relativeFilePath, commitSHA); + + return ` + + ${relativeFilePath} + ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "statements")} + ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "branches")} + ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "functions")} + ${generateCoverageCell(coverageSummary, coverageSummaryCompare, "lines")} + ${createRangeURLs(uncoveredLines, url)} + `; +} + function generateCoverageCell( summary: CoverageReport, summaryCompare: CoverageReport | undefined, @@ -118,9 +144,9 @@ function generateCoverageCell( function formatGroupLine(caption: string): string { return ` - - ${caption} - + + ${caption} + `; }