@vitest-agent/ui 4.0.0 → 4.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/counts.js +4 -1
- package/dispatcher/cells/single-file-threshold.js +2 -3
- package/dispatcher/cells/single-project-threshold.js +3 -3
- package/dispatcher/cells/workspace-threshold.js +3 -3
- package/dispatcher/classify.js +6 -1
- package/dispatcher/helpers.js +52 -3
- package/index.d.ts +104 -1
- package/index.js +3 -3
- package/package.json +3 -3
- package/reducer.js +2 -1
- package/render-agent.js +2 -1
- package/synthesize.js +20 -6
package/counts.js
CHANGED
|
@@ -43,7 +43,10 @@ const formatTotalsLine = (input) => {
|
|
|
43
43
|
}),
|
|
44
44
|
Doc.counter(VitestAgentStatus, "failure", {
|
|
45
45
|
key: "unhandled",
|
|
46
|
-
label:
|
|
46
|
+
label: {
|
|
47
|
+
one: "unhandled error",
|
|
48
|
+
other: "unhandled errors"
|
|
49
|
+
},
|
|
47
50
|
n: unhandled
|
|
48
51
|
})
|
|
49
52
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
2
|
import { formatDisplayDuration } from "../../format-duration.js";
|
|
3
|
-
import {
|
|
3
|
+
import { formatCoverageSummaryLines, soleModulePath } from "../helpers.js";
|
|
4
4
|
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
5
5
|
import { Fmt } from "@effected/cli";
|
|
6
6
|
|
|
@@ -10,8 +10,7 @@ const renderAgent = (inputs) => {
|
|
|
10
10
|
if (modulePath === void 0) return "";
|
|
11
11
|
const { passCount, durationMs } = inputs.state.totals;
|
|
12
12
|
const lines = [`${modulePath}: ${Fmt.plural(passCount, "test")} passed (${formatDisplayDuration(durationMs)})`];
|
|
13
|
-
|
|
14
|
-
if (coverage !== null) lines.push(coverage);
|
|
13
|
+
lines.push(...formatCoverageSummaryLines(inputs.state));
|
|
15
14
|
return `${lines.join("\n")}\n${buildFooter(inputs)}`;
|
|
16
15
|
};
|
|
17
16
|
const renderSingleFileThreshold = {
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
|
-
import { formatBelowTargetTable,
|
|
2
|
+
import { formatBelowTargetTable, formatCoverageSummaryLines, formatTotals, formatTrendLine } from "../helpers.js";
|
|
3
3
|
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
4
|
|
|
5
5
|
//#region src/dispatcher/cells/single-project-threshold.ts
|
|
6
6
|
const BELOW_TARGET_LIMIT = 5;
|
|
7
7
|
const renderAgent = (inputs) => {
|
|
8
8
|
const sections = [[formatTotals(inputs.state)]];
|
|
9
|
-
const coverage =
|
|
9
|
+
const coverage = formatCoverageSummaryLines(inputs.state);
|
|
10
10
|
const trend = formatTrendLine(inputs.trend);
|
|
11
11
|
const summaryLines = [];
|
|
12
|
-
|
|
12
|
+
summaryLines.push(...coverage);
|
|
13
13
|
if (trend !== null) summaryLines.push(trend);
|
|
14
14
|
if (summaryLines.length > 0) sections.push(summaryLines);
|
|
15
15
|
const below = formatBelowTargetTable(inputs.belowTarget, BELOW_TARGET_LIMIT);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildFooter } from "../footer.js";
|
|
2
|
-
import { formatBelowTargetTable,
|
|
2
|
+
import { formatBelowTargetTable, formatCoverageSummaryLines, formatProjectsTable, formatTrendLine, formatWorkspaceTotal } from "../helpers.js";
|
|
3
3
|
import { renderAgentStringAsInk } from "../ink-helpers.js";
|
|
4
4
|
|
|
5
5
|
//#region src/dispatcher/cells/workspace-threshold.ts
|
|
@@ -9,8 +9,8 @@ const renderAgent = (inputs) => {
|
|
|
9
9
|
const projects = formatProjectsTable(inputs.projects);
|
|
10
10
|
if (projects.length > 0) sections.push([...projects]);
|
|
11
11
|
const summaryLines = [];
|
|
12
|
-
const coverage =
|
|
13
|
-
|
|
12
|
+
const coverage = formatCoverageSummaryLines(inputs.state);
|
|
13
|
+
summaryLines.push(...coverage);
|
|
14
14
|
const trend = formatTrendLine(inputs.trend);
|
|
15
15
|
if (trend !== null) summaryLines.push(trend);
|
|
16
16
|
if (summaryLines.length > 0) sections.push(summaryLines);
|
package/dispatcher/classify.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* UI rewrite spec §7 open question 1:
|
|
8
8
|
*
|
|
9
9
|
* 1. More than one project → `workspace`.
|
|
10
|
+
* 1b. `collectedModules` greater than one → `single-project` (the reduced
|
|
11
|
+
* `modules` map may hold only the failing modules of a larger run).
|
|
10
12
|
* 2. One module with exactly one test → `single-test`.
|
|
11
13
|
* 3. One module with more than one test → `single-file`.
|
|
12
14
|
* 4. Otherwise → `single-project`.
|
|
@@ -18,6 +20,7 @@
|
|
|
18
20
|
*/
|
|
19
21
|
const classifyRunShape = (state, projects) => {
|
|
20
22
|
if (projects.length > 1) return "workspace";
|
|
23
|
+
if (state.collectedModules !== void 0 && state.collectedModules > 1) return "single-project";
|
|
21
24
|
const moduleEntries = Object.values(state.modules);
|
|
22
25
|
if (moduleEntries.length === 1) {
|
|
23
26
|
const sole = moduleEntries[0];
|
|
@@ -50,7 +53,9 @@ const classifyOutcome = (state) => {
|
|
|
50
53
|
if (state.totals.failCount > 0) return "some-fail";
|
|
51
54
|
if (state.unhandledErrors.length > 0) return "some-fail";
|
|
52
55
|
if (state.totals.timeoutCount > 0) return "some-fail";
|
|
53
|
-
if (state.coverage !== null
|
|
56
|
+
if (state.coverage !== null) {
|
|
57
|
+
if (state.coverage.violations.length > 0 || (state.coverage.globShortfalls?.length ?? 0) > 0) return "threshold-violation";
|
|
58
|
+
}
|
|
54
59
|
return "all-pass";
|
|
55
60
|
};
|
|
56
61
|
|
package/dispatcher/helpers.js
CHANGED
|
@@ -5,6 +5,12 @@ import { Doc, Fmt, Render } from "@effected/cli";
|
|
|
5
5
|
|
|
6
6
|
//#region src/dispatcher/helpers.ts
|
|
7
7
|
/**
|
|
8
|
+
* Format a coverage percentage given on istanbul's 0–100 scale. One
|
|
9
|
+
* decimal place for non-integer values, none for integers, with a
|
|
10
|
+
* trailing percent sign (`Fmt.percent` with `scale: 100`).
|
|
11
|
+
*/
|
|
12
|
+
const formatPercent = (n) => Fmt.percent(n, { scale: 100 });
|
|
13
|
+
/**
|
|
8
14
|
* Format the `Tests:` header line —
|
|
9
15
|
* `<pass>/<total> passed[, <fail> failed][, <timeout> timed out][, <skip> skipped] (Xms)`.
|
|
10
16
|
* `total` folds in `timeoutCount` — a timed-out test is a real collected
|
|
@@ -70,18 +76,61 @@ const formatFailure = (f, width) => {
|
|
|
70
76
|
})).split("\n");
|
|
71
77
|
};
|
|
72
78
|
/**
|
|
79
|
+
* The `Glob aggregates below threshold:` block — one
|
|
80
|
+
* `- <pattern>: lines 60% < 90%, branches 55% < 80%` line per shortfall,
|
|
81
|
+
* listing only the metrics that miss their minimum. Returns an empty array
|
|
82
|
+
* when there are no shortfalls. Shared by the dispatcher cells and
|
|
83
|
+
* `renderAgent` so both agent surfaces use one line format.
|
|
84
|
+
*/
|
|
85
|
+
const formatGlobShortfallLines = (shortfalls) => {
|
|
86
|
+
if (shortfalls === void 0 || shortfalls.length === 0) return [];
|
|
87
|
+
const metricOrder = [
|
|
88
|
+
"lines",
|
|
89
|
+
"branches",
|
|
90
|
+
"functions",
|
|
91
|
+
"statements"
|
|
92
|
+
];
|
|
93
|
+
const lines = ["Glob aggregates below threshold:"];
|
|
94
|
+
for (const g of shortfalls) {
|
|
95
|
+
const parts = [];
|
|
96
|
+
for (const m of metricOrder) {
|
|
97
|
+
const min = g.thresholds[m];
|
|
98
|
+
const actual = g.summary[m];
|
|
99
|
+
if (min !== void 0 && actual < min) parts.push(`${m} ${formatPercent(actual)} < ${formatPercent(min)}`);
|
|
100
|
+
}
|
|
101
|
+
lines.push(`- ${g.pattern}: ${parts.join(", ")}`);
|
|
102
|
+
}
|
|
103
|
+
return lines;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
73
106
|
* One coverage judgment line — `Coverage: ✓ all metrics meet thresholds`
|
|
74
107
|
* for a clean run, `Coverage: ✗ <N> files below minimum thresholds (...)`
|
|
75
|
-
* for a violation
|
|
108
|
+
* for a per-file violation, `Coverage: ✗ <N> glob aggregates below
|
|
109
|
+
* threshold` when only glob aggregate shortfalls exist. Returns `null`
|
|
110
|
+
* when the run carries no coverage block.
|
|
76
111
|
*/
|
|
77
112
|
const formatCoverageJudgmentLine = (state) => {
|
|
78
113
|
const cov = state.coverage;
|
|
79
114
|
if (cov === null) return null;
|
|
80
|
-
|
|
115
|
+
const shortfallCount = cov.globShortfalls?.length ?? 0;
|
|
116
|
+
if (cov.violations.length === 0) {
|
|
117
|
+
if (shortfallCount > 0) return `Coverage: ${statusGlyph("failure")} ${Fmt.plural(shortfallCount, "glob aggregate")} below threshold`;
|
|
118
|
+
return `Coverage: ${statusGlyph("success")} all metrics meet thresholds`;
|
|
119
|
+
}
|
|
81
120
|
const metrics = cov.violations.map((v) => v.metric).join(", ");
|
|
82
121
|
const fileCount = countLowCoverageFiles(cov.gaps);
|
|
83
122
|
return `Coverage: ${statusGlyph("failure")} ${Fmt.plural(fileCount, "file")} below minimum thresholds (${metrics})`;
|
|
84
123
|
};
|
|
124
|
+
/**
|
|
125
|
+
* The coverage judgment line followed by the glob aggregate shortfall
|
|
126
|
+
* block, as the lines a threshold cell appends to its summary. Empty when
|
|
127
|
+
* the run carries no coverage block.
|
|
128
|
+
*/
|
|
129
|
+
const formatCoverageSummaryLines = (state) => {
|
|
130
|
+
const judgment = formatCoverageJudgmentLine(state);
|
|
131
|
+
if (judgment === null) return [];
|
|
132
|
+
return [judgment, ...formatGlobShortfallLines(state.coverage?.globShortfalls)];
|
|
133
|
+
};
|
|
85
134
|
const countLowCoverageFiles = (gaps) => {
|
|
86
135
|
const seen = /* @__PURE__ */ new Set();
|
|
87
136
|
for (const g of gaps) seen.add(g.file);
|
|
@@ -191,4 +240,4 @@ const pctCell = (n) => {
|
|
|
191
240
|
};
|
|
192
241
|
|
|
193
242
|
//#endregion
|
|
194
|
-
export { formatBelowTargetTable, formatCoverageJudgmentLine, formatDisplayDuration, formatFailure, formatProjectRow, formatProjectsTable, formatTestName, formatTotals, formatTrendLine, formatWorkspaceTotal, soleModulePath, soleTest };
|
|
243
|
+
export { formatBelowTargetTable, formatCoverageJudgmentLine, formatCoverageSummaryLines, formatDisplayDuration, formatFailure, formatGlobShortfallLines, formatPercent, formatProjectRow, formatProjectsTable, formatTestName, formatTotals, formatTrendLine, formatWorkspaceTotal, soleModulePath, soleTest };
|
package/index.d.ts
CHANGED
|
@@ -31,6 +31,35 @@ declare const FileCoverageReport: Schema.Struct<{
|
|
|
31
31
|
}>;
|
|
32
32
|
/** @public */
|
|
33
33
|
type FileCoverageReport = typeof FileCoverageReport.Type;
|
|
34
|
+
/**
|
|
35
|
+
* A threshold glob whose AGGREGATE coverage (summed covered/total across
|
|
36
|
+
* every file the glob matches, the same way Vitest evaluates a glob
|
|
37
|
+
* threshold) is below at least one of the glob's metric numbers.
|
|
38
|
+
*
|
|
39
|
+
* This is independent of the per-file lists: with an object `perFile` every
|
|
40
|
+
* matched file can pass while the glob as a whole still fails.
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
declare const GlobShortfall: Schema.Struct<{
|
|
44
|
+
/** The threshold glob pattern as configured. */
|
|
45
|
+
readonly pattern: Schema.String;
|
|
46
|
+
/** Aggregate coverage percentages over the files the glob matches. */
|
|
47
|
+
readonly summary: Schema.Struct<{
|
|
48
|
+
readonly statements: Schema.Number;
|
|
49
|
+
readonly branches: Schema.Number;
|
|
50
|
+
readonly functions: Schema.Number;
|
|
51
|
+
readonly lines: Schema.Number;
|
|
52
|
+
}>;
|
|
53
|
+
/** The glob's own metric numbers that were set (`perFile` excluded). */
|
|
54
|
+
readonly thresholds: Schema.Struct<{
|
|
55
|
+
readonly lines: Schema.optional<Schema.Number>;
|
|
56
|
+
readonly functions: Schema.optional<Schema.Number>;
|
|
57
|
+
readonly branches: Schema.optional<Schema.Number>;
|
|
58
|
+
readonly statements: Schema.optional<Schema.Number>;
|
|
59
|
+
}>;
|
|
60
|
+
}>;
|
|
61
|
+
/** @public */
|
|
62
|
+
type GlobShortfall = typeof GlobShortfall.Type;
|
|
34
63
|
//#endregion
|
|
35
64
|
//#region src/schemas/RenderState.d.ts
|
|
36
65
|
/**
|
|
@@ -153,6 +182,21 @@ export declare const CoverageRenderState: Schema.Struct<{
|
|
|
153
182
|
readonly scoped: Schema.optional<Schema.Boolean>;
|
|
154
183
|
readonly scopedFiles: Schema.optional<Schema.Number>;
|
|
155
184
|
readonly totalFiles: Schema.optional<Schema.Number>;
|
|
185
|
+
readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
186
|
+
readonly pattern: Schema.String;
|
|
187
|
+
readonly summary: Schema.Struct<{
|
|
188
|
+
readonly statements: Schema.Number;
|
|
189
|
+
readonly branches: Schema.Number;
|
|
190
|
+
readonly functions: Schema.Number;
|
|
191
|
+
readonly lines: Schema.Number;
|
|
192
|
+
}>;
|
|
193
|
+
readonly thresholds: Schema.Struct<{
|
|
194
|
+
readonly lines: Schema.optional<Schema.Number>;
|
|
195
|
+
readonly functions: Schema.optional<Schema.Number>;
|
|
196
|
+
readonly branches: Schema.optional<Schema.Number>;
|
|
197
|
+
readonly statements: Schema.optional<Schema.Number>;
|
|
198
|
+
}>;
|
|
199
|
+
}>>>;
|
|
156
200
|
}>;
|
|
157
201
|
/** @public */
|
|
158
202
|
export type CoverageRenderState = typeof CoverageRenderState.Type;
|
|
@@ -274,6 +318,21 @@ export declare const RenderState: Schema.Struct<{
|
|
|
274
318
|
readonly scoped: Schema.optional<Schema.Boolean>;
|
|
275
319
|
readonly scopedFiles: Schema.optional<Schema.Number>;
|
|
276
320
|
readonly totalFiles: Schema.optional<Schema.Number>;
|
|
321
|
+
readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
322
|
+
readonly pattern: Schema.String;
|
|
323
|
+
readonly summary: Schema.Struct<{
|
|
324
|
+
readonly statements: Schema.Number;
|
|
325
|
+
readonly branches: Schema.Number;
|
|
326
|
+
readonly functions: Schema.Number;
|
|
327
|
+
readonly lines: Schema.Number;
|
|
328
|
+
}>;
|
|
329
|
+
readonly thresholds: Schema.Struct<{
|
|
330
|
+
readonly lines: Schema.optional<Schema.Number>;
|
|
331
|
+
readonly functions: Schema.optional<Schema.Number>;
|
|
332
|
+
readonly branches: Schema.optional<Schema.Number>;
|
|
333
|
+
readonly statements: Schema.optional<Schema.Number>;
|
|
334
|
+
}>;
|
|
335
|
+
}>>>;
|
|
277
336
|
}>>;
|
|
278
337
|
readonly trend: Schema.NullOr<Schema.Struct<{
|
|
279
338
|
readonly direction: Schema.Literals<readonly ["improving", "regressing", "stable"]>;
|
|
@@ -606,6 +665,21 @@ declare const AgentReport: Schema.Struct<{
|
|
|
606
665
|
readonly uncoveredLines: Schema.String;
|
|
607
666
|
}>>>;
|
|
608
667
|
readonly belowTargetFiles: Schema.optional<Schema.$Array<Schema.String>>;
|
|
668
|
+
readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
669
|
+
readonly pattern: Schema.String;
|
|
670
|
+
readonly summary: Schema.Struct<{
|
|
671
|
+
readonly statements: Schema.Number;
|
|
672
|
+
readonly branches: Schema.Number;
|
|
673
|
+
readonly functions: Schema.Number;
|
|
674
|
+
readonly lines: Schema.Number;
|
|
675
|
+
}>;
|
|
676
|
+
readonly thresholds: Schema.Struct<{
|
|
677
|
+
readonly lines: Schema.optional<Schema.Number>;
|
|
678
|
+
readonly functions: Schema.optional<Schema.Number>;
|
|
679
|
+
readonly branches: Schema.optional<Schema.Number>;
|
|
680
|
+
readonly statements: Schema.optional<Schema.Number>;
|
|
681
|
+
}>;
|
|
682
|
+
}>>>;
|
|
609
683
|
}>>;
|
|
610
684
|
readonly tagCounts: Schema.optional<Schema.$Record<Schema.String, Schema.Struct<{
|
|
611
685
|
readonly passed: Schema.optional<Schema.Number>;
|
|
@@ -825,6 +899,21 @@ export declare const RunEvent: Schema.Union<readonly [Schema.TaggedStruct<"RunSt
|
|
|
825
899
|
readonly scoped: Schema.optional<Schema.Boolean>;
|
|
826
900
|
readonly scopedFiles: Schema.optional<Schema.Number>;
|
|
827
901
|
readonly totalFiles: Schema.optional<Schema.Number>;
|
|
902
|
+
readonly globShortfalls: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
903
|
+
readonly pattern: Schema.String;
|
|
904
|
+
readonly summary: Schema.Struct<{
|
|
905
|
+
readonly statements: Schema.Number;
|
|
906
|
+
readonly branches: Schema.Number;
|
|
907
|
+
readonly functions: Schema.Number;
|
|
908
|
+
readonly lines: Schema.Number;
|
|
909
|
+
}>;
|
|
910
|
+
readonly thresholds: Schema.Struct<{
|
|
911
|
+
readonly lines: Schema.optional<Schema.Number>;
|
|
912
|
+
readonly functions: Schema.optional<Schema.Number>;
|
|
913
|
+
readonly branches: Schema.optional<Schema.Number>;
|
|
914
|
+
readonly statements: Schema.optional<Schema.Number>;
|
|
915
|
+
}>;
|
|
916
|
+
}>>>;
|
|
828
917
|
}>, Schema.TaggedStruct<"ThresholdViolation", {
|
|
829
918
|
readonly metric: Schema.Literals<readonly ["lines", "branches", "functions", "statements"]>;
|
|
830
919
|
readonly expected: Schema.Number;
|
|
@@ -1074,6 +1163,8 @@ interface Cell {
|
|
|
1074
1163
|
* UI rewrite spec §7 open question 1:
|
|
1075
1164
|
*
|
|
1076
1165
|
* 1. More than one project → `workspace`.
|
|
1166
|
+
* 1b. `collectedModules` greater than one → `single-project` (the reduced
|
|
1167
|
+
* `modules` map may hold only the failing modules of a larger run).
|
|
1077
1168
|
* 2. One module with exactly one test → `single-test`.
|
|
1078
1169
|
* 3. One module with more than one test → `single-file`.
|
|
1079
1170
|
* 4. Otherwise → `single-project`.
|
|
@@ -1593,6 +1684,16 @@ export declare const TrendLine: FC<TrendLineProps>;
|
|
|
1593
1684
|
* @public
|
|
1594
1685
|
*/
|
|
1595
1686
|
export declare const SUITE_LOAD_FAILURE_LABEL = "test suite failed to load";
|
|
1687
|
+
/**
|
|
1688
|
+
* Synthetic test name for a suite-level failure in a module that loaded
|
|
1689
|
+
* and collected one or more test cases — a failing `beforeAll` or
|
|
1690
|
+
* `afterAll` hook, for example. The file imported fine, so
|
|
1691
|
+
* {@link SUITE_LOAD_FAILURE_LABEL} would mislead; this label is used
|
|
1692
|
+
* instead when the module has collected tests.
|
|
1693
|
+
*
|
|
1694
|
+
* @public
|
|
1695
|
+
*/
|
|
1696
|
+
export declare const SUITE_FAILURE_LABEL = "test suite failed";
|
|
1596
1697
|
/**
|
|
1597
1698
|
* Optional metadata threaded through the synthesized event stream.
|
|
1598
1699
|
*
|
|
@@ -1636,6 +1737,8 @@ interface SynthesizedCoverage {
|
|
|
1636
1737
|
readonly scopedFiles?: number;
|
|
1637
1738
|
/** Total test-file count for the project, when known. */
|
|
1638
1739
|
readonly totalFiles?: number;
|
|
1740
|
+
/** Threshold globs whose aggregate coverage is below their numbers (issue #391). */
|
|
1741
|
+
readonly globShortfalls?: ReadonlyArray<GlobShortfall>;
|
|
1639
1742
|
}
|
|
1640
1743
|
/**
|
|
1641
1744
|
* Project a fully-completed Vitest run into the canonical
|
|
@@ -1768,5 +1871,5 @@ export declare const statusInkStyle: (name: VitestAgentStatusName) => InkTextSty
|
|
|
1768
1871
|
*/
|
|
1769
1872
|
export declare const CURRENT_UI_VERSION: string;
|
|
1770
1873
|
//#endregion
|
|
1771
|
-
export { type AgentCellFn, type AgentReport, type Cell, type CellOptions, type CountColumnsProps, type CoverageBlockProps, FileCoverageReport as CoverageFile, type CoverageTotals, type DispatchInputs, type FailureSectionProps, type FailuresSectionProps, type InkCellFn, type InkTextStyle, type MetricThresholds, type ModuleHeaderProps, type ProjectRowProps, type ProjectSummary, type RenderAgentOptions, type RunEventByTag, type RunOutcome, type RunShape, type StatusIconKind, type StatusIconProps, type StreamAppProps, type SuggestedActionsProps, type SynthesizeFromAgentReportOptions, type SynthesizeOptions, type SynthesizedCoverage, type TagColumnsProps, type TestClassification, type TestRowProps, type TrendLineProps, type TrendSummary, type VitestAgentStatusName, type VitestModuleDiagnostic, type VitestModuleError, type VitestParsedStack, type VitestTestCase, type VitestTestDiagnostic, type VitestTestError, type VitestTestModule, type VitestTestResult, type VitestTestSuite };
|
|
1874
|
+
export { type AgentCellFn, type AgentReport, type Cell, type CellOptions, type CountColumnsProps, type CoverageBlockProps, FileCoverageReport as CoverageFile, type CoverageTotals, type DispatchInputs, type FailureSectionProps, type FailuresSectionProps, type GlobShortfall, type InkCellFn, type InkTextStyle, type MetricThresholds, type ModuleHeaderProps, type ProjectRowProps, type ProjectSummary, type RenderAgentOptions, type RunEventByTag, type RunOutcome, type RunShape, type StatusIconKind, type StatusIconProps, type StreamAppProps, type SuggestedActionsProps, type SynthesizeFromAgentReportOptions, type SynthesizeOptions, type SynthesizedCoverage, type TagColumnsProps, type TestClassification, type TestRowProps, type TrendLineProps, type TrendSummary, type VitestAgentStatusName, type VitestModuleDiagnostic, type VitestModuleError, type VitestParsedStack, type VitestTestCase, type VitestTestDiagnostic, type VitestTestError, type VitestTestModule, type VitestTestResult, type VitestTestSuite };
|
|
1772
1875
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { TrendLine } from "./render-ink/TrendLine.js";
|
|
|
19
19
|
import { StreamApp } from "./render-ink/StreamApp.js";
|
|
20
20
|
import { SuggestedActions } from "./render-ink/SuggestedActions.js";
|
|
21
21
|
import "./render-ink/index.js";
|
|
22
|
-
import { SUITE_LOAD_FAILURE_LABEL, synthesizeFromAgentReport, synthesizeRunEvents } from "./synthesize.js";
|
|
22
|
+
import { SUITE_FAILURE_LABEL, SUITE_LOAD_FAILURE_LABEL, synthesizeFromAgentReport, synthesizeRunEvents } from "./synthesize.js";
|
|
23
23
|
import { ActionSeverity, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, FailureRecord, ModuleRecord, RenderState, RenderTotals, RunEvent, SuggestedActionRecord, TestRecord, initialRenderState } from "@vitest-agent/sdk";
|
|
24
24
|
|
|
25
25
|
//#region src/index.ts
|
|
@@ -30,7 +30,7 @@ import { ActionSeverity, CoverageFile, CoverageGap, CoverageMetric, CoverageRend
|
|
|
30
30
|
*
|
|
31
31
|
* @public
|
|
32
32
|
*/
|
|
33
|
-
const CURRENT_UI_VERSION = "4.
|
|
33
|
+
const CURRENT_UI_VERSION = "4.1.0";
|
|
34
34
|
|
|
35
35
|
//#endregion
|
|
36
|
-
export { ActionSeverity, CURRENT_UI_VERSION, CountColumns, CoverageBlock, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, DURATION_CELL_WIDTH, FailureRecord, FailureSection, FailuresSection, ModuleHeader, ModuleRecord, ProjectRow, RenderState, RenderTotals, RunEvent, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_LOAD_FAILURE_LABEL, StatusIcon, StreamApp, SuggestedActionRecord, SuggestedActions, TagColumns, TestRecord, TestRow, TrendLine, VitestAgentStatus, VitestAgentTokens, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, formatDisplayDuration, initialRenderState, inkStyle, reduceRenderState, reduceRenderStateAll, renderAgent, spinnerFrame, statusGlyph, statusInkStyle, synthesizeFromAgentReport, synthesizeRunEvents, tagUnion };
|
|
36
|
+
export { ActionSeverity, CURRENT_UI_VERSION, CountColumns, CoverageBlock, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, DURATION_CELL_WIDTH, FailureRecord, FailureSection, FailuresSection, ModuleHeader, ModuleRecord, ProjectRow, RenderState, RenderTotals, RunEvent, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_FAILURE_LABEL, SUITE_LOAD_FAILURE_LABEL, StatusIcon, StreamApp, SuggestedActionRecord, SuggestedActions, TagColumns, TestRecord, TestRow, TrendLine, VitestAgentStatus, VitestAgentTokens, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, formatDisplayDuration, initialRenderState, inkStyle, reduceRenderState, reduceRenderStateAll, renderAgent, spinnerFrame, statusGlyph, statusInkStyle, synthesizeFromAgentReport, synthesizeRunEvents, tagUnion };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/ui",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared event-sourced renderer for vitest-agent. React Ink view for humans, plain-string view for agents, both fed by the same reducer over a RunEvent stream.",
|
|
6
6
|
"keywords": [
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@vitest-agent/sdk": "6.
|
|
42
|
+
"@vitest-agent/sdk": "6.1.0",
|
|
43
43
|
"effect": "^4.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@effected/cli": "^0.
|
|
46
|
+
"@effected/cli": "^0.12.0",
|
|
47
47
|
"@effected/env": "^0.1.0",
|
|
48
48
|
"@effected/glob": "^0.10.0",
|
|
49
49
|
"@effected/walker": "^0.15.0",
|
package/reducer.js
CHANGED
|
@@ -178,7 +178,8 @@ const reduceRenderState = (state, event) => {
|
|
|
178
178
|
violations: state.coverage?.violations ?? [],
|
|
179
179
|
...e.scoped !== void 0 ? { scoped: e.scoped } : {},
|
|
180
180
|
...e.scopedFiles !== void 0 ? { scopedFiles: e.scopedFiles } : {},
|
|
181
|
-
...e.totalFiles !== void 0 ? { totalFiles: e.totalFiles } : {}
|
|
181
|
+
...e.totalFiles !== void 0 ? { totalFiles: e.totalFiles } : {},
|
|
182
|
+
...e.globShortfalls !== void 0 ? { globShortfalls: e.globShortfalls } : {}
|
|
182
183
|
}
|
|
183
184
|
}),
|
|
184
185
|
ThresholdViolation: (e) => {
|
package/render-agent.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatTotalsLine } from "./counts.js";
|
|
2
|
-
import { formatFailure as formatFailure$1 } from "./dispatcher/helpers.js";
|
|
2
|
+
import { formatFailure as formatFailure$1, formatGlobShortfallLines } from "./dispatcher/helpers.js";
|
|
3
3
|
import { Fmt } from "@effected/cli";
|
|
4
4
|
|
|
5
5
|
//#region src/render-agent.ts
|
|
@@ -91,6 +91,7 @@ const formatCoverageSection = (state, maxGaps) => {
|
|
|
91
91
|
lines.push("Violations:");
|
|
92
92
|
for (const v of cov.violations) lines.push(`- ${v.metric}: ${formatPercent(v.actual)} < ${formatPercent(v.expected)}`);
|
|
93
93
|
}
|
|
94
|
+
lines.push(...formatGlobShortfallLines(cov.globShortfalls));
|
|
94
95
|
if (maxGaps > 0 && cov.gaps.length > 0) {
|
|
95
96
|
const topN = [...cov.gaps].sort((a, b) => b.missing.lines - a.missing.lines).slice(0, maxGaps);
|
|
96
97
|
lines.push("Gaps:");
|
package/synthesize.js
CHANGED
|
@@ -10,6 +10,16 @@ import { isTimeoutError } from "@vitest-agent/sdk";
|
|
|
10
10
|
* @public
|
|
11
11
|
*/
|
|
12
12
|
const SUITE_LOAD_FAILURE_LABEL = "test suite failed to load";
|
|
13
|
+
/**
|
|
14
|
+
* Synthetic test name for a suite-level failure in a module that loaded
|
|
15
|
+
* and collected one or more test cases — a failing `beforeAll` or
|
|
16
|
+
* `afterAll` hook, for example. The file imported fine, so
|
|
17
|
+
* {@link SUITE_LOAD_FAILURE_LABEL} would mislead; this label is used
|
|
18
|
+
* instead when the module has collected tests.
|
|
19
|
+
*
|
|
20
|
+
* @public
|
|
21
|
+
*/
|
|
22
|
+
const SUITE_FAILURE_LABEL = "test suite failed";
|
|
13
23
|
const ISO_ZERO = "1970-01-01T00:00:00.000Z";
|
|
14
24
|
const normalizeTestState = (state) => {
|
|
15
25
|
if (state === "passed") return "passed";
|
|
@@ -150,7 +160,8 @@ const synthesizeRunEvents = (modules, options = {}) => {
|
|
|
150
160
|
gaps: cov.gaps,
|
|
151
161
|
...cov.scoped !== void 0 ? { scoped: cov.scoped } : {},
|
|
152
162
|
...cov.scopedFiles !== void 0 ? { scopedFiles: cov.scopedFiles } : {},
|
|
153
|
-
...cov.totalFiles !== void 0 ? { totalFiles: cov.totalFiles } : {}
|
|
163
|
+
...cov.totalFiles !== void 0 ? { totalFiles: cov.totalFiles } : {},
|
|
164
|
+
...cov.globShortfalls !== void 0 ? { globShortfalls: cov.globShortfalls } : {}
|
|
154
165
|
});
|
|
155
166
|
if (cov.violations !== void 0) for (const v of cov.violations) events.push({
|
|
156
167
|
_tag: "ThresholdViolation",
|
|
@@ -218,7 +229,8 @@ const coverageReportToBlock = (report) => {
|
|
|
218
229
|
violations,
|
|
219
230
|
...cov.scoped ? { scoped: cov.scoped } : {},
|
|
220
231
|
...cov.scopedFiles !== void 0 ? { scopedFiles: cov.scopedFiles.length } : {},
|
|
221
|
-
...cov.totalFiles !== void 0 ? { totalFiles: cov.totalFiles } : {}
|
|
232
|
+
...cov.totalFiles !== void 0 ? { totalFiles: cov.totalFiles } : {},
|
|
233
|
+
...cov.globShortfalls !== void 0 ? { globShortfalls: cov.globShortfalls } : {}
|
|
222
234
|
};
|
|
223
235
|
};
|
|
224
236
|
/**
|
|
@@ -300,16 +312,17 @@ const synthesizeFromAgentReport = (report, options = {}) => {
|
|
|
300
312
|
fail = 1;
|
|
301
313
|
suiteFailureCount++;
|
|
302
314
|
const moduleError = mod.errors?.[0];
|
|
315
|
+
const suiteLabel = mod.tests.length > 0 ? SUITE_FAILURE_LABEL : SUITE_LOAD_FAILURE_LABEL;
|
|
303
316
|
events.push({
|
|
304
317
|
_tag: "TestStarted",
|
|
305
318
|
modulePath: mod.file,
|
|
306
|
-
testName:
|
|
319
|
+
testName: suiteLabel,
|
|
307
320
|
suitePath: []
|
|
308
321
|
});
|
|
309
322
|
events.push({
|
|
310
323
|
_tag: "TestFinished",
|
|
311
324
|
modulePath: mod.file,
|
|
312
|
-
testName:
|
|
325
|
+
testName: suiteLabel,
|
|
313
326
|
suitePath: [],
|
|
314
327
|
status: "failed",
|
|
315
328
|
durationMs: 0,
|
|
@@ -340,7 +353,8 @@ const synthesizeFromAgentReport = (report, options = {}) => {
|
|
|
340
353
|
gaps: coverage.gaps,
|
|
341
354
|
...coverage.scoped !== void 0 ? { scoped: coverage.scoped } : {},
|
|
342
355
|
...coverage.scopedFiles !== void 0 ? { scopedFiles: coverage.scopedFiles } : {},
|
|
343
|
-
...coverage.totalFiles !== void 0 ? { totalFiles: coverage.totalFiles } : {}
|
|
356
|
+
...coverage.totalFiles !== void 0 ? { totalFiles: coverage.totalFiles } : {},
|
|
357
|
+
...coverage.globShortfalls !== void 0 ? { globShortfalls: coverage.globShortfalls } : {}
|
|
344
358
|
});
|
|
345
359
|
if (coverage.violations !== void 0) for (const v of coverage.violations) events.push({
|
|
346
360
|
_tag: "ThresholdViolation",
|
|
@@ -378,4 +392,4 @@ const synthesizeFromAgentReport = (report, options = {}) => {
|
|
|
378
392
|
};
|
|
379
393
|
|
|
380
394
|
//#endregion
|
|
381
|
-
export { SUITE_LOAD_FAILURE_LABEL, synthesizeFromAgentReport, synthesizeRunEvents };
|
|
395
|
+
export { SUITE_FAILURE_LABEL, SUITE_LOAD_FAILURE_LABEL, synthesizeFromAgentReport, synthesizeRunEvents };
|