aboutsummaryrefslogtreecommitdiff
path: root/languages/typescript/tests/coverage-summary.test.js
blob: b2b6ea36773e0751d6c93e1641d71c44078f4741 (plain)
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",
    }),
  );
});