@epicat/toon-reporter 0.0.9 → 0.0.10

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
@@ -112,6 +112,27 @@ skipped[2]{at,name}:
112
112
  "src/utils.test.ts:8:3",handles edge case
113
113
  ```
114
114
 
115
+ ### With test name filtering
116
+
117
+ When using `--testNamePattern` or `-t`, results show a `(filtered)` indicator:
118
+
119
+ ```
120
+ passing: 5 (filtered)
121
+ skipped: 42
122
+ ```
123
+
124
+ ### With timing enabled
125
+
126
+ When `timing: true` is set, shows total duration and per-test timing:
127
+
128
+ ```
129
+ duration: 52ms
130
+ passing[3]{at,name,ms}:
131
+ "utils.test.ts:4:3",should be fast,1
132
+ "api.test.ts:12:3",should handle requests,50
133
+ "db.test.ts:8:3",should be slow,1
134
+ ```
135
+
115
136
  ### With flaky tests (Playwright)
116
137
 
117
138
  Tests that fail initially but pass on retry are reported as flaky:
@@ -160,9 +181,10 @@ With `verbose: true`, all files appear with per-file percentages:
160
181
 
161
182
  - **Green**: `passing` count
162
183
  - **Red**: `failing` header
163
- - **Yellow**: `flaky` header, file paths
164
- - **Gray**: `skipped` tests
184
+ - **Yellow**: `flaky` header, `skipped` count, file paths
185
+ - **Gray**: `skipped` tests (detailed list)
165
186
  - **Cyan**: `todo` tests
187
+ - **Purple**: `(filtered)` indicator when using `testNamePattern`
166
188
 
167
189
  Colors are enabled when:
168
190
  - `COLOR` environment variable is set, OR
@@ -212,11 +234,29 @@ Include per-file coverage percentages (lines, stmts, branch, funcs) alongside un
212
234
  reporters: [new ToonReporter({ verbose: true })]
213
235
  ```
214
236
 
215
- ## Skipped/Todo Line Numbers
237
+ ### `timing`
238
+
239
+ Show per-test timing information and total duration. Useful for identifying slow tests.
240
+
241
+ ```ts
242
+ reporters: [new ToonReporter({ timing: true })]
243
+ ```
244
+
245
+ Output:
246
+
247
+ ```
248
+ duration: 1m30s52ms
249
+ passing[3]{at,name,ms}:
250
+ "utils.test.ts:4:3",should be fast,1
251
+ "api.test.ts:12:3",should handle requests,50
252
+ "db.test.ts:8:3",should query slowly,90000
253
+ ```
254
+
255
+ ## Line Numbers
216
256
 
217
257
  ### Vitest
218
258
 
219
- To get line:column information for skipped and todo tests, enable `includeTaskLocation` in your vitest config:
259
+ To get line:column information for passing tests (with `timing: true`), skipped, and todo tests, enable `includeTaskLocation` in your vitest config:
220
260
 
221
261
  ```ts
222
262
  // vitest.config.ts
@@ -234,7 +274,9 @@ Or via CLI:
234
274
  npx vitest run --reporter=@epicat/toon-reporter --includeTaskLocation
235
275
  ```
236
276
 
237
- Without this option, skipped/todo tests will only show the file path (not line:column). This is a Vitest limitation - test locations are only collected when this config is enabled before test collection.
277
+ Without this option, passing tests (with `timing: true`), skipped, and todo tests will only show the file path (not line:column). This is a Vitest limitation - test locations are only collected when this config is enabled before test collection.
278
+
279
+ **Note:** Failing tests always include line:column from the error stack trace, regardless of this setting.
238
280
 
239
281
  ### Playwright
240
282
 
@@ -3,6 +3,8 @@ import { FullConfig, FullResult, Reporter, Suite } from "@playwright/test/report
3
3
  //#region src/toon-playwright-reporter.d.ts
