monocart-coverage-reports 2.13.2 → 3.1.0
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 +314 -116
- package/README.zh-Hans.md +315 -117
- package/lib/assets.js +6 -6
- package/lib/cli.js +191 -95
- package/lib/client/cdp-client.js +74 -43
- package/lib/client/coverage-client.js +24 -8
- package/lib/client/ws-session.js +115 -37
- package/lib/converter/ast-visitor.js +5 -7
- package/lib/converter/ast.js +29 -8
- package/lib/converter/collect-source-maps.js +42 -21
- package/lib/converter/converter.js +124 -129
- package/lib/converter/css-coverage.js +76 -0
- package/lib/converter/fake-source-coverage.js +64 -0
- package/lib/converter/find-original-range.js +214 -6
- package/lib/converter/flatten-source-maps.js +2 -4
- package/lib/converter/ignore.js +2 -4
- package/lib/converter/info-branch.js +5 -3
- package/lib/converter/info-function.js +5 -3
- package/lib/converter/info-statement.js +5 -3
- package/lib/converter/range-index.js +78 -0
- package/lib/converter/source-range-merge.js +103 -0
- package/lib/converter/untested.js +30 -60
- package/lib/default/options.js +13 -1
- package/lib/generate.js +44 -26
- package/lib/index.d.ts +52 -4
- package/lib/index.js +35 -41
- package/lib/istanbul/istanbul-summary.js +2 -2
- package/lib/istanbul/istanbul.js +35 -13
- package/lib/packages/monocart-coverage-assets.js +4 -3
- package/lib/packages/monocart-coverage-converter.js +27 -0
- package/lib/packages/monocart-coverage-vendor.js +2 -41
- package/lib/packages/monocart-coverage-vendor.js.LICENSE +3 -221
- package/lib/platform/concurrency.js +47 -43
- package/lib/platform/share.js +8 -1
- package/lib/register/hooks.js +19 -13
- package/lib/register/register.js +5 -3
- package/lib/reports/codacy.js +3 -5
- package/lib/reports/codecov.js +3 -5
- package/lib/reports/console-details.js +5 -5
- package/lib/reports/console-summary.js +4 -4
- package/lib/reports/custom.js +8 -24
- package/lib/reports/markdown-details.js +4 -6
- package/lib/reports/markdown-summary.js +4 -6
- package/lib/reports/raw.js +10 -12
- package/lib/reports/v8-json.js +3 -5
- package/lib/reports/v8.js +3 -5
- package/lib/utils/dedupe.js +1 -1
- package/lib/utils/gc.js +30 -14
- package/lib/utils/markdown.js +2 -2
- package/lib/utils/merge/merge.js +2 -4
- package/lib/utils/merge/range-tree.js +1 -1
- package/lib/utils/request.js +85 -73
- package/lib/utils/snapshot.js +4 -4
- package/lib/utils/source-path.js +26 -11
- package/lib/utils/util.js +67 -42
- package/lib/v8/v8-summary.js +2 -4
- package/lib/v8/v8.js +11 -11
- package/migrate-to-3.md +175 -0
- package/package.json +54 -42
- package/skills/monocart-coverage-reports/SKILL.md +106 -0
- package/skills/monocart-coverage-reports/references/best-practices.md +218 -0
- package/skills/monocart-coverage-reports/references/cli.md +108 -0
- package/skills/monocart-coverage-reports/references/collection.md +154 -0
- package/skills/monocart-coverage-reports/references/filtering-and-sourcemaps.md +170 -0
- package/skills/monocart-coverage-reports/references/reports-and-ci.md +219 -0
- package/skills/monocart-coverage-reports/references/repository-map.md +59 -0
- package/lib/index.mjs +0 -9
- package/lib/register/hooks.mjs +0 -3
- package/lib/register/register.mjs +0 -3
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
> A JavaScript code coverage tool to generate native [V8](https://v8.dev/blog/javascript-code-coverage) reports or [Istanbul](https://istanbul.js.org/) reports.
|
|
14
14
|
|
|
15
15
|
* [Usage](#usage)
|
|
16
|
+
* [Best Practices](#best-practices)
|
|
16
17
|
* [Options](#options)
|
|
17
18
|
* [Available Reports](#available-reports)
|
|
18
19
|
* [Compare Reports](#compare-reports)
|
|
@@ -31,6 +32,7 @@
|
|
|
31
32
|
* [Multiprocessing Support](#multiprocessing-support)
|
|
32
33
|
* [Command Line](#command-line)
|
|
33
34
|
* [Config File](#config-file)
|
|
35
|
+
* [Run Tests with `mcr test`](#run-tests-with-mcr-test)
|
|
34
36
|
* [Merge Coverage Reports](#merge-coverage-reports)
|
|
35
37
|
- [Automatic Merging](#automatic-merging)
|
|
36
38
|
- [Manual Merging](#manual-merging)
|
|
@@ -66,18 +68,21 @@
|
|
|
66
68
|
* [Thanks](#thanks)
|
|
67
69
|
|
|
68
70
|
## Usage
|
|
69
|
-
> [Node.js 22
|
|
71
|
+
> **MCR 3.x requires [Node.js >= 22.15.0](https://nodejs.org/)** for all APIs and CLI commands, including ordinary commands, `merge` and `test`.
|
|
72
|
+
|
|
73
|
+
**Upgrading from 2.x:** See the [3.x migration guide](migrate-to-3.md).
|
|
74
|
+
|
|
70
75
|
- Install
|
|
71
76
|
```sh
|
|
72
77
|
npm install monocart-coverage-reports
|
|
73
78
|
```
|
|
74
79
|
- API
|
|
75
80
|
```js
|
|
76
|
-
|
|
81
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
77
82
|
const mcr = MCR({
|
|
78
83
|
name: 'My Coverage Report - 2024-02-28',
|
|
79
84
|
outputDir: './coverage-reports',
|
|
80
|
-
reports: [
|
|
85
|
+
reports: ['v8', 'console-details'],
|
|
81
86
|
cleanCache: true
|
|
82
87
|
});
|
|
83
88
|
await mcr.add(coverageData);
|
|
@@ -99,6 +104,35 @@ mcr node my-app.js -r v8,console-details
|
|
|
99
104
|
```
|
|
100
105
|
For more information, see [Command Line](#command-line)
|
|
101
106
|
|
|
107
|
+
## Best Practices
|
|
108
|
+
|
|
109
|
+
For new test setups, prefer native coverage collection without an instrumentation step:
|
|
110
|
+
|
|
111
|
+
| Target | Recommended setup | Responsibility |
|
|
112
|
+
| --- | --- | --- |
|
|
113
|
+
| Node.js | **Node.js native test runner + `mcr test`** | `node:test` runs assertions; MCR automatically collects native V8 JavaScript coverage and generates reports. |
|
|
114
|
+
| Browser | **Playwright Test + MCR** | Playwright runs assertions; automatic fixtures collect Chromium JS/CSS coverage; MCR merges it and generates reports. |
|
|
115
|
+
| Browser with integrated test reporting | **Playwright Test + [monocart-reporter](https://github.com/cenfun/monocart-reporter#code-coverage-report)** | The reporter already integrates MCR; configure its coverage option and submit coverage from fixtures for unified test and coverage reporting. |
|
|
116
|
+
|
|
117
|
+
For detailed setup instructions and examples, see the [best-practices guide](skills/monocart-coverage-reports/references/best-practices.md).
|
|
118
|
+
|
|
119
|
+
## AI Agent Skill
|
|
120
|
+
|
|
121
|
+
For AI-assisted development in a source checkout, start with [AGENTS.md](https://github.com/cenfun/monocart-coverage-reports/blob/main/AGENTS.md). It describes the source layout, development commands, validation, and safety boundaries. `.github/copilot-instructions.md` provides a self-contained summary of the core rules for GitHub Copilot. These checkout-only guides are not included in the npm package and are separate from the reusable integration skill below.
|
|
122
|
+
|
|
123
|
+
The reusable [Monocart Coverage Reports skill](skills/monocart-coverage-reports/SKILL.md) helps AI coding agents collect coverage, configure the CLI, debug sourcemaps and filters, merge raw reports, and enforce CI thresholds. It uses a main `SKILL.md` with task-specific references loaded on demand, targeting MCR 3.x only and checking its runtime requirements.
|
|
124
|
+
|
|
125
|
+
The npm package includes `skills/monocart-coverage-reports/`. Copy this **entire directory**, including `references/`, into your agent's supported skills location, or ask the agent to read `SKILL.md` directly. Installing MCR does not automatically register the skill or modify agent settings. The instructions are in English; requests and responses can use any language.
|
|
126
|
+
|
|
127
|
+
From the host project root, locate the installed skill and package manifest (older releases may not include the skill):
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
node -p "require.resolve('monocart-coverage-reports/skills/monocart-coverage-reports/SKILL.md')"
|
|
131
|
+
node -p "require.resolve('monocart-coverage-reports/package.json')"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Read the resolved skill, then follow its relative reference links. Use the installed package's runtime, API, and version requirements rather than assuming all examples apply to older releases. These commands resolve files; they do not import Markdown as JavaScript.
|
|
135
|
+
|
|
102
136
|
## Options
|
|
103
137
|
- Default options: [lib/default/options.js](./lib/default/options.js)
|
|
104
138
|
- Options declaration see `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
|
|
@@ -115,6 +149,7 @@ For more information, see [Command Line](#command-line)
|
|
|
115
149
|
| `dataDir` | `string` | `null` | Coverage data directory loaded automatically in `generate()` — alternative to `addFromDir()`. |
|
|
116
150
|
| `entryFilter` | `string \| object \| function` | `null` | (V8 only) Filter V8 entries by url. See [Filtering Results](#filtering-results). |
|
|
117
151
|
| `sourceFilter` | `string \| object \| function` | `null` | (V8 only) Filter sources unpacked from sourcemaps. |
|
|
152
|
+
| `outputFilter` | `string \| object \| function` | `null` | (V8 only) Filter final files by `sourcePath` after sourcemap conversion. See [Filtering Results](#filtering-results). |
|
|
118
153
|
| `filter` | `string \| object \| function` | `null` | Combined filter replacing both `entryFilter` and `sourceFilter`. |
|
|
119
154
|
| `sourcePath` | `object \| function` | `null` | Rewrite source paths; see [Resolve `sourcePath`](#resolve-sourcepath-for-the-source-files). |
|
|
120
155
|
| `all` | `string \| string[] \| object` | `null` | Include untested files as empty coverage. See [Adding Empty Coverage](#adding-empty-coverage-for-untested-files). |
|
|
@@ -127,7 +162,7 @@ For more information, see [Command Line](#command-line)
|
|
|
127
162
|
| `watermarks` | `[number, number] \| object` | `[50, 80]` | Low/high thresholds (percent). Accepts per-metric object like `{ bytes:[50,80], lines:[50,80] }`. |
|
|
128
163
|
| `clean` | `boolean` | `true` | Clean previous reports in `outputDir` before generating (the `.cache` dir is preserved unless `cleanCache` is also `true`). |
|
|
129
164
|
| `cleanCache` | `boolean` | `false` | Clean the cache dir on start. When both `clean` and `cleanCache` are `false`, cached coverage data from previous runs is preserved and can be reused across processes. |
|
|
130
|
-
| `gc` | `number` | `null` | Memory threshold in MB; force GC at critical stages when
|
|
165
|
+
| `gc` | `number` | `null` | Memory threshold in MB; force GC at critical stages when JavaScript heap usage (`heapUsed`) exceeds it. Useful for very large data sets, see [JavaScript heap out of memory](#javascript-heap-out-of-memory). |
|
|
131
166
|
| `sourceMap` | `boolean` | `false` | Save source/sourcemap files to cache for debugging (requires `logging: "debug"`). |
|
|
132
167
|
| `sourceMapResolver` | `(url, defaultResolver) => Promise<string \| object>` | `null` | Custom sourcemap loader (e.g. from a build cache). Call `defaultResolver(url)` to fall back. |
|
|
133
168
|
| `onEntry` | `(entry) => void \| Promise<void>` | `null` | (V8 only) Per-entry hook; see [Hooks](#hooks). |
|
|
@@ -202,7 +237,7 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
|
|
|
202
237
|
|
|
203
238
|
- Custom Reporter
|
|
204
239
|
```js
|
|
205
|
-
{
|
|
240
|
+
const coverageOptions = {
|
|
206
241
|
reports: [
|
|
207
242
|
[path.resolve('./test/custom-istanbul-reporter.js'), {
|
|
208
243
|
type: 'istanbul',
|
|
@@ -216,21 +251,23 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
|
|
|
216
251
|
type: 'both'
|
|
217
252
|
}]
|
|
218
253
|
]
|
|
219
|
-
}
|
|
254
|
+
};
|
|
220
255
|
```
|
|
221
256
|
- Istanbul custom reporter
|
|
222
257
|
> example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
|
|
223
258
|
- V8 custom reporter
|
|
224
259
|
> example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
|
|
225
260
|
|
|
226
|
-
The `type` option
|
|
227
|
-
- `'istanbul'` — the reporter
|
|
228
|
-
- `'v8'` —
|
|
229
|
-
- `'both'` — the
|
|
261
|
+
The `type` option selects the reporter interface and when it runs:
|
|
262
|
+
- `'istanbul'` — uses the Istanbul reporter interface: `new Reporter(reportOptions)` followed by `execute(context)`. The context provides Istanbul report trees and source lookup. V8 input is converted to Istanbul coverage first.
|
|
263
|
+
- `'v8'` (default) — uses `new Reporter(reportOptions, globalOptions)` followed by `await generate(reportData)` for V8 input. `reportData` is the V8 coverage result, with `type`, `summary` and `files`; each file contains V8 ranges and summaries.
|
|
264
|
+
- `'both'` — uses the same `generate(reportData)` interface for either V8 or Istanbul input. Check `reportData.type` to handle the corresponding result structure. This does **not** supply both raw formats together; there are no `v8Data` or `istanbulData` properties.
|
|
265
|
+
|
|
266
|
+
Reporters are loaded with native ESM `import()`. Use `export default Reporter` for ESM; Node's built-in interoperability also accepts CommonJS `module.exports = Reporter` (use `.cjs` inside an ESM project). Local paths must include the file extension; extensionless paths and directory entries are not resolved. No separate CommonJS loader is provided. Reporter loading and initialization errors reject report generation instead of silently skipping the reporter.
|
|
230
267
|
|
|
231
268
|
### Multiple Reports:
|
|
232
269
|
```js
|
|
233
|
-
|
|
270
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
234
271
|
const coverageOptions = {
|
|
235
272
|
outputDir: './coverage-reports',
|
|
236
273
|
reports: [
|
|
@@ -247,15 +284,15 @@ const coverageOptions = {
|
|
|
247
284
|
|
|
248
285
|
// custom reports
|
|
249
286
|
// Specify reporter name with the NPM package
|
|
250
|
-
[
|
|
251
|
-
[
|
|
252
|
-
type:
|
|
253
|
-
key:
|
|
287
|
+
['custom-reporter-1'],
|
|
288
|
+
['custom-reporter-2', {
|
|
289
|
+
type: 'istanbul',
|
|
290
|
+
key: 'value'
|
|
254
291
|
}],
|
|
255
292
|
// Specify reporter name with local path
|
|
256
293
|
['/absolute/path/to/custom-reporter.js']
|
|
257
294
|
]
|
|
258
|
-
}
|
|
295
|
+
};
|
|
259
296
|
const mcr = MCR(coverageOptions);
|
|
260
297
|
```
|
|
261
298
|
|
|
@@ -322,7 +359,7 @@ await Promise.all([
|
|
|
322
359
|
})
|
|
323
360
|
]);
|
|
324
361
|
|
|
325
|
-
await page.goto(
|
|
362
|
+
await page.goto('your page url');
|
|
326
363
|
|
|
327
364
|
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
328
365
|
page.coverage.stopJSCoverage(),
|
|
@@ -332,11 +369,18 @@ const [jsCoverage, cssCoverage] = await Promise.all([
|
|
|
332
369
|
const coverageData = [... jsCoverage, ... cssCoverage];
|
|
333
370
|
|
|
334
371
|
```
|
|
335
|
-
|
|
372
|
+
For Playwright Test automatic fixtures, see [fixtures.js](https://github.com/cenfun/playwright-coverage/blob/main/fixtures.js) in the complete [playwright-coverage example](https://github.com/cenfun/playwright-coverage). The [best-practices guide](skills/monocart-coverage-reports/references/best-practices.md#complete-project-example) explains the integration.
|
|
336
373
|
For more examples, see [./test/test-v8.js](./test/test-v8.js), [css](./test/test-css.js)
|
|
337
374
|
|
|
338
375
|
|
|
339
376
|
### Collecting Raw V8 Coverage Data with Puppeteer
|
|
377
|
+
|
|
378
|
+
> Depending on your package manager, Puppeteer may not download a browser during installation. If no browser is available, install one manually:
|
|
379
|
+
>
|
|
380
|
+
> ```sh
|
|
381
|
+
> npx puppeteer browsers install chrome
|
|
382
|
+
> ```
|
|
383
|
+
|
|
340
384
|
```js
|
|
341
385
|
await Promise.all([
|
|
342
386
|
page.coverage.startJSCoverage({
|
|
@@ -350,7 +394,7 @@ await Promise.all([
|
|
|
350
394
|
})
|
|
351
395
|
]);
|
|
352
396
|
|
|
353
|
-
await page.goto(
|
|
397
|
+
await page.goto('your page url');
|
|
354
398
|
|
|
355
399
|
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
356
400
|
page.coverage.stopJSCoverage(),
|
|
@@ -403,29 +447,34 @@ Possible solutions:
|
|
|
403
447
|
- see [Command Line](#command-line)
|
|
404
448
|
|
|
405
449
|
### Collecting V8 Coverage Data with `CDPClient` API
|
|
450
|
+
|
|
451
|
+
`timeout` controls HTTP discovery and WebSocket connection deadlines (default: 10000ms). `requestTimeout` controls each CDP command on built-in WebSocket sessions (default: 30000ms). Set either to `0` to disable its deadline. Custom Playwright/Puppeteer sessions manage their own command timeouts. For custom sockets, use `new MCR.WSSession(ws, { timeout: 30000 })`.
|
|
452
|
+
|
|
406
453
|
- `CDPClient` available APIs
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
|
|
454
|
+
```ts
|
|
455
|
+
interface CoverageClient {
|
|
456
|
+
startJSCoverage: () => Promise<void>;
|
|
457
|
+
stopJSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
410
458
|
|
|
411
|
-
startCSSCoverage: () => Promise<void>;
|
|
412
|
-
stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
459
|
+
startCSSCoverage: () => Promise<void>;
|
|
460
|
+
stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
413
461
|
|
|
414
|
-
/** start both js and css coverage */
|
|
415
|
-
startCoverage: () => Promise<void>;
|
|
416
|
-
/** stop and return both js and css coverage */
|
|
417
|
-
stopCoverage: () => Promise<V8CoverageEntry[]>;
|
|
462
|
+
/** start both js and css coverage */
|
|
463
|
+
startCoverage: () => Promise<void>;
|
|
464
|
+
/** stop and return both js and css coverage */
|
|
465
|
+
stopCoverage: () => Promise<V8CoverageEntry[]>;
|
|
418
466
|
|
|
419
|
-
/** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
|
|
420
|
-
writeCoverage: () => Promise<string>;
|
|
467
|
+
/** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
|
|
468
|
+
writeCoverage: () => Promise<string>;
|
|
421
469
|
|
|
422
|
-
/** get istanbul coverage data */
|
|
423
|
-
getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
|
|
470
|
+
/** get istanbul coverage data */
|
|
471
|
+
getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
|
|
472
|
+
}
|
|
424
473
|
```
|
|
425
474
|
|
|
426
475
|
- Work with node debugger port `--inspect=9229` or browser debugging port `--remote-debugging-port=9229`
|
|
427
476
|
```js
|
|
428
|
-
|
|
477
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
429
478
|
const client = await MCR.CDPClient({
|
|
430
479
|
port: 9229
|
|
431
480
|
});
|
|
@@ -436,8 +485,8 @@ const coverageData = await client.stopJSCoverage();
|
|
|
436
485
|
|
|
437
486
|
- Work with [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
|
|
438
487
|
```js
|
|
439
|
-
|
|
440
|
-
|
|
488
|
+
import { chromium } from 'playwright';
|
|
489
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
441
490
|
const browser = await chromium.launch();
|
|
442
491
|
const page = await browser.newPage();
|
|
443
492
|
const session = await page.context().newCDPSession(page);
|
|
@@ -447,14 +496,14 @@ const client = await MCR.CDPClient({
|
|
|
447
496
|
// both js and css coverage
|
|
448
497
|
await client.startCoverage();
|
|
449
498
|
// run your test page here
|
|
450
|
-
await page.goto(
|
|
499
|
+
await page.goto('your page url');
|
|
451
500
|
const coverageData = await client.stopCoverage();
|
|
452
501
|
```
|
|
453
502
|
|
|
454
503
|
- Work with [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
|
|
455
504
|
```js
|
|
456
|
-
|
|
457
|
-
|
|
505
|
+
import puppeteer from 'puppeteer';
|
|
506
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
458
507
|
const browser = await puppeteer.launch({});
|
|
459
508
|
const page = await browser.newPage();
|
|
460
509
|
const session = await page.target().createCDPSession();
|
|
@@ -464,20 +513,20 @@ const client = await MCR.CDPClient({
|
|
|
464
513
|
// both js and css coverage
|
|
465
514
|
await client.startCoverage();
|
|
466
515
|
// run your test page here
|
|
467
|
-
await page.goto(
|
|
516
|
+
await page.goto('your page url');
|
|
468
517
|
const coverageData = await client.stopCoverage();
|
|
469
518
|
```
|
|
470
519
|
|
|
471
520
|
- Work with [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (Chrome/Edge Browser)
|
|
472
521
|
```js
|
|
473
|
-
|
|
474
|
-
|
|
522
|
+
import { Builder, Browser } from 'selenium-webdriver';
|
|
523
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
475
524
|
const driver = await new Builder().forBrowser(Browser.CHROME).build();
|
|
476
525
|
const pageCdpConnection = await driver.createCDPConnection('page');
|
|
477
526
|
const session = new MCR.WSSession(pageCdpConnection._wsConnection);
|
|
478
527
|
const client = await MCR.CDPClient({
|
|
479
528
|
session
|
|
480
|
-
})
|
|
529
|
+
});
|
|
481
530
|
```
|
|
482
531
|
|
|
483
532
|
### V8 Coverage Data API
|
|
@@ -485,7 +534,7 @@ const client = await MCR.CDPClient({
|
|
|
485
534
|
- [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
|
|
486
535
|
- [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
|
|
487
536
|
- [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) see [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
|
|
488
|
-
```
|
|
537
|
+
```ts
|
|
489
538
|
// Coverage data for a source range.
|
|
490
539
|
export interface CoverageRange {
|
|
491
540
|
// JavaScript script source offset for the range start.
|
|
@@ -574,15 +623,15 @@ We can use `sourceFilter` to filter the source files. For example, we should rem
|
|
|
574
623
|
For example:
|
|
575
624
|
```js
|
|
576
625
|
const coverageOptions = {
|
|
577
|
-
entryFilter: (entry) => entry.url.indexOf(
|
|
626
|
+
entryFilter: (entry) => entry.url.indexOf('main.js') !== -1,
|
|
578
627
|
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
|
|
579
628
|
};
|
|
580
629
|
```
|
|
581
630
|
Or using [`minimatch`](https://github.com/isaacs/minimatch) pattern:
|
|
582
631
|
```js
|
|
583
632
|
const coverageOptions = {
|
|
584
|
-
entryFilter:
|
|
585
|
-
sourceFilter:
|
|
633
|
+
entryFilter: '**/main.js',
|
|
634
|
+
sourceFilter: '**/src/**'
|
|
586
635
|
};
|
|
587
636
|
```
|
|
588
637
|
Support multiple patterns:
|
|
@@ -607,10 +656,17 @@ Note, those patterns will be transformed to a function, and the order of the pat
|
|
|
607
656
|
```js
|
|
608
657
|
const coverageOptions = {
|
|
609
658
|
entryFilter: (entry) => {
|
|
610
|
-
if (minimatch(entry.url, '**/node_modules/**')) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
659
|
+
if (minimatch(entry.url, '**/node_modules/**')) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
if (minimatch(entry.url, '**/vendor.js')) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
if (minimatch(entry.url, '**/src/**')) {
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
// else unmatched
|
|
669
|
+
return false;
|
|
614
670
|
}
|
|
615
671
|
};
|
|
616
672
|
```
|
|
@@ -629,6 +685,42 @@ const coverageOptions = {
|
|
|
629
685
|
};
|
|
630
686
|
```
|
|
631
687
|
|
|
688
|
+
### Using `outputFilter` to filter final files
|
|
689
|
+
> **Version requirement:** `outputFilter` is introduced in **v3.0.0**. It is not supported in v2.13.x or earlier.
|
|
690
|
+
|
|
691
|
+
**Choose one filtering approach.** `outputFilter` and the existing `entryFilter` + `sourceFilter` approach serve the same purpose: deciding which files appear in the report. They are alternatives, not additional filters you need to configure together. The combined `filter` option is shorthand for `entryFilter` + `sourceFilter`; it does not configure `outputFilter`.
|
|
692
|
+
|
|
693
|
+
| Approach | When and what it filters | Advantages | Trade-offs |
|
|
694
|
+
| :-- | :-- | :-- | :-- |
|
|
695
|
+
| `entryFilter` + `sourceFilter` (or `filter`) | Filters inputs during discovery: `entryFilter` matches entry URLs, and `sourceFilter` matches source paths as sourcemaps are unpacked. | Excludes unwanted files earlier, avoiding unnecessary downstream processing. Prefer this approach for better performance, especially with large projects or bundles. | Requires understanding entry files versus sourcemap sources and configuring both stages. Excluding a bundle via `entryFilter` also removes any desired sources inside it. |
|
|
696
|
+
| `outputFilter` | Filters final files by `sourcePath` after sourcemap conversion, merging and adding untested files (`all`), immediately before report generation. | Simpler configuration: use one filter against the final file list, without distinguishing entries from unpacked sources. | Unwanted files are still discovered and processed before being discarded. This does not save their earlier processing cost and can increase generation time and memory usage, especially when many files will ultimately be excluded. |
|
|
697
|
+
|
|
698
|
+
**Recommendation:** use input filtering (`entryFilter` + `sourceFilter`, or `filter`) when performance matters. Choose `outputFilter` when simpler configuration and filtering the final results are more important, and the extra processing cost is acceptable. If switching to `outputFilter`, remove the old input filters for the same scope: output filtering cannot restore files already excluded during input filtering.
|
|
699
|
+
|
|
700
|
+
For V8 input, `outputFilter` filters both V8 reports and converted Istanbul reports. It defaults to `null` (disabled).
|
|
701
|
+
|
|
702
|
+
Like `entryFilter` and `sourceFilter`, it supports strings and ordered pattern objects (the first matching pattern wins; unmatched files are excluded). Patterns match the final `sourcePath`, including any `sourcePath` rewrites. A function receives the final file entry, not a path string.
|
|
703
|
+
|
|
704
|
+
```js
|
|
705
|
+
const coverageOptions = {
|
|
706
|
+
outputFilter: {
|
|
707
|
+
'**/node_modules/**': false,
|
|
708
|
+
'**/src/**': true
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
// Alternatively:
|
|
712
|
+
// outputFilter: '**/src/**'
|
|
713
|
+
// outputFilter: (entry) => entry.sourcePath.startsWith('src/')
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
CLI examples (quote patterns to prevent shell expansion):
|
|
717
|
+
```sh
|
|
718
|
+
mcr merge --inputDir ./raw --outputFilter "**/src/**"
|
|
719
|
+
mcr merge --inputDir ./raw --outputFilter "{'**/node_modules/**':false,'**/src/**':true}"
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
`outputFilter` does not change raw coverage exports and does not apply to Istanbul input.
|
|
723
|
+
|
|
632
724
|
## Resolve `sourcePath` for the Source Files
|
|
633
725
|
If the source file comes from the sourcemap, then its path is a virtual path. Using the `sourcePath` option to resolve a custom path.
|
|
634
726
|
For example, we have tested multiple dist files, which contain some common files. We hope to merge the coverage of the same files, so we need to unify the `sourcePath` in order to be able to merge the coverage data.
|
|
@@ -646,38 +738,40 @@ const coverageOptions = {
|
|
|
646
738
|
}
|
|
647
739
|
};
|
|
648
740
|
```
|
|
741
|
+
Built-in report path normalization is best-effort; it cannot fully reconcile every nonstandard, virtual, or foreign-platform path. Use `sourcePath` to correct these special cases rather than relying on automatic inference. The callback receives the normalized `filePath`; for V8 entries and sourcemap sources, `info.url` provides the original URL before report path normalization (including the sourcemap's `sourceRoot`, when present). This rewrites report paths, not the filesystem paths used to load missing source content. When `sourcesContent` is missing, local sources are loaded relative to the external map's URL (or the generated file for inline maps), with `sourceRoot` applied as a directory.
|
|
742
|
+
|
|
649
743
|
It also supports simple key/value replacement:
|
|
650
744
|
```js
|
|
651
745
|
const coverageOptions = {
|
|
652
746
|
sourcePath: {
|
|
653
|
-
'my-dist-file1/': '',
|
|
747
|
+
'my-dist-file1/': '',
|
|
654
748
|
'my-dist-file2/': ''
|
|
655
749
|
}
|
|
656
750
|
};
|
|
657
751
|
```
|
|
658
752
|
Normalize the full path of the file:
|
|
659
753
|
```js
|
|
660
|
-
|
|
754
|
+
import path from 'node:path';
|
|
661
755
|
|
|
662
756
|
// MCR coverage options
|
|
663
757
|
const coverageOptions = {
|
|
664
|
-
sourcePath: (filePath, info)=> {
|
|
758
|
+
sourcePath: (filePath, info) => {
|
|
665
759
|
if (!filePath.includes('/') && info.distFile) {
|
|
666
760
|
return `${path.dirname(info.distFile)}/${filePath}`;
|
|
667
761
|
}
|
|
668
762
|
return filePath;
|
|
669
763
|
}
|
|
670
|
-
}
|
|
764
|
+
};
|
|
671
765
|
```
|
|
672
766
|
|
|
673
767
|
## Adding Empty Coverage for Untested Files
|
|
674
|
-
|
|
768
|
+
Untested files are excluded by default. Use `all` to include them with 0% coverage, even when no coverage data was collected. For the same original source, real V8 coverage takes precedence over empty entries.
|
|
675
769
|
```js
|
|
676
770
|
const coverageOptions = {
|
|
677
|
-
all: './src'
|
|
771
|
+
all: './src'
|
|
678
772
|
|
|
679
773
|
// or multiple dirs
|
|
680
|
-
all: ['./src', './lib']
|
|
774
|
+
// all: ['./src', './lib']
|
|
681
775
|
};
|
|
682
776
|
```
|
|
683
777
|
The untested files will apply to the `sourceFilter`. And it also supports additional `filter` (return the file type for js or css coverage):
|
|
@@ -690,30 +784,29 @@ const coverageOptions = {
|
|
|
690
784
|
'**/ignored-*.js': false,
|
|
691
785
|
'**/*.html': false,
|
|
692
786
|
// empty css coverage
|
|
693
|
-
'**/*.scss':
|
|
787
|
+
'**/*.scss': 'css',
|
|
694
788
|
'**/*': true
|
|
695
789
|
}
|
|
696
790
|
}
|
|
697
791
|
};
|
|
698
792
|
```
|
|
699
|
-
|
|
793
|
+
Untested files can be transformed into JavaScript for AST analysis. This example handles **TypeScript without JSX** and assumes `@swc/core` is installed. Use an appropriate compiler/parser for JSX/TSX or Vue; this is not a universal transformer. Preserve the full filename to keep same-basename files in different directories distinct, and parse SWC's JSON map before assigning it.
|
|
700
794
|
```js
|
|
701
|
-
|
|
702
|
-
|
|
795
|
+
import { fileURLToPath } from 'node:url';
|
|
796
|
+
import { transform } from '@swc/core';
|
|
703
797
|
const coverageOptions = {
|
|
704
798
|
all: {
|
|
705
799
|
dir: ['./src'],
|
|
800
|
+
filter: '**/*.ts',
|
|
706
801
|
transformer: async (entry) => {
|
|
707
|
-
const { code, map } = await
|
|
708
|
-
filename:
|
|
802
|
+
const { code, map } = await transform(entry.source, {
|
|
803
|
+
filename: fileURLToPath(entry.url),
|
|
709
804
|
sourceMaps: true,
|
|
710
805
|
isModule: true,
|
|
711
806
|
jsc: {
|
|
712
807
|
parser: {
|
|
713
|
-
syntax:
|
|
714
|
-
|
|
715
|
-
},
|
|
716
|
-
transform: {}
|
|
808
|
+
syntax: 'typescript'
|
|
809
|
+
}
|
|
717
810
|
}
|
|
718
811
|
});
|
|
719
812
|
entry.source = code;
|
|
@@ -726,36 +819,45 @@ const coverageOptions = {
|
|
|
726
819
|
## Hooks
|
|
727
820
|
|
|
728
821
|
### `onEnd` — after the report is generated
|
|
729
|
-
Typical use case: enforce coverage thresholds.
|
|
822
|
+
Typical use case: enforce coverage thresholds. Throw on failure so API callers can handle the error and the CLI exits nonzero, without forcing `process.exit()` before cleanup finishes.
|
|
730
823
|
```js
|
|
731
|
-
const
|
|
824
|
+
const getCoverageError = (metric, summary, minimum) => {
|
|
825
|
+
if (!summary || !(summary.total > 0)) {
|
|
826
|
+
return `${metric}: missing or empty coverage`;
|
|
827
|
+
}
|
|
828
|
+
const { pct } = summary;
|
|
829
|
+
if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < minimum) {
|
|
830
|
+
return `${metric}: ${pct}%, required ${minimum}%`;
|
|
831
|
+
}
|
|
832
|
+
return '';
|
|
833
|
+
};
|
|
732
834
|
const coverageOptions = {
|
|
733
835
|
name: 'My Coverage Report',
|
|
734
836
|
outputDir: './coverage-reports',
|
|
735
837
|
onEnd: (coverageResults) => {
|
|
838
|
+
if (!coverageResults) {
|
|
839
|
+
throw new Error('No coverage results');
|
|
840
|
+
}
|
|
736
841
|
const thresholds = {
|
|
737
842
|
bytes: 80,
|
|
738
843
|
lines: 60
|
|
739
844
|
};
|
|
740
|
-
console.log('check thresholds ...', thresholds);
|
|
741
845
|
const errors = [];
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
|
|
846
|
+
for (const [metric, minimum] of Object.entries(thresholds)) {
|
|
847
|
+
const error = getCoverageError(metric, coverageResults.summary?.[metric], minimum);
|
|
848
|
+
if (error) {
|
|
849
|
+
errors.push(error);
|
|
747
850
|
}
|
|
748
|
-
}
|
|
851
|
+
}
|
|
749
852
|
if (errors.length) {
|
|
750
|
-
|
|
751
|
-
console.log(EC.red(errMsg));
|
|
752
|
-
// throw new Error(errMsg);
|
|
753
|
-
// process.exit(1);
|
|
853
|
+
throw new Error(errors.join('\n'));
|
|
754
854
|
}
|
|
755
855
|
}
|
|
756
|
-
}
|
|
856
|
+
};
|
|
757
857
|
```
|
|
758
858
|
|
|
859
|
+
This policy rejects missing/zero-total metrics; adapt it explicitly if a metric is legitimately empty. With no usable input, `generate()` may return `undefined` without calling `onEnd`. API callers must check that result; ordinary CLI workflows should use a fresh output directory and verify a newly generated nonempty report. See the [strict CI recipes](skills/monocart-coverage-reports/references/reports-and-ci.md#reject-stale-reports-and-missing-collection-in-ci).
|
|
860
|
+
|
|
759
861
|
### `onEntry` — before each V8 entry is processed (V8 only)
|
|
760
862
|
Use this to mutate the entry (for example, transform non-standard sources into valid ECMAScript before the AST parser runs). See [Unparsable source](#unparsable-source).
|
|
761
863
|
```js
|
|
@@ -772,7 +874,7 @@ Only available when using `mcr <command>`:
|
|
|
772
874
|
- `onReady(coverageReport, nodeV8CoverageDir, subprocess)` — runs after the child exits, before MCR reads coverage from `nodeV8CoverageDir`. Useful when the child hasn't finished flushing coverage data and needs to be waited on.
|
|
773
875
|
```js
|
|
774
876
|
// mcr.config.js
|
|
775
|
-
|
|
877
|
+
export default {
|
|
776
878
|
onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
|
|
777
879
|
// wait for something, or pre-process the raw files
|
|
778
880
|
}
|
|
@@ -780,6 +882,8 @@ module.exports = {
|
|
|
780
882
|
```
|
|
781
883
|
|
|
782
884
|
## Ignoring Uncovered Codes
|
|
885
|
+
<!-- readme-lint-disable -->
|
|
886
|
+
|
|
783
887
|
To ignore codes, use the special comment which starts with `v8 ignore `:
|
|
784
888
|
- Ignoring all until stop
|
|
785
889
|
```js
|
|
@@ -814,12 +918,14 @@ function uncovered() {
|
|
|
814
918
|
```
|
|
815
919
|
- To disable comment-based ignoring entirely, set `v8Ignore: false`.
|
|
816
920
|
|
|
921
|
+
<!-- readme-lint-enable -->
|
|
922
|
+
|
|
817
923
|
## Multiprocessing Support
|
|
818
924
|
> The data will be added to `[outputDir]/.cache`, After the generation of the report, this data will be removed unless debugging has been enabled or a raw report has been used, see [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
|
|
819
925
|
- Main process, before the start of testing
|
|
820
926
|
```js
|
|
821
|
-
|
|
822
|
-
|
|
927
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
928
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
823
929
|
const mcr = MCR(coverageOptions);
|
|
824
930
|
// clean previous cache before the start of testing
|
|
825
931
|
// unless the running environment is new and no cache
|
|
@@ -828,16 +934,16 @@ mcr.cleanCache();
|
|
|
828
934
|
|
|
829
935
|
- Sub process 1, testing stage 1
|
|
830
936
|
```js
|
|
831
|
-
|
|
832
|
-
|
|
937
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
938
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
833
939
|
const mcr = MCR(coverageOptions);
|
|
834
940
|
await mcr.add(coverageData1);
|
|
835
941
|
```
|
|
836
942
|
|
|
837
943
|
- Sub process 2, testing stage 2
|
|
838
944
|
```js
|
|
839
|
-
|
|
840
|
-
|
|
945
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
946
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
841
947
|
const mcr = MCR(coverageOptions);
|
|
842
948
|
await mcr.add(coverageData2);
|
|
843
949
|
```
|
|
@@ -845,8 +951,8 @@ await mcr.add(coverageData2);
|
|
|
845
951
|
- Main process, after the completion of testing
|
|
846
952
|
```js
|
|
847
953
|
// generate coverage reports after the completion of testing
|
|
848
|
-
|
|
849
|
-
|
|
954
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
955
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
850
956
|
const mcr = MCR(coverageOptions);
|
|
851
957
|
await mcr.generate();
|
|
852
958
|
```
|
|
@@ -868,14 +974,17 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
|
|
|
868
974
|
|
|
869
975
|
- CLI Options
|
|
870
976
|
|
|
977
|
+
Commands are executed with Execa's default options, without enabling shell mode. Pass the executable and its arguments separately, for example `mcr node "./my app.js"`; do not quote an entire command as a single argument. Argument boundaries are preserved: MCR does not expand environment variables or globs, or interpret pipes, redirections, `&&` or other shell syntax. Move command sequences into a script instead. Use `mcr [options] -- executable ...args` to keep child options separate from MCR options. Execa handles platform-specific command execution, including escaped batch-file invocation through `cmd.exe` when needed on Windows.
|
|
978
|
+
|
|
871
979
|
The options below are only available when using the CLI (`mcr <command>`):
|
|
872
980
|
|
|
873
981
|
| Option | Type | Default | Description |
|
|
874
982
|
| :-- | :-- | :-- | :-- |
|
|
875
983
|
| `-c, --config` | `string` | — | Custom config file path |
|
|
876
|
-
| `--import` | `string` | `null` | Preload ESM module via `NODE_OPTIONS`, e.g. `tsx` for TypeScript.
|
|
877
|
-
| `--require` | `string` | `null` |
|
|
878
|
-
| `--env` | `string` | `null` | Load environment variables from a dotenv file (defaults to `.env`).
|
|
984
|
+
| `--import` | `string` | `null` | Preload ESM module via `NODE_OPTIONS`, e.g. `tsx` for TypeScript. |
|
|
985
|
+
| `--require` | `string` | `null` | Deprecated alias for `--import`; uses ESM loading, not CommonJS resolution |
|
|
986
|
+
| `--env` | `string` | `null` | Load environment variables from a dotenv file (defaults to `.env`). |
|
|
987
|
+
| `test` (config only) | `string \| string[] \| null` | `null` | Default test file paths or glob patterns accepted by `node --test`, passed unchanged when `mcr test` has no positional targets; ignored by other commands and the API. Requires Node.js `>= 22.15.0` |
|
|
879
988
|
| `onStart` | `(coverageReport) => Promise<void>` | `null` | Runs before the child process is spawned; see [Hooks](#hooks) |
|
|
880
989
|
| `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | Runs after the child exits, before MCR reads coverage; see [Hooks](#hooks) |
|
|
881
990
|
|
|
@@ -886,16 +995,18 @@ For other options (e.g. `--name`, `--reports`, `--outputDir`, `--lcov`, etc.), s
|
|
|
886
995
|
mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
|
|
887
996
|
```
|
|
888
997
|
|
|
889
|
-
- `--import <module>` / `--require <module>`: forward a preload module to the child process via `NODE_OPTIONS`. Use this for TypeScript/JSX runtimes (`tsx`, `ts-node`, etc.) and also when loading a `mcr.config.ts`.
|
|
998
|
+
- `--import <module>` / `--require <module>`: forward a preload module to the child process via `NODE_OPTIONS`. Use this for TypeScript/JSX runtimes (`tsx`, `ts-node`, etc.) and also when loading a `mcr.config.ts`. Available on all supported Node.js versions. Local paths are relative to the current working directory and must include the file extension; paths containing spaces are supported. Package names use native ESM resolution from MCR's module location. Both flags use `import()` and forward `--import` to Node; `--require` is a deprecated alias, not a CommonJS loader. CJS modules work only through Node's built-in import interoperability.
|
|
890
999
|
```sh
|
|
891
1000
|
mcr --import tsx node ./test.ts
|
|
892
1001
|
```
|
|
893
1002
|
|
|
894
|
-
- `--env [path]`: load environment variables from a dotenv file before the child starts (defaults to `.env`).
|
|
1003
|
+
- `--env [path]`: load environment variables from a dotenv file before the child starts (defaults to `.env`). Uses `process.loadEnvFile`, available on all supported Node.js versions.
|
|
895
1004
|
```sh
|
|
896
1005
|
mcr --env .env.test node ./test.js
|
|
897
1006
|
```
|
|
898
1007
|
|
|
1008
|
+
- `test`: run Node.js tests and generate coverage reports. See [Run Tests with `mcr test`](#run-tests-with-mcr-test).
|
|
1009
|
+
|
|
899
1010
|
- `merge`: merge raw coverage data from `inputDir`(s) and generate merged reports directly, without running a child process.
|
|
900
1011
|
```sh
|
|
901
1012
|
mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
|
|
@@ -908,6 +1019,8 @@ See [Merge Coverage Reports](#merge-coverage-reports).
|
|
|
908
1019
|
- [AVA](#ava)
|
|
909
1020
|
|
|
910
1021
|
## Config File
|
|
1022
|
+
In MCR 3.x, `loadConfig()` throws if an explicitly requested config does not exist or a discovered config fails to load (including JSON parsing, module syntax and startup errors). All CLI commands exit with a non-zero status for these errors. No `strict` option is needed. If no custom config is requested and no default config is found, execution continues with existing options.
|
|
1023
|
+
|
|
911
1024
|
Loading config file by priority:
|
|
912
1025
|
- Custom config file:
|
|
913
1026
|
- CLI: `mcr --config <my-config-file-path>`
|
|
@@ -923,6 +1036,59 @@ mcr --import tsx -c mcr.config.ts node ./test.js
|
|
|
923
1036
|
|
|
924
1037
|
> If no custom path is given, MCR walks up from the current working directory to find the default config file (via [`find-up`](https://github.com/sindresorhus/find-up)). Files outside the search path will be ignored.
|
|
925
1038
|
|
|
1039
|
+
## Run Tests with `mcr test`
|
|
1040
|
+
|
|
1041
|
+
**New in MCR 3.x:** Run tests with the built-in [Node.js test runner](https://nodejs.org/docs/latest/api/test.html) and automatically generate coverage reports with one command. Requires **Node.js >= 22.15.0**.
|
|
1042
|
+
|
|
1043
|
+
Install MCR and run your test files directly — no config file is required:
|
|
1044
|
+
|
|
1045
|
+
```sh
|
|
1046
|
+
npm install --save-dev monocart-coverage-reports
|
|
1047
|
+
npx mcr test "./test/**/*.test.js" -r v8,console-summary -o ./coverage-reports
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
This runs the matching tests, prints a coverage summary in the terminal, and writes the V8 HTML report to `./coverage-reports/index.html`. Quote glob patterns so Node.js, rather than your shell, handles matching.
|
|
1051
|
+
|
|
1052
|
+
For repeated runs, save the test targets and coverage options in `mcr.config.js` (ESM; use `"type": "module"` in `package.json`, or name the config `mcr.config.mjs`):
|
|
1053
|
+
|
|
1054
|
+
```js
|
|
1055
|
+
export default {
|
|
1056
|
+
test: ['./test/unit/**/*.test.js', './test/integration/**/*.test.js'],
|
|
1057
|
+
reports: ['v8', 'console-summary'],
|
|
1058
|
+
outputDir: './coverage-reports'
|
|
1059
|
+
};
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
```sh
|
|
1063
|
+
# Run the targets from the config
|
|
1064
|
+
npx mcr test
|
|
1065
|
+
# Override the configured targets and forward Node.js test runner options after --
|
|
1066
|
+
npx mcr test "./test/unit/**/*.test.js" -- --test-reporter=spec
|
|
1067
|
+
|
|
1068
|
+
# Use an explicit config file
|
|
1069
|
+
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js
|
|
1070
|
+
|
|
1071
|
+
# Choose reports from the command line
|
|
1072
|
+
npx mcr test "../tests/**/*.test.js" -r v8,console-summary
|
|
1073
|
+
|
|
1074
|
+
# Run multiple test files
|
|
1075
|
+
npx mcr test ./test/first.test.js ./test/second.test.js
|
|
1076
|
+
|
|
1077
|
+
# Filter test names and select a reporter
|
|
1078
|
+
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js -- --test-name-pattern="my test" --test-reporter=spec
|
|
1079
|
+
|
|
1080
|
+
# Place the test command after an initial separator
|
|
1081
|
+
npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=spec
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
Positional targets override (rather than append to) the config `test` option. If no positional targets are given, the config option is used. A config file is not required when targets are supplied on the command line. Existing MCR options such as `-c`, `-r`, `-o` and `--lcov` can appear before or after targets. Put Node runner options after `--`; those arguments are forwarded unchanged, not parsed as MCR options or target overrides. Quote globs to prevent shell expansion. In the alternative `mcr [options] -- test ...` form, put all MCR options before the first `--`; a second `--` separates Node runner options.
|
|
1085
|
+
|
|
1086
|
+
Targets are passed unchanged to `node --test`. MCR does not check their types or filesystem existence, expand directories/globs, resolve or normalize paths, escape special characters, or deduplicate targets. `node --test` accepts positional test file paths or glob patterns, not a JavaScript configuration object. In MCR config, a string represents one such argument (it is not split on spaces), and a string array represents multiple arguments forwarded in their original order. `null` is only MCR's skip default and is never passed to Node. Node handles discovery, matching, interpretation and errors. Relative paths use the current working directory, not the config file directory. A bare directory is not promised to trigger recursive discovery; use a glob such as `"../tests/**/*.test.js"` for recursive matching, following the installed Node version's native behavior. TypeScript execution depends on your Node version or a loader such as `--import tsx`.
|
|
1087
|
+
|
|
1088
|
+
`test` defaults to `null`; when no positional targets are supplied, `null`, an omitted value, an empty string or `[]` skips tests without generating a report. MCR only checks whether targets were supplied; invalid or unmatched targets are left to Node. If no coverage data is produced and no report can be generated, the command fails at report generation. Missing explicit config files and failed config loading also fail the command rather than silently skipping tests. Test failures still produce coverage reports and retain a non-zero exit status; report generation failures also fail the command. Existing coverage options and CLI hooks are supported. Use coverage filters to exclude test files if needed. **The `test` option has no effect on other CLI commands or API calls**, including `merge` and `generate()`.
|
|
1089
|
+
|
|
1090
|
+
See [Command Line](#command-line) for other CLI options.
|
|
1091
|
+
|
|
926
1092
|
## Merge Coverage Reports
|
|
927
1093
|
The following usage scenarios may require merging coverage reports:
|
|
928
1094
|
- When the code is executed in different environments, like Node.js `server side` and browser `client side` (`Next.js` for instance). Each environment may generate its own coverage report. Merging them can give a more comprehensive view of the test coverage.
|
|
@@ -943,12 +1109,12 @@ First, using the `raw` report to export the original coverage data to the specif
|
|
|
943
1109
|
```js
|
|
944
1110
|
const coverageOptions = {
|
|
945
1111
|
name: 'My Unit Test Coverage Report',
|
|
946
|
-
outputDir:
|
|
1112
|
+
outputDir: './coverage-reports/unit',
|
|
947
1113
|
reports: [
|
|
948
1114
|
['raw', {
|
|
949
1115
|
// relative path will be "./coverage-reports/unit/raw"
|
|
950
1116
|
// defaults to raw
|
|
951
|
-
outputDir:
|
|
1117
|
+
outputDir: 'raw'
|
|
952
1118
|
}],
|
|
953
1119
|
['v8'],
|
|
954
1120
|
['console-details']
|
|
@@ -964,8 +1130,8 @@ const coverageOptions = {
|
|
|
964
1130
|
- Then create a script `merge-coverage.js` to generate a merged report with option `inputDir`.
|
|
965
1131
|
```js
|
|
966
1132
|
// merge-coverage.js
|
|
967
|
-
|
|
968
|
-
|
|
1133
|
+
import fs from 'fs';
|
|
1134
|
+
import { CoverageReport } from 'monocart-coverage-reports';
|
|
969
1135
|
const inputDir = [
|
|
970
1136
|
'./coverage-reports/unit/raw',
|
|
971
1137
|
'./coverage-reports/e2e/raw'
|
|
@@ -984,7 +1150,7 @@ const coverageOptions = {
|
|
|
984
1150
|
'**/node_modules/**': false,
|
|
985
1151
|
'**/src/**': true
|
|
986
1152
|
},
|
|
987
|
-
|
|
1153
|
+
|
|
988
1154
|
sourcePath: (filePath, info) => {
|
|
989
1155
|
// Unify the file path for the same files
|
|
990
1156
|
// For example, the file index.js has different paths:
|
|
@@ -998,7 +1164,7 @@ const coverageOptions = {
|
|
|
998
1164
|
['v8'],
|
|
999
1165
|
['console-details']
|
|
1000
1166
|
],
|
|
1001
|
-
|
|
1167
|
+
|
|
1002
1168
|
onEnd: () => {
|
|
1003
1169
|
// remove the raw files if no longer needed
|
|
1004
1170
|
// inputDir.forEach((p) => {
|
|
@@ -1027,7 +1193,7 @@ see example: [merge-code-coverage](https://github.com/cenfun/merge-code-coverage
|
|
|
1027
1193
|
## Common issues
|
|
1028
1194
|
### Unexpected coverage
|
|
1029
1195
|
In most cases, it happens when the coverage of the generated code is converted to the coverage of the original code through a sourcemap. In other words, it's an issue with the sourcemap. Most of the time, we can solve this by setting `minify` to `false` in the configuration of build tools. Let's take a look at an example:
|
|
1030
|
-
```
|
|
1196
|
+
```text
|
|
1031
1197
|
const a = tf ? 'true' : 'false';
|
|
1032
1198
|
^ ^ ^
|
|
1033
1199
|
m1 p m2
|
|
@@ -1043,21 +1209,21 @@ How `MCR` Works:
|
|
|
1043
1209
|
### Unparsable source
|
|
1044
1210
|
It happens during the parsing of the source code into AST, if the source code is not in the standard ECMAScript. For example `ts`, `jsx` and so on. There is a option to fix it, which is to manually compile the source code for these files.
|
|
1045
1211
|
```js
|
|
1046
|
-
import * as fs from
|
|
1047
|
-
import * as path from
|
|
1048
|
-
import { fileURLToPath } from
|
|
1212
|
+
import * as fs from 'fs';
|
|
1213
|
+
import * as path from 'path';
|
|
1214
|
+
import { fileURLToPath } from 'url';
|
|
1049
1215
|
import * as TsNode from 'ts-node';
|
|
1050
1216
|
const coverageOptions = {
|
|
1051
|
-
onEntry:
|
|
1052
|
-
const filePath = fileURLToPath(entry.url)
|
|
1053
|
-
const originalSource = fs.readFileSync(filePath).toString(
|
|
1217
|
+
onEntry: (entry) => {
|
|
1218
|
+
const filePath = fileURLToPath(entry.url);
|
|
1219
|
+
const originalSource = fs.readFileSync(filePath).toString('utf-8');
|
|
1054
1220
|
const fileName = path.basename(filePath);
|
|
1055
1221
|
const tn = TsNode.create({});
|
|
1056
1222
|
const source = tn.compile(originalSource, fileName);
|
|
1057
1223
|
entry.fake = false;
|
|
1058
1224
|
entry.source = source;
|
|
1059
1225
|
}
|
|
1060
|
-
}
|
|
1226
|
+
};
|
|
1061
1227
|
```
|
|
1062
1228
|
|
|
1063
1229
|
### JavaScript heap out of memory
|
|
@@ -1098,9 +1264,30 @@ const coverageOptions = {
|
|
|
1098
1264
|
|
|
1099
1265
|
- Show time logs with env `MCR_LOG_TIME`
|
|
1100
1266
|
```js
|
|
1101
|
-
process.env.MCR_LOG_TIME = true
|
|
1267
|
+
process.env.MCR_LOG_TIME = true;
|
|
1102
1268
|
```
|
|
1103
1269
|
|
|
1270
|
+
### CSS coverage issues
|
|
1271
|
+
|
|
1272
|
+
CSS coverage from multiple bundles is merged as a union for each original source.
|
|
1273
|
+
|
|
1274
|
+
For preprocessor syntax that `all` cannot parse directly, use `all.transformer` to compile to CSS, set `entry.type = 'css'`, and preserve the sourcemap.
|
|
1275
|
+
|
|
1276
|
+
#### Sass mixins and `@include`
|
|
1277
|
+
|
|
1278
|
+
Sass `@include` can map a generated rule to non-contiguous source locations. When a rule cannot be reliably mapped, MCR reports the **whole bundle as generated CSS** instead of the preprocessor sources. That bundle's coverage is not merged into the original-source coverage.
|
|
1279
|
+
|
|
1280
|
+
#### Line coverage of untested preprocessor sources
|
|
1281
|
+
|
|
1282
|
+
Untested files included through `all` have `0%` byte coverage, including sourcemap originals. Lines outside parsed or mapped rules (such as variables or unused mixins) may appear covered; use **bytes** coverage for these files.
|
|
1283
|
+
|
|
1284
|
+
#### Source map can be inaccurate after minification
|
|
1285
|
+
CSS minification can produce imprecise sourcemaps and trigger fallback to generated CSS. If source mapping is inaccurate, try **disabling CSS minification**.
|
|
1286
|
+
|
|
1287
|
+
#### V8 covered the whole block
|
|
1288
|
+
|
|
1289
|
+
Chromium may mark an entire media query or nested CSS block as covered. Consider [postcss-atomic-selectors](https://www.npmjs.com/package/postcss-atomic-selectors) as a potential workaround.
|
|
1290
|
+
|
|
1104
1291
|
## Integration with Any Testing Framework
|
|
1105
1292
|
- API
|
|
1106
1293
|
- Collecting coverage data when any stage of the test is completed, and adding the coverage data to the coverage reporter. `await mcr.add(coverageData)`
|
|
@@ -1181,7 +1368,7 @@ mcr mocha ./test/**/*.js
|
|
|
1181
1368
|
```sh
|
|
1182
1369
|
cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
|
|
1183
1370
|
cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
|
|
1184
|
-
# Node.js
|
|
1371
|
+
# MCR 3.x: Node.js >= 22.15.0
|
|
1185
1372
|
mcr --import tsx tsx ./src/example.ts
|
|
1186
1373
|
mcr --import tsx mocha ./test/**/*.ts
|
|
1187
1374
|
```
|
|
@@ -1201,7 +1388,7 @@ mcr ava
|
|
|
1201
1388
|
- Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
|
|
1202
1389
|
```js
|
|
1203
1390
|
const coverageOptions = {
|
|
1204
|
-
outputDir:
|
|
1391
|
+
outputDir: './coverage-reports',
|
|
1205
1392
|
reports: [
|
|
1206
1393
|
['codecov']
|
|
1207
1394
|
]
|
|
@@ -1221,7 +1408,7 @@ const coverageOptions = {
|
|
|
1221
1408
|
- Using `lcov` report:
|
|
1222
1409
|
```js
|
|
1223
1410
|
const coverageOptions = {
|
|
1224
|
-
outputDir:
|
|
1411
|
+
outputDir: './coverage-reports',
|
|
1225
1412
|
lcov: true
|
|
1226
1413
|
};
|
|
1227
1414
|
```
|
|
@@ -1239,7 +1426,7 @@ const coverageOptions = {
|
|
|
1239
1426
|
- Using `lcov` report:
|
|
1240
1427
|
```js
|
|
1241
1428
|
const coverageOptions = {
|
|
1242
|
-
outputDir:
|
|
1429
|
+
outputDir: './coverage-reports',
|
|
1243
1430
|
lcov: true
|
|
1244
1431
|
};
|
|
1245
1432
|
```
|
|
@@ -1272,17 +1459,25 @@ const coverageOptions = {
|
|
|
1272
1459
|
```
|
|
1273
1460
|
|
|
1274
1461
|
## Contributing
|
|
1275
|
-
- Node.js
|
|
1462
|
+
- Latest Node.js 24 LTS recommended for development; minimum package runtime is 22.15.0. Development tools may require a newer patch than the minimum runtime.
|
|
1276
1463
|
- VSCode (extensions: eslint/stylelint/vue)
|
|
1277
1464
|
```sh
|
|
1278
1465
|
npm install
|
|
1279
1466
|
npx playwright install --with-deps
|
|
1280
1467
|
|
|
1281
1468
|
npm run build
|
|
1469
|
+
npm run lint
|
|
1282
1470
|
npm run test
|
|
1283
1471
|
|
|
1284
1472
|
npm run dev
|
|
1285
1473
|
```
|
|
1474
|
+
README JavaScript examples are checked with the project's ESLint configuration via `npm run lint:readme` and `npm run lint`. Only `no-undef` and `no-unused-vars` are disabled for snippets that omit surrounding context; syntax, style, and other correctness rules remain enabled. Use `ts` fences for TypeScript declarations and `text` for diagrams. For intentionally nonstandard syntax demonstrations, keep `js` highlighting and wrap the region in standalone `<!-- readme-lint-disable -->` / `<!-- readme-lint-enable -->` comments. Such regions are neither linted nor autofixed. Markers must be paired and cannot be nested; markers inside code fences are treated as example text.
|
|
1475
|
+
|
|
1476
|
+
```sh
|
|
1477
|
+
npm run lint:readme
|
|
1478
|
+
npm run lint:readme -- --fix
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1286
1481
|
- Refreshing `eol=lf` for snapshot of test (Windows)
|
|
1287
1482
|
```sh
|
|
1288
1483
|
git add . -u
|
|
@@ -1293,4 +1488,7 @@ npm run eol
|
|
|
1293
1488
|
|
|
1294
1489
|
## Thanks
|
|
1295
1490
|
- [@bcoe](https://github.com/bcoe)
|
|
1296
|
-
- [@edumserrano](https://github.com/edumserrano)
|
|
1491
|
+
- [@edumserrano](https://github.com/edumserrano)
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+

|