monocart-coverage-reports 2.13.1 → 3.0.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 +267 -114
- package/README.zh-Hans.md +268 -115
- 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 +6 -6
- package/lib/converter/collect-source-maps.js +119 -92
- package/lib/converter/converter.js +49 -117
- package/lib/converter/fake-source-coverage.js +64 -0
- package/lib/converter/find-original-range.js +3 -3
- 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 +27 -55
- 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/chunks/chunk-RVOSNMVU.js +2 -0
- 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 +1 -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 +5 -5
- package/lib/utils/util.js +61 -41
- 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 +153 -149
- package/skills/monocart-coverage-reports/SKILL.md +106 -0
- package/skills/monocart-coverage-reports/references/cli.md +108 -0
- package/skills/monocart-coverage-reports/references/collection.md +152 -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 +55 -0
- package/lib/index.mjs +0 -9
- package/lib/register/hooks.mjs +0 -3
- package/lib/register/register.mjs +0 -3
package/README.zh-Hans.md
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
* [多进程支持](#multiprocessing-support)
|
|
32
32
|
* [如何使用CLI命令行](#command-line)
|
|
33
33
|
* [如何加载配置文件](#config-file)
|
|
34
|
+
* [使用 `mcr test` 运行测试](#run-tests-with-mcr-test)
|
|
34
35
|
* [如何合并覆盖率报告](#merge-coverage-reports)
|
|
35
36
|
- [自动合并](#automatic-merging)
|
|
36
37
|
- [手动合并](#manual-merging)
|
|
@@ -66,18 +67,21 @@
|
|
|
66
67
|
* [感谢](#thanks)
|
|
67
68
|
|
|
68
69
|
## Usage
|
|
69
|
-
>
|
|
70
|
+
> **MCR 3.x 的所有 API 和 CLI 命令均要求 [Node.js >= 22.15.0](https://nodejs.org/)**,包括普通命令、`merge` 和 `test`。
|
|
71
|
+
|
|
72
|
+
**从 2.x 升级:** 请参阅 [3.x 迁移指南](migrate-to-3.md)。
|
|
73
|
+
|
|
70
74
|
- 安装
|
|
71
75
|
```sh
|
|
72
76
|
npm install monocart-coverage-reports
|
|
73
77
|
```
|
|
74
78
|
- API
|
|
75
79
|
```js
|
|
76
|
-
|
|
80
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
77
81
|
const mcr = MCR({
|
|
78
82
|
name: 'My Coverage Report - 2024-02-28',
|
|
79
83
|
outputDir: './coverage-reports',
|
|
80
|
-
reports: [
|
|
84
|
+
reports: ['v8', 'console-details'],
|
|
81
85
|
cleanCache: true
|
|
82
86
|
});
|
|
83
87
|
await mcr.add(coverageData);
|
|
@@ -99,6 +103,21 @@ mcr node my-app.js -r v8,console-details
|
|
|
99
103
|
```
|
|
100
104
|
参见 [命令行](#command-line)
|
|
101
105
|
|
|
106
|
+
## AI Agent Skill
|
|
107
|
+
|
|
108
|
+
项目提供可复用的 [Monocart Coverage Reports 技能](skills/monocart-coverage-reports/SKILL.md),帮助 AI coding agent 采集覆盖率、配置 CLI、排查 sourcemap 和过滤问题、合并 raw 报告及设置 CI 阈值。结构为主 `SKILL.md` 加按需加载的专题参考文档,仅面向 MCR 3.x,并检查其运行环境要求。
|
|
109
|
+
|
|
110
|
+
npm 包包含 `skills/monocart-coverage-reports/`。请将**整个目录**(包括 `references/`)复制到 agent 支持的 skills 目录,或直接让 agent 读取 `SKILL.md`。安装 MCR 不会自动注册技能或修改 agent 配置。技能说明使用英文,但提问和回复可以使用中文或其他语言。
|
|
111
|
+
|
|
112
|
+
在使用 MCR 的项目根目录中定位已安装的技能和包配置(旧版本可能尚未包含技能):
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
node -p "require.resolve('monocart-coverage-reports/skills/monocart-coverage-reports/SKILL.md')"
|
|
116
|
+
node -p "require.resolve('monocart-coverage-reports/package.json')"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
读取定位到的技能文件,再按相对链接读取专题文档。请以已安装包的运行时、API 和版本要求为准,不要假设所有示例都适用于旧版本。以上命令用于定位文件,不是将 Markdown 作为 JavaScript 导入。
|
|
120
|
+
|
|
102
121
|
## Options
|
|
103
122
|
- 默认选项: [lib/default/options.js](./lib/default/options.js)
|
|
104
123
|
- 选项的类型描述,见 `CoverageReportOptions` [lib/index.d.ts](./lib/index.d.ts)
|
|
@@ -115,6 +134,7 @@ mcr node my-app.js -r v8,console-details
|
|
|
115
134
|
| `dataDir` | `string` | `null` | `generate()` 阶段自动加载的覆盖率数据目录,作为 `addFromDir()` 的替代。 |
|
|
116
135
|
| `entryFilter` | `string \| object \| function` | `null` | (仅 V8)按 URL 过滤入口文件,参见 [过滤V8覆盖率数据](#filtering-results)。 |
|
|
117
136
|
| `sourceFilter` | `string \| object \| function` | `null` | (仅 V8)过滤从 sourcemap 解包出的源文件。 |
|
|
137
|
+
| `outputFilter` | `string \| object \| function` | `null` | (仅 V8)在 sourcemap 转换后,按最终 `sourcePath` 过滤文件。参见 [过滤V8覆盖率数据](#filtering-results)。 |
|
|
118
138
|
| `filter` | `string \| object \| function` | `null` | 合并 `entryFilter` 和 `sourceFilter` 的统一过滤器。 |
|
|
119
139
|
| `sourcePath` | `object \| function` | `null` | 修改源文件路径,参见 [使用 `sourcePath`](#resolve-sourcepath-for-the-source-files)。 |
|
|
120
140
|
| `all` | `string \| string[] \| object` | `null` | 为未测试文件添加空覆盖率,参见 [为未测试的文件添加空的覆盖率报告](#adding-empty-coverage-for-untested-files)。 |
|
|
@@ -127,7 +147,7 @@ mcr node my-app.js -r v8,console-details
|
|
|
127
147
|
| `watermarks` | `[number, number] \| object` | `[50, 80]` | 覆盖率高/低阈值百分比。也支持按指标配置:`{ bytes:[50,80], lines:[50,80] }`。 |
|
|
128
148
|
| `clean` | `boolean` | `true` | 生成报告前清理 `outputDir` 中的旧报告(`.cache` 目录在 `cleanCache` 也为 `true` 时才会被清理)。 |
|
|
129
149
|
| `cleanCache` | `boolean` | `false` | 启动时清理缓存目录。当 `clean` 和 `cleanCache` 都为 `false` 时,之前运行的缓存数据会保留,可在多进程间复用。 |
|
|
130
|
-
| `gc` | `number` | `null` | 内存阈值(MB),在关键阶段若
|
|
150
|
+
| `gc` | `number` | `null` | 内存阈值(MB),在关键阶段若 JavaScript 堆内存使用量(`heapUsed`)超过阈值则强制触发 GC,适用于超大数据集,参见 [JavaScript heap out of memory](#javascript-heap-out-of-memory)。 |
|
|
131
151
|
| `sourceMap` | `boolean` | `false` | 将源文件和 sourcemap 也保存到 cache 便于调试(需要 `logging: "debug"`)。 |
|
|
132
152
|
| `sourceMapResolver` | `(url, defaultResolver) => Promise<string \| object>` | `null` | 自定义 sourcemap 加载逻辑(例如从构建缓存中读取),可调用 `defaultResolver(url)` 回退到默认解析。 |
|
|
133
153
|
| `onEntry` | `(entry) => void \| Promise<void>` | `null` | (仅 V8)每个入口文件处理前触发,参见 [Hooks](#hooks)。 |
|
|
@@ -202,7 +222,7 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
|
|
|
202
222
|
|
|
203
223
|
- 自定义报告
|
|
204
224
|
```js
|
|
205
|
-
{
|
|
225
|
+
const coverageOptions = {
|
|
206
226
|
reports: [
|
|
207
227
|
[path.resolve('./test/custom-istanbul-reporter.js'), {
|
|
208
228
|
type: 'istanbul',
|
|
@@ -216,22 +236,24 @@ cat path-to/coverage-summary.md >> $GITHUB_STEP_SUMMARY
|
|
|
216
236
|
type: 'both'
|
|
217
237
|
}]
|
|
218
238
|
]
|
|
219
|
-
}
|
|
239
|
+
};
|
|
220
240
|
```
|
|
221
241
|
- Istanbul自定义报告
|
|
222
242
|
> 例子: [./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
243
|
- V8自定义报告
|
|
224
244
|
> 例子: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
|
|
225
245
|
|
|
226
|
-
`type`
|
|
227
|
-
- `'istanbul'` —
|
|
228
|
-
- `'v8'`
|
|
229
|
-
- `'both'` —
|
|
246
|
+
`type` 选项决定报告接口及执行时机:
|
|
247
|
+
- `'istanbul'` — 使用 Istanbul 报告接口:先调用 `new Reporter(reportOptions)`,再调用 `execute(context)`。context 提供 Istanbul 报告树和源码查找能力。V8 输入会先转换为 Istanbul 覆盖率数据。
|
|
248
|
+
- `'v8'`(默认值)— 对 V8 输入,先调用 `new Reporter(reportOptions, globalOptions)`,再调用 `await generate(reportData)`。`reportData` 是 V8 覆盖率结果,包含 `type`、`summary` 和 `files`;每个文件包含 V8 覆盖范围和汇总。
|
|
249
|
+
- `'both'` — 对 V8 或 Istanbul 输入均使用上述 `generate(reportData)` 接口。请检查 `reportData.type`,按对应的结果结构处理。它**不会**同时提供两份原始格式的数据,结果中不存在 `v8Data` 或 `istanbulData` 属性。
|
|
250
|
+
|
|
251
|
+
报告统一使用原生 ESM `import()` 加载。ESM 报告使用 `export default Reporter`;通过 Node 内置互操作,也可加载 `module.exports = Reporter` 的 CommonJS 报告(在 ESM 项目中使用 `.cjs`)。本地路径必须明确指定文件扩展名,不补全扩展名、不解析目录入口,不提供独立的 CommonJS 加载器。报告加载或初始化失败会使报告生成失败,不会静默跳过。
|
|
230
252
|
|
|
231
253
|
### Multiple Reports:
|
|
232
254
|
如何配置多个报告
|
|
233
255
|
```js
|
|
234
|
-
|
|
256
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
235
257
|
const coverageOptions = {
|
|
236
258
|
outputDir: './coverage-reports',
|
|
237
259
|
reports: [
|
|
@@ -248,15 +270,15 @@ const coverageOptions = {
|
|
|
248
270
|
|
|
249
271
|
// custom reports
|
|
250
272
|
// Specify reporter name with the NPM package
|
|
251
|
-
[
|
|
252
|
-
[
|
|
253
|
-
type:
|
|
254
|
-
key:
|
|
273
|
+
['custom-reporter-1'],
|
|
274
|
+
['custom-reporter-2', {
|
|
275
|
+
type: 'istanbul',
|
|
276
|
+
key: 'value'
|
|
255
277
|
}],
|
|
256
278
|
// Specify reporter name with local path
|
|
257
279
|
['/absolute/path/to/custom-reporter.js']
|
|
258
280
|
]
|
|
259
|
-
}
|
|
281
|
+
};
|
|
260
282
|
const mcr = MCR(coverageOptions);
|
|
261
283
|
```
|
|
262
284
|
|
|
@@ -324,7 +346,7 @@ await Promise.all([
|
|
|
324
346
|
})
|
|
325
347
|
]);
|
|
326
348
|
|
|
327
|
-
await page.goto(
|
|
349
|
+
await page.goto('your page url');
|
|
328
350
|
|
|
329
351
|
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
330
352
|
page.coverage.stopJSCoverage(),
|
|
@@ -353,7 +375,7 @@ await Promise.all([
|
|
|
353
375
|
})
|
|
354
376
|
]);
|
|
355
377
|
|
|
356
|
-
await page.goto(
|
|
378
|
+
await page.goto('your page url');
|
|
357
379
|
|
|
358
380
|
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
359
381
|
page.coverage.stopJSCoverage(),
|
|
@@ -406,30 +428,35 @@ const coverageData = [... jsCoverage.map((it) => {
|
|
|
406
428
|
- 如果是子进程,可参见 [命令行](#command-line)
|
|
407
429
|
|
|
408
430
|
### Collecting V8 Coverage Data with `CDPClient` API
|
|
431
|
+
|
|
432
|
+
`timeout` 控制 HTTP 调试目标发现和 WebSocket 连接的超时(默认 10000ms);`requestTimeout` 控制内置 WebSocket 会话中每条 CDP 命令的超时(默认 30000ms)。设为 `0` 可禁用相应超时。外部 Playwright/Puppeteer 会话自行管理命令超时。自定义 socket 可使用 `new MCR.WSSession(ws, { timeout: 30000 })`。
|
|
433
|
+
|
|
409
434
|
- `CDPClient`为`MCR`提供的内置接口类,用来更便捷的处理覆盖率相关数据,所有的API如下
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
435
|
+
```ts
|
|
436
|
+
interface CoverageClient {
|
|
437
|
+
// 开始和停止并收集JS的覆盖率数据
|
|
438
|
+
startJSCoverage: () => Promise<void>;
|
|
439
|
+
stopJSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
414
440
|
|
|
415
|
-
// 开始和停止并收集CSS的覆盖率数据,支持匿名文件(比如style里的css)
|
|
416
|
-
startCSSCoverage: () => Promise<void>;
|
|
417
|
-
stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
441
|
+
// 开始和停止并收集CSS的覆盖率数据,支持匿名文件(比如style里的css)
|
|
442
|
+
startCSSCoverage: () => Promise<void>;
|
|
443
|
+
stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
|
|
418
444
|
|
|
419
|
-
// 开始和停止并收集JS和CSS的覆盖率数据
|
|
420
|
-
startCoverage: () => Promise<void>;
|
|
421
|
-
stopCoverage: () => Promise<V8CoverageEntry[]>;
|
|
445
|
+
// 开始和停止并收集JS和CSS的覆盖率数据
|
|
446
|
+
startCoverage: () => Promise<void>;
|
|
447
|
+
stopCoverage: () => Promise<V8CoverageEntry[]>;
|
|
422
448
|
|
|
423
|
-
/** 如果开启了NODE_V8_COVERAGE,这个接口用来手动保存当前覆盖率数据 */
|
|
424
|
-
writeCoverage: () => Promise<string>;
|
|
449
|
+
/** 如果开启了NODE_V8_COVERAGE,这个接口用来手动保存当前覆盖率数据 */
|
|
450
|
+
writeCoverage: () => Promise<string>;
|
|
425
451
|
|
|
426
|
-
/** 收集istanbul覆盖率数据 */
|
|
427
|
-
getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
|
|
452
|
+
/** 收集istanbul覆盖率数据 */
|
|
453
|
+
getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
|
|
454
|
+
}
|
|
428
455
|
```
|
|
429
456
|
|
|
430
457
|
- 结合使用Node调试端口`--inspect=9229` 或者浏览器调试端口 `--remote-debugging-port=9229`
|
|
431
458
|
```js
|
|
432
|
-
|
|
459
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
433
460
|
const client = await MCR.CDPClient({
|
|
434
461
|
port: 9229
|
|
435
462
|
});
|
|
@@ -440,8 +467,8 @@ const coverageData = await client.stopJSCoverage();
|
|
|
440
467
|
|
|
441
468
|
- 结合使用 [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
|
|
442
469
|
```js
|
|
443
|
-
|
|
444
|
-
|
|
470
|
+
import { chromium } from 'playwright';
|
|
471
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
445
472
|
const browser = await chromium.launch();
|
|
446
473
|
const page = await browser.newPage();
|
|
447
474
|
const session = await page.context().newCDPSession(page);
|
|
@@ -451,14 +478,14 @@ const client = await MCR.CDPClient({
|
|
|
451
478
|
// both js and css coverage
|
|
452
479
|
await client.startCoverage();
|
|
453
480
|
// run your test page here
|
|
454
|
-
await page.goto(
|
|
481
|
+
await page.goto('your page url');
|
|
455
482
|
const coverageData = await client.stopCoverage();
|
|
456
483
|
```
|
|
457
484
|
|
|
458
485
|
- 结合使用 [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
|
|
459
486
|
```js
|
|
460
|
-
|
|
461
|
-
|
|
487
|
+
import puppeteer from 'puppeteer';
|
|
488
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
462
489
|
const browser = await puppeteer.launch({});
|
|
463
490
|
const page = await browser.newPage();
|
|
464
491
|
const session = await page.target().createCDPSession();
|
|
@@ -468,20 +495,20 @@ const client = await MCR.CDPClient({
|
|
|
468
495
|
// both js and css coverage
|
|
469
496
|
await client.startCoverage();
|
|
470
497
|
// run your test page here
|
|
471
|
-
await page.goto(
|
|
498
|
+
await page.goto('your page url');
|
|
472
499
|
const coverageData = await client.stopCoverage();
|
|
473
500
|
```
|
|
474
501
|
|
|
475
502
|
- 结合使用 [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (仅支持Chrome/Edge浏览器)
|
|
476
503
|
```js
|
|
477
|
-
|
|
478
|
-
|
|
504
|
+
import { Builder, Browser } from 'selenium-webdriver';
|
|
505
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
479
506
|
const driver = await new Builder().forBrowser(Browser.CHROME).build();
|
|
480
507
|
const pageCdpConnection = await driver.createCDPConnection('page');
|
|
481
508
|
const session = new MCR.WSSession(pageCdpConnection._wsConnection);
|
|
482
509
|
const client = await MCR.CDPClient({
|
|
483
510
|
session
|
|
484
|
-
})
|
|
511
|
+
});
|
|
485
512
|
```
|
|
486
513
|
|
|
487
514
|
### V8 Coverage Data API
|
|
@@ -489,7 +516,7 @@ const client = await MCR.CDPClient({
|
|
|
489
516
|
- [Playwright的覆盖率接口](https://playwright.dev/docs/api/class-coverage)
|
|
490
517
|
- [Puppeteer的覆盖率接口](https://pptr.dev/api/puppeteer.coverage)
|
|
491
518
|
- [DevTools Protocol的覆盖率接口](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) 参见 [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) 和 [v8-coverage](https://github.com/bcoe/v8-coverage)
|
|
492
|
-
```
|
|
519
|
+
```ts
|
|
493
520
|
// Coverage data for a source range.
|
|
494
521
|
export interface CoverageRange {
|
|
495
522
|
// JavaScript script source offset for the range start.
|
|
@@ -578,15 +605,15 @@ export type V8CoverageData = ScriptCoverage[];
|
|
|
578
605
|
过滤可以使用函数:
|
|
579
606
|
```js
|
|
580
607
|
const coverageOptions = {
|
|
581
|
-
entryFilter: (entry) => entry.url.indexOf(
|
|
608
|
+
entryFilter: (entry) => entry.url.indexOf('main.js') !== -1,
|
|
582
609
|
sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
|
|
583
610
|
};
|
|
584
611
|
```
|
|
585
612
|
也可以使用便捷的[`minimatch`](https://github.com/isaacs/minimatch)来匹配(推荐):
|
|
586
613
|
```js
|
|
587
614
|
const coverageOptions = {
|
|
588
|
-
entryFilter:
|
|
589
|
-
sourceFilter:
|
|
615
|
+
entryFilter: '**/main.js',
|
|
616
|
+
sourceFilter: '**/src/**'
|
|
590
617
|
};
|
|
591
618
|
```
|
|
592
619
|
支持多个匹配:
|
|
@@ -611,10 +638,17 @@ mcr --sourceFilter "{'**/node_modules/**':false,'**/**':true}"
|
|
|
611
638
|
```js
|
|
612
639
|
const coverageOptions = {
|
|
613
640
|
entryFilter: (entry) => {
|
|
614
|
-
if (minimatch(entry.url, '**/node_modules/**')) {
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
641
|
+
if (minimatch(entry.url, '**/node_modules/**')) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
if (minimatch(entry.url, '**/vendor.js')) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
if (minimatch(entry.url, '**/src/**')) {
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
// else unmatched
|
|
651
|
+
return false;
|
|
618
652
|
}
|
|
619
653
|
};
|
|
620
654
|
```
|
|
@@ -633,6 +667,42 @@ const coverageOptions = {
|
|
|
633
667
|
};
|
|
634
668
|
```
|
|
635
669
|
|
|
670
|
+
### 使用 `outputFilter` 过滤最终文件
|
|
671
|
+
> **版本要求:** `outputFilter` 是 **v3.0.0** 新增的选项,v2.13.x 及更早版本不支持。
|
|
672
|
+
|
|
673
|
+
**两套过滤方案选一套即可,不要混淆。** `outputFilter` 与已有的 `entryFilter` + `sourceFilter` 都用于决定报告中保留哪些文件,功能重叠,是两种替代方案,而不是必须叠加配置的过滤器。合并选项 `filter` 只是 `entryFilter` + `sourceFilter` 的简写,不会配置 `outputFilter`。
|
|
674
|
+
|
|
675
|
+
| 方案 | 过滤时机和对象 | 优点 | 代价与注意事项 |
|
|
676
|
+
| :-- | :-- | :-- | :-- |
|
|
677
|
+
| `entryFilter` + `sourceFilter`(或 `filter`) | 从输入端过滤:在发现入口文件时,`entryFilter` 按 URL 过滤;在 sourcemap 解包源文件时,`sourceFilter` 按源路径过滤。 | 尽早排除不需要的文件,减少后续无效处理,性能更高。大型项目或 bundle 较多时,优先推荐此方案。 | 需要区分入口文件和 sourcemap 源文件,并配置两个阶段的规则。如果通过 `entryFilter` 排除整个 bundle,其中需要保留的源文件也会一起丢失。 |
|
|
678
|
+
| `outputFilter` | 从输出结果过滤:在 sourcemap 转换、合并和添加未测试文件(`all`)完成后、生成报告前,按最终 `sourcePath` 过滤。 | 配置更简单,一套规则直接面向最终文件列表,无需区分入口文件和解包后的源文件。 | 不需要的文件在被丢弃前仍会经历前期发现和处理,无法节省这些开销。当最终排除的文件较多时,可能带来明显的生成耗时和内存开销,存在性能问题。 |
|
|
679
|
+
|
|
680
|
+
**选择建议:** 优先考虑性能时,使用输入过滤方案(`entryFilter` + `sourceFilter`,或 `filter`);更看重配置简单、希望直接过滤最终结果,且可以接受额外处理成本时,选择 `outputFilter`。切换到 `outputFilter` 时,应移除同一范围的旧输入过滤配置:输入阶段已经排除的文件,无法通过输出过滤恢复。
|
|
681
|
+
|
|
682
|
+
对于 V8 输入,`outputFilter` 同时作用于 V8 报告和转换后的 Istanbul 报告。默认值为 `null`,不启用过滤。
|
|
683
|
+
|
|
684
|
+
它与 `entryFilter` 和 `sourceFilter` 使用相同的字符串和有序对象匹配规则:按顺序返回第一个匹配项的值,未匹配的文件不保留。匹配目标是经过 `sourcePath` 改写后的最终路径。函数形式接收最终文件对象,而不是路径字符串。
|
|
685
|
+
|
|
686
|
+
```js
|
|
687
|
+
const coverageOptions = {
|
|
688
|
+
outputFilter: {
|
|
689
|
+
'**/node_modules/**': false,
|
|
690
|
+
'**/src/**': true
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
// 也可以使用:
|
|
694
|
+
// outputFilter: '**/src/**'
|
|
695
|
+
// outputFilter: (entry) => entry.sourcePath.startsWith('src/')
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
命令行示例(使用引号避免 shell 展开匹配规则):
|
|
699
|
+
```sh
|
|
700
|
+
mcr merge --inputDir ./raw --outputFilter "**/src/**"
|
|
701
|
+
mcr merge --inputDir ./raw --outputFilter "{'**/node_modules/**':false,'**/src/**':true}"
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
`outputFilter` 不改变 raw 原始覆盖率导出,也不适用于 Istanbul 格式的输入数据。
|
|
705
|
+
|
|
636
706
|
## Resolve `sourcePath` for the Source Files
|
|
637
707
|
当一个文件从sourcemap解包,它的路径可能是个虚拟路径, 此时可以使用`sourcePath`选项来修改文件路径。比如,我们测试了多个dist包的入口文件,它们的源文件可能包含了一些共同的文件,但路径可能不同,如果我们需要相同的文件覆盖率数据可以自动合并,那么需要使用`sourcePath`来统一这些相同文件的路径
|
|
638
708
|
```js
|
|
@@ -653,34 +723,34 @@ const coverageOptions = {
|
|
|
653
723
|
```js
|
|
654
724
|
const coverageOptions = {
|
|
655
725
|
sourcePath: {
|
|
656
|
-
'my-dist-file1/': '',
|
|
726
|
+
'my-dist-file1/': '',
|
|
657
727
|
'my-dist-file2/': ''
|
|
658
728
|
}
|
|
659
729
|
};
|
|
660
730
|
```
|
|
661
731
|
解决文件路径不完整的问题:
|
|
662
732
|
```js
|
|
663
|
-
|
|
733
|
+
import path from 'node:path';
|
|
664
734
|
|
|
665
735
|
// MCR coverage options
|
|
666
736
|
const coverageOptions = {
|
|
667
|
-
sourcePath: (filePath, info)=> {
|
|
737
|
+
sourcePath: (filePath, info) => {
|
|
668
738
|
if (!filePath.includes('/') && info.distFile) {
|
|
669
739
|
return `${path.dirname(info.distFile)}/${filePath}`;
|
|
670
740
|
}
|
|
671
741
|
return filePath;
|
|
672
742
|
}
|
|
673
|
-
}
|
|
743
|
+
};
|
|
674
744
|
```
|
|
675
745
|
|
|
676
746
|
## Adding Empty Coverage for Untested Files
|
|
677
747
|
默认,未测试的文件不会包含在覆盖率报告中。使用 `all` 选项可将它们以 0% 覆盖率加入报告。即使没有收集到任何覆盖率数据,也会为匹配的未测试文件生成 V8 报告。
|
|
678
748
|
```js
|
|
679
749
|
const coverageOptions = {
|
|
680
|
-
all: './src'
|
|
750
|
+
all: './src'
|
|
681
751
|
|
|
682
752
|
// 支持多个目录
|
|
683
|
-
all: ['./src', './lib']
|
|
753
|
+
// all: ['./src', './lib']
|
|
684
754
|
};
|
|
685
755
|
```
|
|
686
756
|
未测试的文件也适用于`sourceFilter`过滤器. 而且也可以指定自己的`filter`过滤器 (可以返回文件类型来支持js或css的覆盖率格式):
|
|
@@ -693,30 +763,29 @@ const coverageOptions = {
|
|
|
693
763
|
'**/ignored-*.js': false,
|
|
694
764
|
'**/*.html': false,
|
|
695
765
|
// empty css coverage
|
|
696
|
-
'**/*.scss':
|
|
766
|
+
'**/*.scss': 'css',
|
|
697
767
|
'**/*': true
|
|
698
768
|
}
|
|
699
769
|
}
|
|
700
770
|
};
|
|
701
771
|
```
|
|
702
|
-
|
|
772
|
+
可以将未测试的文件转换成 JavaScript,以便进行 AST 分析。以下示例仅处理**不含 JSX 的 TypeScript**,并假定已安装 `@swc/core`。JSX/TSX 或 Vue 应使用匹配的编译器和解析器,这不是通用转换器。保留完整文件路径,避免不同目录下的同名文件冲突;SWC 返回的 sourcemap 是 JSON 字符串,赋值前需要解析。
|
|
703
773
|
```js
|
|
704
|
-
|
|
705
|
-
|
|
774
|
+
import { fileURLToPath } from 'node:url';
|
|
775
|
+
import { transform } from '@swc/core';
|
|
706
776
|
const coverageOptions = {
|
|
707
777
|
all: {
|
|
708
778
|
dir: ['./src'],
|
|
779
|
+
filter: '**/*.ts',
|
|
709
780
|
transformer: async (entry) => {
|
|
710
|
-
const { code, map } = await
|
|
711
|
-
filename:
|
|
781
|
+
const { code, map } = await transform(entry.source, {
|
|
782
|
+
filename: fileURLToPath(entry.url),
|
|
712
783
|
sourceMaps: true,
|
|
713
784
|
isModule: true,
|
|
714
785
|
jsc: {
|
|
715
786
|
parser: {
|
|
716
|
-
syntax:
|
|
717
|
-
|
|
718
|
-
},
|
|
719
|
-
transform: {}
|
|
787
|
+
syntax: 'typescript'
|
|
788
|
+
}
|
|
720
789
|
}
|
|
721
790
|
});
|
|
722
791
|
entry.source = code;
|
|
@@ -729,36 +798,45 @@ const coverageOptions = {
|
|
|
729
798
|
## Hooks
|
|
730
799
|
|
|
731
800
|
### `onEnd` — 报告生成后触发
|
|
732
|
-
|
|
801
|
+
典型用法:检测覆盖率是否达标,失败时抛错,让 API 调用方处理异常、CLI 返回非零状态;不要使用 `process.exit()` 提前中断清理。
|
|
733
802
|
```js
|
|
734
|
-
const
|
|
803
|
+
const getCoverageError = (metric, summary, minimum) => {
|
|
804
|
+
if (!summary || !(summary.total > 0)) {
|
|
805
|
+
return `${metric}: missing or empty coverage`;
|
|
806
|
+
}
|
|
807
|
+
const { pct } = summary;
|
|
808
|
+
if (typeof pct !== 'number' || !Number.isFinite(pct) || pct < minimum) {
|
|
809
|
+
return `${metric}: ${pct}%, required ${minimum}%`;
|
|
810
|
+
}
|
|
811
|
+
return '';
|
|
812
|
+
};
|
|
735
813
|
const coverageOptions = {
|
|
736
814
|
name: 'My Coverage Report',
|
|
737
815
|
outputDir: './coverage-reports',
|
|
738
816
|
onEnd: (coverageResults) => {
|
|
817
|
+
if (!coverageResults) {
|
|
818
|
+
throw new Error('No coverage results');
|
|
819
|
+
}
|
|
739
820
|
const thresholds = {
|
|
740
821
|
bytes: 80,
|
|
741
822
|
lines: 60
|
|
742
823
|
};
|
|
743
|
-
console.log('check thresholds ...', thresholds);
|
|
744
824
|
const errors = [];
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
errors.push(`Coverage threshold for ${k} (${pct} %) not met: ${thresholds[k]} %`);
|
|
825
|
+
for (const [metric, minimum] of Object.entries(thresholds)) {
|
|
826
|
+
const error = getCoverageError(metric, coverageResults.summary?.[metric], minimum);
|
|
827
|
+
if (error) {
|
|
828
|
+
errors.push(error);
|
|
750
829
|
}
|
|
751
|
-
}
|
|
830
|
+
}
|
|
752
831
|
if (errors.length) {
|
|
753
|
-
|
|
754
|
-
console.log(EC.red(errMsg));
|
|
755
|
-
// throw new Error(errMsg);
|
|
756
|
-
// process.exit(1);
|
|
832
|
+
throw new Error(errors.join('\n'));
|
|
757
833
|
}
|
|
758
834
|
}
|
|
759
|
-
}
|
|
835
|
+
};
|
|
760
836
|
```
|
|
761
837
|
|
|
838
|
+
本示例拒绝缺失或总数为零的指标;如果某项指标允许为空,应明确调整策略。没有可用输入时,`generate()` 可能直接返回 `undefined` 而不调用 `onEnd`。API 调用方必须检查返回值;普通 CLI 流程应使用干净的输出目录,并验证本次生成的非空报告。参见 [严格 CI 示例](skills/monocart-coverage-reports/references/reports-and-ci.md#reject-stale-reports-and-missing-collection-in-ci)。
|
|
839
|
+
|
|
762
840
|
### `onEntry` — 每个V8入口在处理前触发(仅V8)
|
|
763
841
|
可以在进入AST解析前改写 entry(例如对非标准语法的源码做预编译),参见 [Unparsable source](#unparsable-source)
|
|
764
842
|
```js
|
|
@@ -775,7 +853,7 @@ const coverageOptions = {
|
|
|
775
853
|
- `onReady(coverageReport, nodeV8CoverageDir, subprocess)` — 子进程退出后、MCR 读取 `nodeV8CoverageDir` 之前触发。用于子进程尚未写完覆盖数据需要等待的场景
|
|
776
854
|
```js
|
|
777
855
|
// mcr.config.js
|
|
778
|
-
|
|
856
|
+
export default {
|
|
779
857
|
onReady: async (coverageReport, nodeV8CoverageDir, subprocess) => {
|
|
780
858
|
// 等待某个信号,或先对 raw 文件做一些预处理
|
|
781
859
|
}
|
|
@@ -783,6 +861,8 @@ module.exports = {
|
|
|
783
861
|
```
|
|
784
862
|
|
|
785
863
|
## Ignoring Uncovered Codes
|
|
864
|
+
<!-- readme-lint-disable -->
|
|
865
|
+
|
|
786
866
|
使用特定的注释,以`v8 ignore `开头可以忽略未覆盖的代码:
|
|
787
867
|
- 忽略开始到结束
|
|
788
868
|
```js
|
|
@@ -817,12 +897,14 @@ function uncovered() {
|
|
|
817
897
|
```
|
|
818
898
|
- 可以设置 `v8Ignore: false` 完全禁用基于注释的忽略机制。
|
|
819
899
|
|
|
900
|
+
<!-- readme-lint-enable -->
|
|
901
|
+
|
|
820
902
|
## Multiprocessing Support
|
|
821
903
|
> 多进程支持可以很好的解决异步并行的情况。所有的覆盖率数据会保存到`[outputDir]/.cache`,在报告生成之后,这些缓存数据会被清除。除非开启了[调试模式](#debug-for-coverage-and-sourcemap),或者使用了`raw`报告
|
|
822
904
|
- 主进程,初始化,清理之前的缓存
|
|
823
905
|
```js
|
|
824
|
-
|
|
825
|
-
|
|
906
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
907
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
826
908
|
const mcr = MCR(coverageOptions);
|
|
827
909
|
// clean previous cache before the start of testing
|
|
828
910
|
// unless the running environment is new and no cache
|
|
@@ -831,16 +913,16 @@ mcr.cleanCache();
|
|
|
831
913
|
|
|
832
914
|
- 子进程1, 测试业务1
|
|
833
915
|
```js
|
|
834
|
-
|
|
835
|
-
|
|
916
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
917
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
836
918
|
const mcr = MCR(coverageOptions);
|
|
837
919
|
await mcr.add(coverageData1);
|
|
838
920
|
```
|
|
839
921
|
|
|
840
922
|
- 子进程2, 测试业务2
|
|
841
923
|
```js
|
|
842
|
-
|
|
843
|
-
|
|
924
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
925
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
844
926
|
const mcr = MCR(coverageOptions);
|
|
845
927
|
await mcr.add(coverageData2);
|
|
846
928
|
```
|
|
@@ -848,8 +930,8 @@ await mcr.add(coverageData2);
|
|
|
848
930
|
- 主进程,所有测试完成之后
|
|
849
931
|
```js
|
|
850
932
|
// generate coverage reports after the completion of testing
|
|
851
|
-
|
|
852
|
-
|
|
933
|
+
import { MCR } from 'monocart-coverage-reports';
|
|
934
|
+
import coverageOptions from 'path-to/same-options.js';
|
|
853
935
|
const mcr = MCR(coverageOptions);
|
|
854
936
|
await mcr.generate();
|
|
855
937
|
```
|
|
@@ -871,14 +953,17 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
|
|
|
871
953
|
|
|
872
954
|
- CLI 选项
|
|
873
955
|
|
|
956
|
+
命令使用 Execa 的默认选项执行,不主动开启 shell 模式。请分别传入可执行文件及参数,例如 `mcr node "./my app.js"`,不要把整条命令作为一个带引号的参数传入。MCR 保留参数边界,不展开环境变量或 glob,也不解析管道、重定向、`&&` 等 shell 语法;组合命令请放入脚本。使用 `mcr [options] -- executable ...args` 可以避免子命令参数被当作 MCR 参数解析。平台相关的命令执行由 Execa 处理,包括在 Windows 下按需通过 `cmd.exe` 调用批处理文件并转义参数。
|
|
957
|
+
|
|
874
958
|
以下选项仅在 CLI 模式(`mcr <command>`)下可用:
|
|
875
959
|
|
|
876
960
|
| 选项 | 类型 | 默认值 | 说明 |
|
|
877
961
|
| :-- | :-- | :-- | :-- |
|
|
878
962
|
| `-c, --config` | `string` | — | 自定义配置文件路径 |
|
|
879
|
-
| `--import` | `string` | `null` | 通过 `NODE_OPTIONS` 预加载 ESM 模块,如 `tsx
|
|
880
|
-
| `--require` | `string` | `null` |
|
|
881
|
-
| `--env` | `string` | `null` | 从 dotenv 文件加载环境变量(默认 `.env
|
|
963
|
+
| `--import` | `string` | `null` | 通过 `NODE_OPTIONS` 预加载 ESM 模块,如 `tsx`。|
|
|
964
|
+
| `--require` | `string` | `null` | `--import` 的已弃用别名;使用 ESM 加载,不使用 CommonJS 解析 |
|
|
965
|
+
| `--env` | `string` | `null` | 从 dotenv 文件加载环境变量(默认 `.env`)。|
|
|
966
|
+
| `test`(仅配置文件) | `string \| string[] \| null` | `null` | 未指定命令行目标时,原样传给 `node --test` 的默认测试文件路径或 glob,**仅 `mcr test` 使用**;其他命令和 API 忽略。需要 Node.js `>= 22.15.0` |
|
|
882
967
|
| `onStart` | `(coverageReport) => Promise<void>` | `null` | 子进程启动前触发;参见 [Hooks](#hooks) |
|
|
883
968
|
| `onReady` | `(coverageReport, nodeV8CoverageDir, subprocess) => Promise<void>` | `null` | 子进程退出后、MCR 读取覆盖率前触发;参见 [Hooks](#hooks) |
|
|
884
969
|
|
|
@@ -889,16 +974,18 @@ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
|
|
|
889
974
|
mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
|
|
890
975
|
```
|
|
891
976
|
|
|
892
|
-
- `--import <module>` / `--require <module>`: 通过 `NODE_OPTIONS` 把一个预加载模块传给子进程。常用于 TypeScript/JSX 运行时(`tsx`、`ts-node` 等),也可用于加载 `mcr.config.ts
|
|
977
|
+
- `--import <module>` / `--require <module>`: 通过 `NODE_OPTIONS` 把一个预加载模块传给子进程。常用于 TypeScript/JSX 运行时(`tsx`、`ts-node` 等),也可用于加载 `mcr.config.ts`。在所有受支持的 Node.js 版本中均可用。本地路径以当前工作目录为基准,必须明确指定文件扩展名,支持包含空格的路径。包名由原生 ESM 按 MCR 模块位置解析。两个选项都使用 `import()`,并向 Node 传递 `--import`;`--require` 仅为已弃用别名,不提供 CommonJS 加载逻辑。CJS 模块仅通过 Node 内置的 import 互操作支持。
|
|
893
978
|
```sh
|
|
894
979
|
mcr --import tsx node ./test.ts
|
|
895
980
|
```
|
|
896
981
|
|
|
897
|
-
- `--env [path]`: 子进程启动前从 dotenv 文件加载环境变量(默认 `.env
|
|
982
|
+
- `--env [path]`: 子进程启动前从 dotenv 文件加载环境变量(默认 `.env`)。使用 `process.loadEnvFile`,在所有受支持的 Node.js 版本中均可用
|
|
898
983
|
```sh
|
|
899
984
|
mcr --env .env.test node ./test.js
|
|
900
985
|
```
|
|
901
986
|
|
|
987
|
+
- `test`:运行 Node.js 测试并生成覆盖率报告,参见[使用 `mcr test` 运行测试](#run-tests-with-mcr-test)。
|
|
988
|
+
|
|
902
989
|
- `merge`: 直接合并 `inputDir` 中的原始覆盖率数据并生成合并报告,无需启动子进程。
|
|
903
990
|
```sh
|
|
904
991
|
mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverage-reports/e2e/raw --outputDir ./coverage-reports/merged
|
|
@@ -911,6 +998,8 @@ mcr merge -r v8,console-details --inputDir ./coverage-reports/unit/raw,./coverag
|
|
|
911
998
|
- [AVA](#ava)
|
|
912
999
|
|
|
913
1000
|
## Config File
|
|
1001
|
+
MCR 3.x 中,显式指定的配置不存在,或找到的配置加载失败(包括 JSON 解析、模块语法及初始化错误)时,`loadConfig()` 会直接抛出异常,所有 CLI 命令也会非零退出,不再需要 `strict` 参数。未指定自定义配置且找不到默认配置时,仍使用已有选项继续执行。
|
|
1002
|
+
|
|
914
1003
|
根据以下优先级加载配置文件
|
|
915
1004
|
- 自定义配置文件(如果没有指定则加载后面的默认配置文件):
|
|
916
1005
|
- CLI: `mcr --config <my-config-file-path>`
|
|
@@ -926,6 +1015,59 @@ mcr --import tsx -c mcr.config.ts node ./test.js
|
|
|
926
1015
|
|
|
927
1016
|
> 未指定自定义路径时,MCR 会从当前工作目录向上查找默认配置文件(通过 [`find-up`](https://github.com/sindresorhus/find-up))。不在搜索路径内的文件会被忽略
|
|
928
1017
|
|
|
1018
|
+
## Run Tests with `mcr test`
|
|
1019
|
+
|
|
1020
|
+
**MCR 3.x 重要更新:** 使用内置的 [Node.js 测试 runner](https://nodejs.org/docs/latest/api/test.html),一条命令即可运行测试并自动生成覆盖率报告。需要 **Node.js >= 22.15.0**。
|
|
1021
|
+
|
|
1022
|
+
安装 MCR 后,直接指定测试文件即可运行,无需配置文件:
|
|
1023
|
+
|
|
1024
|
+
```sh
|
|
1025
|
+
npm install --save-dev monocart-coverage-reports
|
|
1026
|
+
npx mcr test "./test/**/*.test.js" -r v8,console-summary -o ./coverage-reports
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
此命令会运行匹配的测试,在终端打印覆盖率摘要,并将 V8 HTML 报告写入 `./coverage-reports/index.html`。请为 glob 加引号,让 Node.js 而不是 shell 处理匹配。
|
|
1030
|
+
|
|
1031
|
+
对于需要反复执行的测试,可将测试目标和覆盖率选项保存到 `mcr.config.js`(ESM;需在 `package.json` 设置 `"type": "module"`,或将配置命名为 `mcr.config.mjs`):
|
|
1032
|
+
|
|
1033
|
+
```js
|
|
1034
|
+
export default {
|
|
1035
|
+
test: ['./test/unit/**/*.test.js', './test/integration/**/*.test.js'],
|
|
1036
|
+
reports: ['v8', 'console-summary'],
|
|
1037
|
+
outputDir: './coverage-reports'
|
|
1038
|
+
};
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
```sh
|
|
1042
|
+
# 运行配置中的测试目标
|
|
1043
|
+
npx mcr test
|
|
1044
|
+
# 覆盖配置中的测试目标,并在 -- 后透传 Node.js 测试 runner 参数
|
|
1045
|
+
npx mcr test "./test/unit/**/*.test.js" -- --test-reporter=spec
|
|
1046
|
+
|
|
1047
|
+
# 显式指定配置文件
|
|
1048
|
+
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js
|
|
1049
|
+
|
|
1050
|
+
# 在命令行选择报告
|
|
1051
|
+
npx mcr test "../tests/**/*.test.js" -r v8,console-summary
|
|
1052
|
+
|
|
1053
|
+
# 运行多个测试文件
|
|
1054
|
+
npx mcr test ./test/first.test.js ./test/second.test.js
|
|
1055
|
+
|
|
1056
|
+
# 按测试名称筛选,并指定 reporter
|
|
1057
|
+
npx mcr test "../tests/**/*.test.js" -c ./mcr.config.js -- --test-name-pattern="my test" --test-reporter=spec
|
|
1058
|
+
|
|
1059
|
+
# 将 test 命令放在初始分隔符后
|
|
1060
|
+
npx mcr -c ./mcr.config.js -- test "../tests/**/*.test.js" -- --test-reporter=spec
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
命令行指定的测试目标会**覆盖**配置中的 `test`,而不是追加;未指定命令行目标时才使用配置。命令行指定目标时可以不使用配置文件。原有 MCR 参数(如 `-c`、`-r`、`-o`、`--lcov`)可以放在目标之前或之后。Node runner 参数请放在 `--` 后,原样透传,不作为 MCR 参数或目标覆盖项解析。建议为 glob 加引号,避免 shell 提前展开。使用另一种 `mcr [options] -- test ...` 形式时,所有 MCR 参数必须放在第一个 `--` 前,Node runner 参数则放在第二个 `--` 后。
|
|
1064
|
+
|
|
1065
|
+
测试目标原样传给 `node --test`。MCR 不检查目标类型或文件是否存在,不展开目录/glob,不解析或标准化路径,不转义特殊字符,也不去重;`node --test` 原生接收测试文件路径或 glob 形式的位置参数,而不是 JavaScript 配置对象。MCR 配置中的字符串表示一个这样的参数(不会按空格拆分),字符串数组表示多个按原顺序传递的参数;`null` 仅是 MCR 跳过测试的默认值,不会传给 Node。测试发现、匹配、参数解释及错误行为均交给 Node。相对路径以当前工作目录为基准,而不是配置文件目录。不承诺直接指定目录会递归发现测试;递归匹配请使用 `"../tests/**/*.test.js"` 等 glob,行为以当前 Node 版本的原生支持为准。TypeScript 执行能力取决于 Node 版本或 `--import tsx` 等加载器。
|
|
1066
|
+
|
|
1067
|
+
`test` 默认为 `null`;未指定命令行目标,且 `test` 未配置或为 `null`、空字符串、`[]` 时跳过测试,也不生成报告。MCR 仅判断是否提供了目标,无效或未匹配目标由 Node 自行处理。如果没有产生覆盖率数据且无法生成报告,命令仍会在报告生成阶段失败。显式指定的配置文件不存在或配置加载失败时,也会失败退出,不会静默跳过测试。测试失败仍生成覆盖率报告,并保留非零退出状态;报告生成失败同样非零退出。支持现有覆盖率配置和 CLI hooks,可按需通过过滤器排除测试文件。**`test` 配置在其他 CLI 命令和 API 调用中均不生效**,包括 `merge` 和 `generate()`。
|
|
1068
|
+
|
|
1069
|
+
其他 CLI 选项参见[命令行](#command-line)。
|
|
1070
|
+
|
|
929
1071
|
## Merge Coverage Reports
|
|
930
1072
|
以下这些使用场景可能需要使用合并覆盖率报告:
|
|
931
1073
|
- 多个执行环境,比如Node.js服务端,以及浏览器客户端,比如`Next.js`
|
|
@@ -945,12 +1087,12 @@ mcr --import tsx -c mcr.config.ts node ./test.js
|
|
|
945
1087
|
```js
|
|
946
1088
|
const coverageOptions = {
|
|
947
1089
|
name: 'My Unit Test Coverage Report',
|
|
948
|
-
outputDir:
|
|
1090
|
+
outputDir: './coverage-reports/unit',
|
|
949
1091
|
reports: [
|
|
950
1092
|
['raw', {
|
|
951
1093
|
// relative path will be "./coverage-reports/unit/raw"
|
|
952
1094
|
// defaults to raw
|
|
953
|
-
outputDir:
|
|
1095
|
+
outputDir: 'raw'
|
|
954
1096
|
}],
|
|
955
1097
|
['v8'],
|
|
956
1098
|
['console-details']
|
|
@@ -966,8 +1108,8 @@ const coverageOptions = {
|
|
|
966
1108
|
- 然后创建一个`merge-coverage.js`文件,使用`inputDir`参数导入`raw`数据,来生成合并的覆盖率报告.
|
|
967
1109
|
```js
|
|
968
1110
|
// merge-coverage.js
|
|
969
|
-
|
|
970
|
-
|
|
1111
|
+
import fs from 'fs';
|
|
1112
|
+
import { CoverageReport } from 'monocart-coverage-reports';
|
|
971
1113
|
const inputDir = [
|
|
972
1114
|
'./coverage-reports/unit/raw',
|
|
973
1115
|
'./coverage-reports/e2e/raw'
|
|
@@ -986,7 +1128,7 @@ const coverageOptions = {
|
|
|
986
1128
|
'**/node_modules/**': false,
|
|
987
1129
|
'**/src/**': true
|
|
988
1130
|
},
|
|
989
|
-
|
|
1131
|
+
|
|
990
1132
|
sourcePath: (filePath, info) => {
|
|
991
1133
|
// Unify the file path for the same files
|
|
992
1134
|
// For example, the file index.js has different paths:
|
|
@@ -1000,7 +1142,7 @@ const coverageOptions = {
|
|
|
1000
1142
|
['v8'],
|
|
1001
1143
|
['console-details']
|
|
1002
1144
|
],
|
|
1003
|
-
|
|
1145
|
+
|
|
1004
1146
|
onEnd: () => {
|
|
1005
1147
|
// remove the raw files if no longer needed
|
|
1006
1148
|
// inputDir.forEach((p) => {
|
|
@@ -1030,7 +1172,7 @@ await new CoverageReport(coverageOptions).generate();
|
|
|
1030
1172
|
> 常见问题
|
|
1031
1173
|
### Unexpected coverage
|
|
1032
1174
|
覆盖率看起来不正确,多数情况是因为sourcemap转换的问题导致的. 可以先尝试设置构建工具的 `minify=false` 也就是不要压缩代码来解决。下面来看看sourcemap存在问题的具体原因:
|
|
1033
|
-
```
|
|
1175
|
+
```text
|
|
1034
1176
|
const a = tf ? 'true' : 'false';
|
|
1035
1177
|
^ ^ ^
|
|
1036
1178
|
m1 p m2
|
|
@@ -1046,21 +1188,21 @@ const a = tf ? 'true' : 'false';
|
|
|
1046
1188
|
### Unparsable source
|
|
1047
1189
|
源码无法解析问题。由上面我们知道`MCR`通过分析源码的AST获取更多指标的覆盖率信息,但源码如果不是标准的 ECMAScript,比如`ts`, `jsx`这些,那么分析的时候就会报错,此时我们可以手动来编译这些文件(可行但不推荐).
|
|
1048
1190
|
```js
|
|
1049
|
-
import * as fs from
|
|
1050
|
-
import * as path from
|
|
1051
|
-
import { fileURLToPath } from
|
|
1191
|
+
import * as fs from 'fs';
|
|
1192
|
+
import * as path from 'path';
|
|
1193
|
+
import { fileURLToPath } from 'url';
|
|
1052
1194
|
import * as TsNode from 'ts-node';
|
|
1053
1195
|
const coverageOptions = {
|
|
1054
|
-
onEntry:
|
|
1055
|
-
const filePath = fileURLToPath(entry.url)
|
|
1056
|
-
const originalSource = fs.readFileSync(filePath).toString(
|
|
1196
|
+
onEntry: (entry) => {
|
|
1197
|
+
const filePath = fileURLToPath(entry.url);
|
|
1198
|
+
const originalSource = fs.readFileSync(filePath).toString('utf-8');
|
|
1057
1199
|
const fileName = path.basename(filePath);
|
|
1058
1200
|
const tn = TsNode.create({});
|
|
1059
1201
|
const source = tn.compile(originalSource, fileName);
|
|
1060
1202
|
entry.fake = false;
|
|
1061
1203
|
entry.source = source;
|
|
1062
1204
|
}
|
|
1063
|
-
}
|
|
1205
|
+
};
|
|
1064
1206
|
```
|
|
1065
1207
|
|
|
1066
1208
|
### JavaScript heap out of memory
|
|
@@ -1101,7 +1243,7 @@ const coverageOptions = {
|
|
|
1101
1243
|
|
|
1102
1244
|
- 使用环境变量`MCR_LOG_TIME`显示时间日志
|
|
1103
1245
|
```js
|
|
1104
|
-
process.env.MCR_LOG_TIME = true
|
|
1246
|
+
process.env.MCR_LOG_TIME = true;
|
|
1105
1247
|
```
|
|
1106
1248
|
|
|
1107
1249
|
## Integration with Any Testing Framework
|
|
@@ -1185,7 +1327,7 @@ mcr mocha ./test/**/*.js
|
|
|
1185
1327
|
```sh
|
|
1186
1328
|
cross-env NODE_OPTIONS="--import tsx" npx mcr tsx ./src/example.ts
|
|
1187
1329
|
cross-env NODE_OPTIONS="--import tsx" npx mcr mocha ./test/**/*.ts
|
|
1188
|
-
# Node.js
|
|
1330
|
+
# MCR 3.x: Node.js >= 22.15.0
|
|
1189
1331
|
mcr --import tsx tsx ./src/example.ts
|
|
1190
1332
|
mcr --import tsx mocha ./test/**/*.ts
|
|
1191
1333
|
```
|
|
@@ -1206,7 +1348,7 @@ mcr ava
|
|
|
1206
1348
|
- Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
|
|
1207
1349
|
```js
|
|
1208
1350
|
const coverageOptions = {
|
|
1209
|
-
outputDir:
|
|
1351
|
+
outputDir: './coverage-reports',
|
|
1210
1352
|
reports: [
|
|
1211
1353
|
['codecov']
|
|
1212
1354
|
]
|
|
@@ -1226,7 +1368,7 @@ const coverageOptions = {
|
|
|
1226
1368
|
- Using `lcov` report:
|
|
1227
1369
|
```js
|
|
1228
1370
|
const coverageOptions = {
|
|
1229
|
-
outputDir:
|
|
1371
|
+
outputDir: './coverage-reports',
|
|
1230
1372
|
lcov: true
|
|
1231
1373
|
};
|
|
1232
1374
|
```
|
|
@@ -1244,7 +1386,7 @@ const coverageOptions = {
|
|
|
1244
1386
|
- Using `lcov` report:
|
|
1245
1387
|
```js
|
|
1246
1388
|
const coverageOptions = {
|
|
1247
|
-
outputDir:
|
|
1389
|
+
outputDir: './coverage-reports',
|
|
1248
1390
|
lcov: true
|
|
1249
1391
|
};
|
|
1250
1392
|
```
|
|
@@ -1277,17 +1419,25 @@ const coverageOptions = {
|
|
|
1277
1419
|
```
|
|
1278
1420
|
|
|
1279
1421
|
## Contributing
|
|
1280
|
-
- Node.js 22
|
|
1422
|
+
- 推荐使用最新 Node.js 24 LTS 开发;包的最低运行版本为 22.15.0。开发工具可能要求高于最低运行版本的补丁版本。
|
|
1281
1423
|
- VSCode (extensions: eslint/stylelint/vue)
|
|
1282
1424
|
```sh
|
|
1283
1425
|
npm install
|
|
1284
1426
|
npx playwright install --with-deps
|
|
1285
1427
|
|
|
1286
1428
|
npm run build
|
|
1429
|
+
npm run lint
|
|
1287
1430
|
npm run test
|
|
1288
1431
|
|
|
1289
1432
|
npm run dev
|
|
1290
1433
|
```
|
|
1434
|
+
README 中的 JavaScript 示例通过 `npm run lint:readme` 和 `npm run lint` 使用项目的 ESLint 配置检查。仅针对省略上下文的片段关闭 `no-undef` 和 `no-unused-vars`,语法、代码风格及其他正确性规则保持启用。TypeScript 声明使用 `ts` 代码块,示意图使用 `text`。刻意保留非标准格式的演示可继续使用 `js` 高亮,并在区域前后分别添加独占一行的 `<!-- readme-lint-disable -->` 和 `<!-- readme-lint-enable -->` 隐藏注释;该区域不参与 lint 和自动修复。标记必须配对且不能嵌套,代码块内部出现的标记仅视为演示文本。
|
|
1435
|
+
|
|
1436
|
+
```sh
|
|
1437
|
+
npm run lint:readme
|
|
1438
|
+
npm run lint:readme -- --fix
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1291
1441
|
- Refreshing `eol=lf` for snapshot of test (Windows)
|
|
1292
1442
|
```sh
|
|
1293
1443
|
git add . -u
|
|
@@ -1298,4 +1448,7 @@ npm run eol
|
|
|
1298
1448
|
|
|
1299
1449
|
## Thanks
|
|
1300
1450
|
- [@bcoe](https://github.com/bcoe)
|
|
1301
|
-
- [@edumserrano](https://github.com/edumserrano)
|
|
1451
|
+
- [@edumserrano](https://github.com/edumserrano)
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+

|