@epicat/toon-reporter 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -74,6 +74,40 @@ skipped[2]{at,name}:
74
74
  src/utils.test.ts,handles edge case
75
75
  ```
76
76
 
77
+ ### With coverage
78
+
79
+ Coverage is automatically included when running with `--coverage`. No extra configuration needed:
80
+
81
+ ```bash
82
+ npx vitest run --coverage --reporter=@epicat/toon-reporter
83
+ ```
84
+
85
+ Output includes total percentages and uncovered lines per file:
86
+
87
+ ```
88
+ passing: 8
89
+ coverage:
90
+ "total%":
91
+ lines: 60.99
92
+ stmts: 58.82
93
+ branch: 44.34
94
+ funcs: 57.57
95
+ files[1]{file,uncoveredLines}:
96
+ src/toon-reporter.ts,"12-14,19-23,32,99"
97
+ ```
98
+
99
+ - **Total percentages**: Help teams track coverage thresholds
100
+ - **Per-file uncovered lines**: Give LLMs actionable info to improve coverage
101
+ - **100% covered files are hidden** by default to reduce noise
102
+
103
+ With `verbose: true`, all files appear with per-file percentages:
104
+
105
+ ```
106
+ files[2]{file,uncoveredLines,"lines%","stmts%","branch%","funcs%"}:
107
+ src/toon-reporter.ts,"12-14,19-23,32,99",56.89,55.63,43.63,61.53
108
+ test/test-utils.ts,"",100,100,100,100
109
+ ```
110
+
77
111
  ## Colors
78
112
 
79
113
  - **Green**: `passing` count
@@ -122,6 +156,14 @@ Write report to a file instead of stdout.
122
156
  reporters: [['toon-reporter', { outputFile: 'test-results.txt' }]]
123
157
  ```
124
158
 
159
+ ### `verbose`
160
+
161
+ Include per-file coverage percentages (lines, stmts, branch, funcs) alongside uncovered lines.
162
+
163
+ ```ts
164
+ reporters: [new ToonReporter({ verbose: true })]
165
+ ```
166
+
125
167
  ## Skipped/Todo Line Numbers
126
168
 
127
169
  To get line:column information for skipped and todo tests, enable `includeTaskLocation` in your vitest config:
@@ -151,6 +193,7 @@ Traditional test reporters output verbose information optimized for human readab
151
193
  - Pass count
152
194
  - Failure locations with expected/got values
153
195
  - Skipped/todo test names for context
196
+ - Coverage totals and uncovered lines (when `--coverage` is enabled)
154
197
 
155
198
  ## Token Efficiency
156
199
 
package/dist/index.d.mts CHANGED
@@ -5,6 +5,8 @@ import { Reporter, TestRunEndReason, Vitest } from "vitest/node";
5
5
  interface ToonReporterOptions {
6
6
  outputFile?: string;
7
7
  color?: boolean;
8
+ /** Include per-file coverage percentages (lines, stmts, branch, funcs) */
9
+ verbose?: boolean;
8
10
  /** @internal Used for testing to capture output */
9
11
  _captureOutput?: (output: string) => void;
10
12
  }
