@epicat/toon-reporter 0.0.6 → 0.0.8

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 CHANGED
@@ -5,13 +5,12 @@ 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) */
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
10
  /** @internal Used for testing to capture output */
11
11
  _captureOutput?: (output: string) => void;
12
12
  }
13
13
  declare class ToonReporter implements Reporter {
14
- start: number;
15
14
  ctx: Vitest;
16
15
  options: ToonReporterOptions;
17
16
  private useColor;
@@ -25,6 +24,7 @@ declare class ToonReporter implements Reporter {
25
24
  private parseErrorLocation;
26
25
  private parseExpectedGot;
27
26
  private formatErrorMessage;
27
+ private buildReportForModules;
28
28
  onTestRunEnd(testModules: ReadonlyArray<any>, _unhandledErrors: ReadonlyArray<SerializedError>, _reason: TestRunEndReason): Promise<void>;
29
29
  private colorize;
30
30
  writeReport(report: string): Promise<void>;
package/dist/index.mjs CHANGED
@@ -47,13 +47,11 @@ function shouldUseColor(option) {
47
47
  }
48
48
  var ToonReporter = class {
49
49
  constructor(options = {}) {
50
- this.start = 0;
51
50
  this.options = options;
52
51
  this.useColor = shouldUseColor(options.color);
53
52
  }
54
53
  onInit(ctx) {
55
54
  this.ctx = ctx;
56
- this.start = Date.now();
57
55
  this.coverageMap = void 0;
58
56
  const coverage = ctx.config.coverage;
59
57
  if (coverage?.reporter) coverage.reporter = [];
@@ -78,19 +76,27 @@ var ToonReporter = class {
78
76
  const verbose = this.options.verbose;
79
77
  for (const file of map.files()) {
80
78
  const fc = map.fileCoverageFor(file);
79
+ const fileSummary = fc.toSummary().toJSON();
81
80
  const uncoveredLines = fc.getUncoveredLines();
82
- const hasGaps = uncoveredLines.length > 0;
83
- if (!verbose && !hasGaps) continue;
81
+ const lines = fileSummary.lines?.pct ?? 100;
82
+ const stmts = fileSummary.statements?.pct ?? 100;
83
+ const branch = fileSummary.branches?.pct ?? 100;
84
+ const funcs = fileSummary.functions?.pct ?? 100;
85
+ if (!verbose && lines === 100 && stmts === 100 && branch === 100 && funcs === 100) continue;
84
86
  const entry = {
85
87
  file: relative(rootDir, file),
86
88
  uncoveredLines: this.formatLineRanges(uncoveredLines)
87
89
  };
88
90
  if (verbose) {
89
- const fileSummary = fc.toSummary().toJSON();
90
- entry["lines%"] = fileSummary.lines?.pct ?? 0;
91
- entry["stmts%"] = fileSummary.statements?.pct ?? 0;
92
- entry["branch%"] = fileSummary.branches?.pct ?? 0;
93
- entry["funcs%"] = fileSummary.functions?.pct ?? 0;
91
+ entry["lines%"] = lines;
92
+ entry["stmts%"] = stmts;
93
+ entry["branch%"] = branch;
94
+ entry["funcs%"] = funcs;
95
+ } else {
96
+ if (lines < 100) entry["lines%"] = lines;
97
+ if (stmts < 100) entry["stmts%"] = stmts;
98
+ if (branch < 100) entry["branch%"] = branch;
99
+ if (funcs < 100) entry["funcs%"] = funcs;
94
100
  }
95
101
  entries.push(entry);
96
102
  }
@@ -150,8 +156,8 @@ var ToonReporter = class {
150
156
  if (name && name !== "Error" && !message.startsWith(name)) message = `${name}: ${message}`;
151
157
  return message;
152
158
  }
153
- async onTestRunEnd(testModules, _unhandledErrors, _reason) {
154
- const tests = getTests(testModules.map((m) => m.task));
159
+ buildReportForModules(modules) {
160
+ const tests = getTests(modules.map((m) => m.task));
155
161
  const rootDir = this.ctx.config.root;
156
162
  const failedTests = tests.filter((t) => t.result?.state === "fail");
157
163
  const passedCount = tests.filter((t) => t.result?.state === "pass").length;
@@ -165,20 +171,17 @@ var ToonReporter = class {
165
171
  const { expected, got } = this.parseExpectedGot(error);
166
172
  const relPath = loc?.relPath || relative(rootDir, t.file.filepath);
167
173
  const at = this.formatLocation(relPath, loc?.line || t.location?.line, loc?.column || t.location?.column);
174
+ const failureDetails = expected !== void 0 && got !== void 0 ? {
175
+ expected,
176
+ got
177
+ } : { error: this.formatErrorMessage(error) };
168
178
  if (t.each) {
169
179
  if (!grouped.has(at)) grouped.set(at, []);
170
- grouped.get(at).push(expected !== void 0 && got !== void 0 ? {
171
- expected,
172
- got
173
- } : { error: this.formatErrorMessage(error) });
174
- } else {
175
- const failure = { at };
176
- if (expected !== void 0 && got !== void 0) {
177
- failure.expected = expected;
178
- failure.got = got;
179
- } else if (error) failure.error = this.formatErrorMessage(error);
180
- failures.push(failure);
181
- }
180
+ grouped.get(at).push(failureDetails);
181
+ } else failures.push({
182
+ at,
183
+ ...failureDetails
184
+ });
182
185
  }
183
186
  for (const [at, params] of grouped) failures.push({
184
187
  at,
@@ -192,6 +195,29 @@ var ToonReporter = class {
192
195
  if (failures.length > 0) report.failing = failures;
193
196
  if (todoTests.length > 0) report.todo = todoTests.map(mapToSkipped);
194
197
  if (skippedTests.length > 0) report.skipped = skippedTests.map(mapToSkipped);
198
+ return report;
199
+ }
200
+ async onTestRunEnd(testModules, _unhandledErrors, _reason) {
201
+ const projectNames = /* @__PURE__ */ new Set();
202
+ const modulesByProject = /* @__PURE__ */ new Map();
203
+ for (const m of testModules) {
204
+ const projectName = m.project?.name || "";
205
+ projectNames.add(projectName);
206
+ if (!modulesByProject.has(projectName)) modulesByProject.set(projectName, []);
207
+ modulesByProject.get(projectName).push(m);
208
+ }
209
+ if (projectNames.size > 1) {
210
+ const projectReports = {};
211
+ for (const [projectName, modules] of modulesByProject) projectReports[projectName || "default"] = this.buildReportForModules(modules);
212
+ const coverage$1 = this.getCoverageSummary();
213
+ if (coverage$1) await this.writeReport(encode({
214
+ ...projectReports,
215
+ coverage: coverage$1
216
+ }));
217
+ else await this.writeReport(encode(projectReports));
218
+ return;
219
+ }
220
+ const report = this.buildReportForModules([...testModules]);
195
221
  const coverage = this.getCoverageSummary();
196
222
  if (coverage) report.coverage = coverage;
197
223
  await this.writeReport(encode(report));
@@ -12,7 +12,6 @@ declare class ToonPlaywrightReporter implements Reporter {
12
12
  private options;
13
13
  constructor(options?: ToonPlaywrightReporterOptions);
14
14
  onBegin(config: FullConfig, suite: Suite): void;
15
- private get rootDir();
16
15
  private formatLocation;
17
16
  private stripAnsi;
18
17
  private stripOuterQuotes;
@@ -11,11 +11,8 @@ var ToonPlaywrightReporter = class {
11
11
  this.config = config;
12
12
  this.suite = suite;
13
13
  }
14
- get rootDir() {
15
- return this.config.rootDir;
16
- }
17
14
  formatLocation(filePath, line, column) {
18
- const relPath = relative(this.rootDir, filePath);
15
+ const relPath = relative(process.cwd(), filePath);
19
16
  return line ? `${relPath}:${line}:${column || 0}` : relPath;
20
17
  }
21
18
  stripAnsi(str) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicat/toon-reporter",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "A minimal Vitest reporter optimized for LLM consumption",
5
5
  "repository": {
6
6
  "type": "git",