4
4
  interface ToonPlaywrightReporterOptions {
5
5
  outputFile?: string;
6
+ /** When true, shows per-test timing in passing[N]{at,name,ms} format */
7
+ timing?: boolean;
6
8
  /** @internal Used for testing to capture output */
7
9
  _captureOutput?: (output: string) => void;
8
10
  }
@@ -3,6 +3,18 @@ import { dirname, relative, resolve } from "pathe";
3
3
  import { encode } from "@toon-format/toon";
4
4
 
5
5
  //#region src/toon-playwright-reporter.ts
6
+ function formatDuration(ms) {
7
+ const h = Math.floor(ms / 36e5);
8
+ const m = Math.floor(ms % 36e5 / 6e4);
9
+ const s = Math.floor(ms % 6e4 / 1e3);
10
+ const remaining = Math.round(ms % 1e3);
11
+ const parts = [];
12
+ if (h) parts.push(`${h}h`);
13
+ if (m) parts.push(`${m}m`);
14
+ if (s) parts.push(`${s}s`);
15
+ if (remaining || parts.length === 0) parts.push(`${remaining}ms`);
16
+ return parts.join("");
17
+ }
6
18
  var ToonPlaywrightReporter = class {
7
19
  constructor(options = {}) {
8
20
  this.options = options;
@@ -39,6 +51,10 @@ var ToonPlaywrightReporter = class {
39
51
  }
40
52
  async onEnd(result) {
41
53
  const allTests = this.suite.allTests();
54
+ if (allTests.length === 0) {
55
+ await this.writeReport(encode({ error: "No test files found" }));
56
+ return;
57
+ }
42
58
  const passedTests = [];
43
59
  const failedTests = [];
44
60
  const skippedTests = [];
@@ -78,12 +94,26 @@ var ToonPlaywrightReporter = class {
78
94
  name: test.title,
79
95
  retries: test.results.length - 1
80
96
  });
81
- const report = { passing: passedTests.length };
97
+ const mapToTiming = (test) => {
98
+ const lastResult = test.results[test.results.length - 1];
99
+ return {
100
+ at: this.formatLocation(test.location.file, test.location.line, test.location.column),
101
+ name: test.title,
102
+ ms: Math.round(lastResult?.duration ?? 0)
103
+ };
104
+ };
105
+ const report = {};
106
+ if (this.options.timing) {
107
+ report.duration = formatDuration(result.duration);
108
+ report.passing = passedTests.map(mapToTiming);
109
+ } else report.passing = passedTests.length;
82
110
  if (flakyTests.length > 0) report.flaky = flakyTests.map(mapToFlaky);
83
111
  if (failures.length > 0) report.failing = failures;
84
112
  if (todoTests.length > 0) report.todo = todoTests.map(mapToSkipped);
85
113
  if (skippedTests.length > 0) report.skipped = skippedTests.map(mapToSkipped);
86
- await this.writeReport(encode(report));
114
+ let output = encode(report);
115
+ if (!!(this.config.grep || this.config.grepInvert)) output = output.replace(/^(passing: .+)$/m, "$1 (filtered)");
116
+ await this.writeReport(output);
87
117
  }
88
118
  async writeReport(report) {
89
119
  if (this.options._captureOutput) {
@@ -92,7 +122,7 @@ var ToonPlaywrightReporter = class {
92
122
  }
93
123
  const outputFile = this.options.outputFile;
94
124
  if (outputFile) {
95
- const reportFile = resolve(this.rootDir, outputFile);
125
+ const reportFile = resolve(this.config.rootDir, outputFile);
96
126
  const outputDirectory = dirname(reportFile);
97
127
  if (!existsSync(outputDirectory)) await promises.mkdir(outputDirectory, { recursive: true });
98
128
  await promises.writeFile(reportFile, report, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicat/toon-reporter",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "A minimal Vitest reporter optimized for LLM consumption",
5
5
  "repository": {
6
6
  "type": "git",