@@ -13,8 +15,12 @@ declare class ToonReporter implements Reporter {
13
15
  ctx: Vitest;
14
16
  options: ToonReporterOptions;
15
17
  private useColor;
18
+ private coverageMap;
16
19
  constructor(options?: ToonReporterOptions);
17
20
  onInit(ctx: Vitest): void;
21
+ onCoverage(coverage: unknown): void;
22
+ private getCoverageSummary;
23
+ private formatLineRanges;
18
24
  private formatLocation;
19
25
  private parseErrorLocation;
20
26
  private parseExpectedGot;
package/dist/index.mjs CHANGED
@@ -54,6 +54,61 @@ var ToonReporter = class {
54
54
  onInit(ctx) {
55
55
  this.ctx = ctx;
56
56
  this.start = Date.now();
57
+ this.coverageMap = void 0;
58
+ }
59
+ onCoverage(coverage) {
60
+ this.coverageMap = coverage;
61
+ }
62
+ getCoverageSummary() {
63
+ if (!this.coverageMap) return void 0;
64
+ const map = this.coverageMap;
65
+ if (typeof map.getCoverageSummary !== "function") return void 0;
66
+ const summary = map.getCoverageSummary().toJSON();
67
+ const rootDir = this.ctx.config.root;
68
+ const result = { "total%": {
69
+ lines: summary.lines?.pct ?? 0,
70
+ stmts: summary.statements?.pct ?? 0,
71
+ branch: summary.branches?.pct ?? 0,
72
+ funcs: summary.functions?.pct ?? 0
73
+ } };
74
+ if (map.files && map.fileCoverageFor) {
75
+ const entries = [];
76
+ const verbose = this.options.verbose;
77
+ for (const file of map.files()) {
78
+ const fc = map.fileCoverageFor(file);
79
+ const uncoveredLines = fc.getUncoveredLines();
80
+ const hasGaps = uncoveredLines.length > 0;
81
+ if (!verbose && !hasGaps) continue;
82
+ const entry = {
83
+ file: relative(rootDir, file),
84
+ uncoveredLines: this.formatLineRanges(uncoveredLines)
85
+ };
86
+ if (verbose) {
87
+ const fileSummary = fc.toSummary().toJSON();
88
+ entry["lines%"] = fileSummary.lines?.pct ?? 0;
89
+ entry["stmts%"] = fileSummary.statements?.pct ?? 0;
90
+ entry["branch%"] = fileSummary.branches?.pct ?? 0;
91
+ entry["funcs%"] = fileSummary.functions?.pct ?? 0;
92
+ }
93
+ entries.push(entry);
94
+ }
95
+ if (entries.length > 0) result.files = entries;
96
+ }
97
+ return result;
98
+ }
99
+ formatLineRanges(lines) {
100
+ if (lines.length === 0) return "";
101
+ const sorted = lines.map(Number).sort((a, b) => a - b);
102
+ const ranges = [];
103
+ let start = sorted[0];
104
+ let end = start;
105
+ for (let i = 1; i <= sorted.length; i++) if (sorted[i] === end + 1) end = sorted[i];
106
+ else {
107
+ ranges.push(start === end ? String(start) : `${start}-${end}`);
108
+ start = sorted[i];
109
+ end = start;
110
+ }
111
+ return ranges.join(",");
57
112
  }
58
113
  formatLocation(relPath, line, column) {
59
114
  return line ? `${relPath}:${line}:${column || 0}` : relPath;
@@ -135,11 +190,13 @@ var ToonReporter = class {
135
190
  if (failures.length > 0) report.failing = failures;
136
191
  if (todoTests.length > 0) report.todo = todoTests.map(mapToSkipped);
137
192
  if (skippedTests.length > 0) report.skipped = skippedTests.map(mapToSkipped);
193
+ const coverage = this.getCoverageSummary();
194
+ if (coverage) report.coverage = coverage;
138
195
  await this.writeReport(encode(report));
139
196
  }
140
197
  colorize(report) {
141
198
  if (!this.useColor) return report;
142
- return report.replace(/^(passing:)/m, colors.green("$1")).replace(/^(failing\[.*?\]:)/m, colors.red("$1")).replace(/^(todo\[.*?\]:)/m, colors.cyan("$1")).replace(/^(skipped\[.*?\]:)/m, colors.gray("$1")).replace(/(\S+\.test\.\w+:\d+:\d+)/g, colors.yellow("$1"));
199
+ return report.replace(/^(passing:)/m, colors.green("$1")).replace(/^(failing\[.*?\]:)/m, colors.red("$1")).replace(/^(todo\[.*?\]:)/m, colors.cyan("$1")).replace(/^(skipped\[.*?\]:)/m, colors.gray("$1")).replace(/at: "([^"]+)"/g, `at: "${colors.yellow("$1")}"`).replace(/("at",)([^,\n]+)/g, `$1${colors.yellow("$2")}`);
143
200
  }
144
201
  async writeReport(report) {
145
202
  if (this.options._captureOutput) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicat/toon-reporter",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "A minimal Vitest reporter optimized for LLM consumption",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,8 +28,11 @@
28
28
  "devDependencies": {
29
29
  "@changesets/cli": "^2.29.8",
30
30
  "@types/node": "^24.10.1",
31
+ "@vitest/coverage-v8": "^4.0.15",
31
32
  "@vitest/runner": "4.0.15",
32
33
  "@vitest/utils": "4.0.15",
34
+ "gpt-tokenizer": "^3.4.0",
35
+ "istanbul-lib-coverage": "^3.2.2",
33
36
  "tsdown": "^0.16.4",
34
37
  "typescript": "^5.9.3",
35
38
  "vitest": "4.0.15"