1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
// Black-box tests for the TypeScript bundle coverage-summary script.
//
// The script ships into a project's .claude/scripts/ and runs via `node`, so
// these tests exercise the real CLI rather than importing it: build a temp tree
// (source files + an istanbul json-summary report), run the script against it,
// and assert on stdout. Run with `node --test`.
//
// Normal / Boundary / Error coverage at the behavior level: missing-file
// detection, the file-weighted number, all-tracked, ignoring test files and
// node_modules, and a missing-report error.
const { test } = require("node:test");
const assert = require("node:assert");
const { execFileSync } = require("node:child_process");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const SCRIPT = path.resolve(__dirname, "..", "claude", "scripts", "coverage-summary.js");
// fixture builds a temp project. sources maps relpath -> contents. report is the
// istanbul json-summary object, with per-file keys given as relpaths that get
// rewritten to absolute paths (matching how istanbul records them).
function fixture(sources, reportRel) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "tscov-"));
for (const [rel, body] of Object.entries(sources)) {
const full = path.join(root, rel);
fs.mkdirSync(path.dirname(full), { recursive: true });
fs.writeFileSync(full, body);
}
const report = { total: {} };
for (const [rel, stmts] of Object.entries(reportRel)) {
report[path.join(root, rel)] = {
statements: { total: stmts.total, covered: stmts.covered, pct: 0 },
lines: { total: stmts.total, covered: stmts.covered, pct: 0 },
};
}
const reportPath = path.join(root, "coverage-summary.json");
fs.writeFileSync(reportPath, JSON.stringify(report));
return { root, reportPath };
}
function run(reportPath, sourceDir, root) {
return execFileSync("node", [SCRIPT, reportPath, sourceDir, root], { encoding: "utf8" });
}
test("missing file is surfaced and counted 0%", () => {
const { root, reportPath } = fixture(
{ "src/a.ts": "export const a = 1;\n", "src/untested.ts": "export const u = 2;\n" },
{ "src/a.ts": { total: 2, covered: 2 } }, // a.ts 100%; untested.ts absent
);
const out = run(reportPath, path.join(root, "src"), root);
assert.match(out, /src\/untested\.ts/);
assert.match(out, /0%/);
assert.match(out, /50\.0%/); // a=100, untested missing=0 -> 50
});
test("all tracked, nothing missing", () => {
const { root, reportPath } = fixture(
{ "src/a.ts": "export const a = 1;\n" },
{ "src/a.ts": { total: 2, covered: 2 } },
);
const out = run(reportPath, path.join(root, "src"), root);
assert.match(out, /Not in coverage report: 0 file/);
assert.match(out, /100\.0%/);
});
test("test files and node_modules are not counted as source", () => {
const { root, reportPath } = fixture(
{
"src/a.ts": "export const a = 1;\n",
"src/a.test.ts": "import {a} from './a';\n",
"node_modules/dep/index.js": "module.exports = 1;\n",
},
{ "src/a.ts": { total: 2, covered: 2 } },
);
const out = run(reportPath, path.join(root, "src"), root);
assert.doesNotMatch(out, /a\.test\.ts/);
assert.doesNotMatch(out, /node_modules/);
assert.match(out, /100\.0%/);
});
test("missing report exits non-zero", () => {
const { root } = fixture({ "src/a.ts": "export const a = 1;\n" }, {});
assert.throws(() =>
execFileSync("node", [SCRIPT, path.join(root, "nope.json"), path.join(root, "src"), root], {
encoding: "utf8",
stdio: "pipe",
}),
);
});
|