@epicat/toon-reporter 0.0.7 → 0.0.9
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/dist/index.d.mts +3 -1
- package/dist/index.mjs +58 -15
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -5,8 +5,10 @@ import { Reporter, TestRunEndReason, Vitest } from "vitest/node";
|
|
|
5
5
|
interface ToonReporterOptions {
|
|
6
6
|
outputFile?: string;
|
|
7
7
|
color?: boolean;
|
|
8
|
-
/**
|
|
8
|
+
/** When true, shows all files and all coverage metrics. When false (default), only shows files with gaps and only metrics < 100% */
|
|
9
9
|
verbose?: boolean;
|
|
10
|
+
/** When true, shows per-test timing in passing[N]{at,name,ms} format */
|
|
11
|
+
timing?: boolean;
|
|
10
12
|
/** @internal Used for testing to capture output */
|
|
11
13
|
_captureOutput?: (output: string) => void;
|
|
12
14
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -38,13 +38,26 @@ const colors = {
|
|
|
38
38
|
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
39
39
|
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
40
40
|
gray: (s) => `\x1b[90m${s}\x1b[0m`,
|
|
41
|
-
cyan: (s) => `\x1b[36m${s}\x1b[0m
|
|
41
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
42
|
+
purple: (s) => `\x1b[35m${s}\x1b[0m`
|
|
42
43
|
};
|
|
43
44
|
function shouldUseColor(option) {
|
|
44
45
|
if (process.env.CI) return false;
|
|
45
46
|
if (option !== void 0) return option;
|
|
46
47
|
return !!process.env.COLOR;
|
|
47
48
|
}
|
|
49
|
+
function formatDuration(ms) {
|
|
50
|
+
const h = Math.floor(ms / 36e5);
|
|
51
|
+
const m = Math.floor(ms % 36e5 / 6e4);
|
|
52
|
+
const s = Math.floor(ms % 6e4 / 1e3);
|
|
53
|
+
const remaining = Math.round(ms % 1e3);
|
|
54
|
+
const parts = [];
|
|
55
|
+
if (h) parts.push(`${h}h`);
|
|
56
|
+
if (m) parts.push(`${m}m`);
|
|
57
|
+
if (s) parts.push(`${s}s`);
|
|
58
|
+
if (remaining || parts.length === 0) parts.push(`${remaining}ms`);
|
|
59
|
+
return parts.join("");
|
|
60
|
+
}
|
|
48
61
|
var ToonReporter = class {
|
|
49
62
|
constructor(options = {}) {
|
|
50
63
|
this.options = options;
|
|
@@ -76,19 +89,27 @@ var ToonReporter = class {
|
|
|
76
89
|
const verbose = this.options.verbose;
|
|
77
90
|
for (const file of map.files()) {
|
|
78
91
|
const fc = map.fileCoverageFor(file);
|
|
92
|
+
const fileSummary = fc.toSummary().toJSON();
|
|
79
93
|
const uncoveredLines = fc.getUncoveredLines();
|
|
80
|
-
const
|
|
81
|
-
|
|
94
|
+
const lines = fileSummary.lines?.pct ?? 100;
|
|
95
|
+
const stmts = fileSummary.statements?.pct ?? 100;
|
|
96
|
+
const branch = fileSummary.branches?.pct ?? 100;
|
|
97
|
+
const funcs = fileSummary.functions?.pct ?? 100;
|
|
98
|
+
if (!verbose && lines === 100 && stmts === 100 && branch === 100 && funcs === 100) continue;
|
|
82
99
|
const entry = {
|
|
83
100
|
file: relative(rootDir, file),
|
|
84
101
|
uncoveredLines: this.formatLineRanges(uncoveredLines)
|
|
85
102
|
};
|
|
86
103
|
if (verbose) {
|
|
87
|
-
|
|
88
|
-
entry["
|
|
89
|
-
entry["
|
|
90
|
-
entry["
|
|
91
|
-
|
|
104
|
+
entry["lines%"] = lines;
|
|
105
|
+
entry["stmts%"] = stmts;
|
|
106
|
+
entry["branch%"] = branch;
|
|
107
|
+
entry["funcs%"] = funcs;
|
|
108
|
+
} else {
|
|
109
|
+
if (lines < 100) entry["lines%"] = lines;
|
|
110
|
+
if (stmts < 100) entry["stmts%"] = stmts;
|
|
111
|
+
if (branch < 100) entry["branch%"] = branch;
|
|
112
|
+
if (funcs < 100) entry["funcs%"] = funcs;
|
|
92
113
|
}
|
|
93
114
|
entries.push(entry);
|
|
94
115
|
}
|
|
@@ -149,10 +170,12 @@ var ToonReporter = class {
|
|
|
149
170
|
return message;
|
|
150
171
|
}
|
|
151
172
|
buildReportForModules(modules) {
|
|
152
|
-
const
|
|
173
|
+
const files = modules.map((m) => m.task);
|
|
174
|
+
const tests = getTests(files);
|
|
153
175
|
const rootDir = this.ctx.config.root;
|
|
154
176
|
const failedTests = tests.filter((t) => t.result?.state === "fail");
|
|
155
|
-
const
|
|
177
|
+
const passedTests = tests.filter((t) => t.result?.state === "pass");
|
|
178
|
+
const passedCount = passedTests.length;
|
|
156
179
|
const skippedTests = tests.filter((t) => t.mode === "skip");
|
|
157
180
|
const todoTests = tests.filter((t) => t.mode === "todo");
|
|
158
181
|
const grouped = /* @__PURE__ */ new Map();
|
|
@@ -183,13 +206,30 @@ var ToonReporter = class {
|
|
|
183
206
|
at: this.formatLocation(relative(rootDir, t.file.filepath), t.location?.line, t.location?.column),
|
|
184
207
|
name: t.name
|
|
185
208
|
});
|
|
186
|
-
const report = {
|
|
209
|
+
const report = {};
|
|
210
|
+
if (this.options.timing) {
|
|
211
|
+
report.duration = formatDuration(files.reduce((sum, f) => sum + (f.result?.duration ?? 0), 0));
|
|
212
|
+
report.passing = passedTests.map((t) => ({
|
|
213
|
+
at: this.formatLocation(relative(rootDir, t.file.filepath), t.location?.line, t.location?.column),
|
|
214
|
+
name: t.name,
|
|
215
|
+
ms: Math.round(t.result?.duration ?? 0)
|
|
216
|
+
}));
|
|
217
|
+
} else report.passing = passedCount;
|
|
187
218
|
if (failures.length > 0) report.failing = failures;
|
|
188
|
-
if (
|
|
189
|
-
|
|
219
|
+
if (passedCount === 0 && failedTests.length === 0) {
|
|
220
|
+
if (todoTests.length > 0) report.todo = todoTests.length;
|
|
221
|
+
if (skippedTests.length > 0) report.skipped = skippedTests.length;
|
|
222
|
+
} else {
|
|
223
|
+
if (todoTests.length > 0) report.todo = todoTests.map(mapToSkipped);
|
|
224
|
+
if (skippedTests.length > 0) report.skipped = skippedTests.map(mapToSkipped);
|
|
225
|
+
}
|
|
190
226
|
return report;
|
|
191
227
|
}
|
|
192
228
|
async onTestRunEnd(testModules, _unhandledErrors, _reason) {
|
|
229
|
+
if (testModules.length === 0) {
|
|
230
|
+
await this.writeReport(encode({ error: "No test files found" }));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
193
233
|
const projectNames = /* @__PURE__ */ new Set();
|
|
194
234
|
const modulesByProject = /* @__PURE__ */ new Map();
|
|
195
235
|
for (const m of testModules) {
|
|
@@ -209,14 +249,17 @@ var ToonReporter = class {
|
|
|
209
249
|
else await this.writeReport(encode(projectReports));
|
|
210
250
|
return;
|
|
211
251
|
}
|
|
252
|
+
const testNamePattern = this.ctx.config.testNamePattern;
|
|
212
253
|
const report = this.buildReportForModules([...testModules]);
|
|
213
254
|
const coverage = this.getCoverageSummary();
|
|
214
255
|
if (coverage) report.coverage = coverage;
|
|
215
|
-
|
|
256
|
+
let output = encode(report);
|
|
257
|
+
if (testNamePattern) output = output.replace(/^(passing: .+)$/m, "$1 (filtered)");
|
|
258
|
+
await this.writeReport(output);
|
|
216
259
|
}
|
|
217
260
|
colorize(report) {
|
|
218
261
|
if (!this.useColor) return report;
|
|
219
|
-
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")}`);
|
|
262
|
+
return report.replace(/^(passing:)/m, colors.green("$1")).replace(/(\(filtered\))/g, colors.purple("$1")).replace(/^(failing\[.*?\]:)/m, colors.red("$1")).replace(/^(todo\[.*?\]:)/m, colors.cyan("$1")).replace(/^(todo:)/m, colors.cyan("$1")).replace(/^(skipped\[.*?\]:)/m, colors.gray("$1")).replace(/^(skipped:)/m, colors.yellow("$1")).replace(/at: "([^"]+)"/g, `at: "${colors.yellow("$1")}"`).replace(/("at",)([^,\n]+)/g, `$1${colors.yellow("$2")}`);
|
|
220
263
|
}
|
|
221
264
|
async writeReport(report) {
|
|
222
265
|
if (this.options._captureOutput) {
|