diff options
Diffstat (limited to 'languages/typescript/tests')
| -rw-r--r-- | languages/typescript/tests/coverage-summary.test.js | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/languages/typescript/tests/coverage-summary.test.js b/languages/typescript/tests/coverage-summary.test.js new file mode 100644 index 0000000..b2b6ea3 --- /dev/null +++ b/languages/typescript/tests/coverage-summary.test.js @@ -0,0 +1,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", + }), + ); +}); |
