@epicat/toon-reporter 0.0.4 → 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 +43 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +57 -0
- package/package.json +4 -1
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,6 +190,8 @@ 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) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epicat/toon-reporter",
|
|
3
|
-
"version": "0.0.
|
|
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"
|