@vitest-agent/plugin 5.2.2 → 5.3.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/layers/CoverageAnalyzerLive.js +108 -1
- package/package.json +6 -6
- package/reporter.js +2 -1
- package/version.js +1 -1
|
@@ -17,6 +17,59 @@ function isBelowMetricThresholds(stats, thresholds) {
|
|
|
17
17
|
if (thresholds.statements !== void 0 && stats.statements < thresholds.statements) return true;
|
|
18
18
|
return false;
|
|
19
19
|
}
|
|
20
|
+
const METRICS = [
|
|
21
|
+
"statements",
|
|
22
|
+
"branches",
|
|
23
|
+
"functions",
|
|
24
|
+
"lines"
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Istanbul's own percentage: `covered / total` floored to two decimals, and
|
|
28
|
+
* 100 for an empty (`total === 0`) metric. Mirrors `istanbul-lib-coverage`'s
|
|
29
|
+
* `percent()` so an aggregate computed here equals what Vitest compares.
|
|
30
|
+
*/
|
|
31
|
+
function istanbulPercent(covered, total) {
|
|
32
|
+
return total > 0 ? Math.floor(1e4 * covered / total) / 100 : 100;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Aggregate a glob's matched files the way Vitest builds a glob coverage map:
|
|
36
|
+
* sum `covered` and `total` per metric across the files' summaries, then take
|
|
37
|
+
* the percentage of the sums (NOT a mean of per-file percentages). Returns
|
|
38
|
+
* `undefined` when any file's summary lacks counts, since no honest aggregate
|
|
39
|
+
* exists then.
|
|
40
|
+
*/
|
|
41
|
+
function aggregateSummaries(summaries) {
|
|
42
|
+
const sums = {
|
|
43
|
+
statements: {
|
|
44
|
+
covered: 0,
|
|
45
|
+
total: 0
|
|
46
|
+
},
|
|
47
|
+
branches: {
|
|
48
|
+
covered: 0,
|
|
49
|
+
total: 0
|
|
50
|
+
},
|
|
51
|
+
functions: {
|
|
52
|
+
covered: 0,
|
|
53
|
+
total: 0
|
|
54
|
+
},
|
|
55
|
+
lines: {
|
|
56
|
+
covered: 0,
|
|
57
|
+
total: 0
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
for (const s of summaries) for (const m of METRICS) {
|
|
61
|
+
const { covered, total } = s[m];
|
|
62
|
+
if (typeof covered !== "number" || typeof total !== "number") return void 0;
|
|
63
|
+
sums[m].covered += covered;
|
|
64
|
+
sums[m].total += total;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
statements: istanbulPercent(sums.statements.covered, sums.statements.total),
|
|
68
|
+
branches: istanbulPercent(sums.branches.covered, sums.branches.total),
|
|
69
|
+
functions: istanbulPercent(sums.functions.covered, sums.functions.total),
|
|
70
|
+
lines: istanbulPercent(sums.lines.covered, sums.lines.total)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
20
73
|
/**
|
|
21
74
|
* Runtime duck-type check for istanbul CoverageMap.
|
|
22
75
|
*/
|
|
@@ -86,6 +139,53 @@ function resolveEffectivePerFileThresholds(filePath, resolved) {
|
|
|
86
139
|
return setting;
|
|
87
140
|
}
|
|
88
141
|
/**
|
|
142
|
+
* Find the threshold globs whose aggregate coverage is below their own metric
|
|
143
|
+
* numbers (issue #391), matching Vitest 5: a glob's coverage map holds every
|
|
144
|
+
* file in the run that matches it (a file may belong to several globs), the
|
|
145
|
+
* aggregate is `sum(covered) / sum(total)` per metric, and it is enforced in
|
|
146
|
+
* addition to any object `perFile`. A glob with `perFile: true` is checked per
|
|
147
|
+
* file only, so it has no aggregate to fail.
|
|
148
|
+
*
|
|
149
|
+
* Choices (document before changing):
|
|
150
|
+
* - Bare-zero files COUNT toward the aggregate, regardless of
|
|
151
|
+
* `includeBareZero`. That option only decides whether such files are listed
|
|
152
|
+
* per file; Vitest's aggregate includes them, and dropping them would hide
|
|
153
|
+
* exactly the shortfall they cause.
|
|
154
|
+
* - Scoped runs never produce shortfalls (the caller skips this): a partial
|
|
155
|
+
* coverage map says nothing about a glob's full aggregate, mirroring how
|
|
156
|
+
* scoped runs never flag threshold violations.
|
|
157
|
+
* - Only non-negative numbers are compared (minimum percentages). Vitest's
|
|
158
|
+
* negative "max uncovered count" form is not evaluated here.
|
|
159
|
+
* - A glob that matches no file, or whose matched summaries lack
|
|
160
|
+
* covered/total counts, yields nothing.
|
|
161
|
+
*/
|
|
162
|
+
function computeGlobShortfalls(files, resolved) {
|
|
163
|
+
const shortfalls = [];
|
|
164
|
+
for (const [pattern, metrics] of resolved.patterns ?? []) {
|
|
165
|
+
if (metrics.perFile === true) continue;
|
|
166
|
+
const thresholds = {
|
|
167
|
+
...metrics.lines !== void 0 ? { lines: metrics.lines } : {},
|
|
168
|
+
...metrics.functions !== void 0 ? { functions: metrics.functions } : {},
|
|
169
|
+
...metrics.branches !== void 0 ? { branches: metrics.branches } : {},
|
|
170
|
+
...metrics.statements !== void 0 ? { statements: metrics.statements } : {}
|
|
171
|
+
};
|
|
172
|
+
if (METRICS.every((m) => thresholds[m] === void 0)) continue;
|
|
173
|
+
const matched = files.filter((f) => matchGlob(f.matchPath, pattern));
|
|
174
|
+
if (matched.length === 0) continue;
|
|
175
|
+
const aggregate = aggregateSummaries(matched.map((f) => f.summary));
|
|
176
|
+
if (aggregate === void 0) continue;
|
|
177
|
+
if (METRICS.some((m) => {
|
|
178
|
+
const min = thresholds[m];
|
|
179
|
+
return min !== void 0 && min >= 0 && aggregate[m] < min;
|
|
180
|
+
})) shortfalls.push({
|
|
181
|
+
pattern,
|
|
182
|
+
summary: aggregate,
|
|
183
|
+
thresholds
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return shortfalls;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
89
189
|
* Internal coverage processing logic. Shared by both `process` and `processScoped`.
|
|
90
190
|
*
|
|
91
191
|
* @param coverageMap - The value received by `onCoverage`; duck-typed at runtime
|
|
@@ -110,10 +210,15 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
110
210
|
const belowTarget = [];
|
|
111
211
|
const { root } = options;
|
|
112
212
|
const matchKey = (filePath) => toPosixPath(root === void 0 ? filePath : relative(root, filePath));
|
|
213
|
+
const mapSummaries = [];
|
|
113
214
|
for (const filePath of coverageMap.files()) {
|
|
114
215
|
const matchPath = matchKey(filePath);
|
|
115
216
|
const fileCoverage = coverageMap.fileCoverageFor(filePath);
|
|
116
217
|
const fileSummary = fileCoverage.toSummary();
|
|
218
|
+
mapSummaries.push({
|
|
219
|
+
matchPath,
|
|
220
|
+
summary: fileSummary
|
|
221
|
+
});
|
|
117
222
|
const fileStats = {
|
|
118
223
|
statements: fileSummary.statements.pct,
|
|
119
224
|
branches: fileSummary.branches.pct,
|
|
@@ -134,7 +239,7 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
134
239
|
continue;
|
|
135
240
|
}
|
|
136
241
|
if (options.targets) {
|
|
137
|
-
if (isBelowMetricThresholds(fileStats, resolveEffectiveThresholds(matchPath, options.targets))) {
|
|
242
|
+
if (isBelowMetricThresholds(fileStats, resolveEffectivePerFileThresholds(matchPath, options.targets) ?? resolveEffectiveThresholds(matchPath, options.targets))) {
|
|
138
243
|
const uncoveredLines = compressLines(fileCoverage.getUncoveredLines());
|
|
139
244
|
belowTarget.push({
|
|
140
245
|
file: filePath,
|
|
@@ -144,6 +249,7 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
144
249
|
}
|
|
145
250
|
}
|
|
146
251
|
}
|
|
252
|
+
const globShortfalls = scoped ? [] : computeGlobShortfalls(mapSummaries, options.thresholds);
|
|
147
253
|
lowCoverage.sort((a, b) => a.summary.lines - b.summary.lines);
|
|
148
254
|
belowTarget.sort((a, b) => a.summary.lines - b.summary.lines);
|
|
149
255
|
return {
|
|
@@ -165,6 +271,7 @@ function processCoverageInternal(coverageMap, options, testedFiles) {
|
|
|
165
271
|
...scoped && options.totalFiles !== void 0 ? { totalFiles: options.totalFiles } : {},
|
|
166
272
|
lowCoverage,
|
|
167
273
|
lowCoverageFiles: lowCoverage.map((f) => f.file),
|
|
274
|
+
...globShortfalls.length > 0 ? { globShortfalls } : {},
|
|
168
275
|
...options.targets ? {
|
|
169
276
|
belowTarget,
|
|
170
277
|
belowTargetFiles: belowTarget.map((f) => f.file)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/plugin",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vitest plugin for the vitest-agent ecosystem: owns persistence, classification, baselines, trends, and dispatches rendering to a configurable reporter.",
|
|
6
6
|
"keywords": [
|
|
@@ -49,11 +49,11 @@
|
|
|
49
49
|
"@effected/glob": "^0.10.0",
|
|
50
50
|
"@effected/walker": "^0.15.0",
|
|
51
51
|
"@effected/workspaces": "^0.31.0",
|
|
52
|
-
"@vitest-agent/cli": "4.0.
|
|
53
|
-
"@vitest-agent/engine": "0.6.
|
|
54
|
-
"@vitest-agent/mcp": "5.1.
|
|
55
|
-
"@vitest-agent/reporter": "4.1.
|
|
56
|
-
"@vitest-agent/sdk": "6.
|
|
52
|
+
"@vitest-agent/cli": "4.0.3",
|
|
53
|
+
"@vitest-agent/engine": "0.6.1",
|
|
54
|
+
"@vitest-agent/mcp": "5.1.9",
|
|
55
|
+
"@vitest-agent/reporter": "4.1.1",
|
|
56
|
+
"@vitest-agent/sdk": "6.1.0",
|
|
57
57
|
"effect": "^4.0.0",
|
|
58
58
|
"magic-string": "^1.4.2"
|
|
59
59
|
},
|
package/reporter.js
CHANGED
|
@@ -1287,7 +1287,8 @@ var AgentReporter = class {
|
|
|
1287
1287
|
})),
|
|
1288
1288
|
...coverageReport.scoped ? { scoped: coverageReport.scoped } : {},
|
|
1289
1289
|
...coverageReport.scopedFiles !== void 0 ? { scopedFiles: coverageReport.scopedFiles.length } : {},
|
|
1290
|
-
...coverageReport.totalFiles !== void 0 ? { totalFiles: coverageReport.totalFiles } : {}
|
|
1290
|
+
...coverageReport.totalFiles !== void 0 ? { totalFiles: coverageReport.totalFiles } : {},
|
|
1291
|
+
...coverageReport.globShortfalls !== void 0 ? { globShortfalls: coverageReport.globShortfalls } : {}
|
|
1291
1292
|
});
|
|
1292
1293
|
if (!coverageReport.scoped) for (const metric of [
|
|
1293
1294
|
"lines",
|