@vitest-agent/ui 2.5.3 → 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/counts.js +61 -0
- package/dispatcher/cells/single-file-pass.js +2 -1
- package/dispatcher/cells/single-file-threshold.js +2 -1
- package/dispatcher/cells/single-project-pass.js +2 -1
- package/dispatcher/cells/single-test-fail.js +2 -1
- package/dispatcher/cells/single-test-pass.js +2 -1
- package/dispatcher/helpers.js +38 -49
- package/dispatcher/ink-helpers.js +32 -56
- package/format-duration.js +10 -10
- package/index.d.ts +97 -37
- package/index.js +4 -3
- package/package.json +7 -3
- package/render-agent.js +23 -29
- package/render-ink/CountColumns.js +20 -19
- package/render-ink/CoverageBlock.js +13 -7
- package/render-ink/FailureSection.js +12 -5
- package/render-ink/FailuresSection.js +16 -3
- package/render-ink/ProjectRow.js +2 -1
- package/render-ink/StatusIcon.js +24 -25
- package/render-ink/StreamApp.js +76 -55
- package/render-ink/SuggestedActions.js +7 -5
- package/render-ink/TagColumns.js +2 -1
- package/render-ink/TrendLine.js +9 -9
- package/render-ink/index.js +2 -2
- package/render-ink/spinner.js +14 -43
- package/theme.js +82 -0
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { classifyOutcome, classifyRunShape } from "./dispatcher/classify.js";
|
|
2
2
|
import { buildFooter, dominantClassification } from "./dispatcher/footer.js";
|
|
3
|
+
import { VitestAgentStatus, VitestAgentTokens, inkStyle, statusGlyph, statusInkStyle } from "./theme.js";
|
|
3
4
|
import { formatDisplayDuration } from "./format-duration.js";
|
|
4
5
|
import { dispatch, dispatchInk, dispatcherTable } from "./dispatcher/dispatch.js";
|
|
5
6
|
import { RunEventChannel, RunEventChannelLive } from "./pubsub/Channel.js";
|
|
@@ -16,7 +17,7 @@ import { StatusIcon } from "./render-ink/StatusIcon.js";
|
|
|
16
17
|
import { ModuleHeader } from "./render-ink/ModuleHeader.js";
|
|
17
18
|
import { TagColumns, tagUnion } from "./render-ink/TagColumns.js";
|
|
18
19
|
import { ProjectRow } from "./render-ink/ProjectRow.js";
|
|
19
|
-
import { SPINNER_FRAMES, SPINNER_FRAME_MS, spinnerFrame
|
|
20
|
+
import { SPINNER_FRAMES, SPINNER_FRAME_MS, spinnerFrame } from "./render-ink/spinner.js";
|
|
20
21
|
import { TestRow } from "./render-ink/TestRow.js";
|
|
21
22
|
import { TrendLine } from "./render-ink/TrendLine.js";
|
|
22
23
|
import { StreamApp } from "./render-ink/StreamApp.js";
|
|
@@ -33,7 +34,7 @@ import { ActionSeverity, CoverageFile, CoverageGap, CoverageMetric, CoverageRend
|
|
|
33
34
|
*
|
|
34
35
|
* @public
|
|
35
36
|
*/
|
|
36
|
-
const CURRENT_UI_VERSION = "
|
|
37
|
+
const CURRENT_UI_VERSION = "3.0.0";
|
|
37
38
|
|
|
38
39
|
//#endregion
|
|
39
|
-
export { ActionSeverity, CURRENT_UI_VERSION, CountColumns, CoverageBlock, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, DURATION_CELL_WIDTH, FailureRecord, FailureSection, FailuresSection, ModuleHeader, ModuleRecord, ProjectRow, RenderState, RenderTotals, RunEvent, RunEventChannel, RunEventChannelLive, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_LOAD_FAILURE_LABEL, StatusIcon, StreamApp, SuggestedActionRecord, SuggestedActions, TagColumns, TestRecord, TestRow, TrendLine, accumulateUntilFinished, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, forEachRenderState, formatDisplayDuration, initialRenderState, publish, publishAll, reduceRenderState, reduceRenderStateAll, renderAgent, renderStateStream, spinnerFrame,
|
|
40
|
+
export { ActionSeverity, CURRENT_UI_VERSION, CountColumns, CoverageBlock, CoverageFile, CoverageGap, CoverageMetric, CoverageRenderState, DURATION_CELL_WIDTH, FailureRecord, FailureSection, FailuresSection, ModuleHeader, ModuleRecord, ProjectRow, RenderState, RenderTotals, RunEvent, RunEventChannel, RunEventChannelLive, SPINNER_FRAMES, SPINNER_FRAME_MS, SUITE_LOAD_FAILURE_LABEL, StatusIcon, StreamApp, SuggestedActionRecord, SuggestedActions, TagColumns, TestRecord, TestRow, TrendLine, VitestAgentStatus, VitestAgentTokens, accumulateUntilFinished, buildFooter, classifyOutcome, classifyRunShape, dispatch, dispatchInk, dispatcherTable, dominantClassification, forEachRenderState, formatDisplayDuration, initialRenderState, inkStyle, publish, publishAll, reduceRenderState, reduceRenderStateAll, renderAgent, renderStateStream, spinnerFrame, statusGlyph, statusInkStyle, subscribeRaw, synthesizeFromAgentReport, synthesizeRunEvents, tagUnion };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitest-agent/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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,10 +39,14 @@
|
|
|
39
39
|
"./package.json": "./package.json"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@vitest-agent/sdk": "5.
|
|
43
|
-
"effect": "4.0.0
|
|
42
|
+
"@vitest-agent/sdk": "5.2.0",
|
|
43
|
+
"effect": "^4.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
+
"@effected/cli": "^0.11.0",
|
|
47
|
+
"@effected/env": "^0.1.0",
|
|
48
|
+
"@effected/glob": "^0.10.0",
|
|
49
|
+
"@effected/walker": "^0.15.0",
|
|
46
50
|
"ink": "^7.1.0",
|
|
47
51
|
"react": "^19.3.0"
|
|
48
52
|
},
|
package/render-agent.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatTotalsLine } from "./counts.js";
|
|
2
|
+
import { Fmt } from "@effected/cli";
|
|
2
3
|
|
|
3
4
|
//#region src/render-agent.ts
|
|
5
|
+
/**
|
|
6
|
+
* Agent-mode renderer: produces a token-efficient final-frame string
|
|
7
|
+
* from a fully-reduced `RenderState`.
|
|
8
|
+
*
|
|
9
|
+
* Output is intentionally separate from the Ink renderer — agents
|
|
10
|
+
* benefit from a different layout than humans (no ANSI, no live
|
|
11
|
+
* progress, no decoration overhead). The output is stable for stable
|
|
12
|
+
* inputs: no timestamps, no derived "time ago" phrasing, no randomized
|
|
13
|
+
* ordering.
|
|
14
|
+
*/
|
|
4
15
|
const DEFAULT_WIDTH = 80;
|
|
5
16
|
const DEFAULT_MAX_GAPS = 3;
|
|
6
17
|
const severityLabel = {
|
|
@@ -8,18 +19,11 @@ const severityLabel = {
|
|
|
8
19
|
warn: "warn",
|
|
9
20
|
blocker: "blocker"
|
|
10
21
|
};
|
|
11
|
-
const formatHeader = (state) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (skipCount > 0) parts.push(`${skipCount} skipped`);
|
|
17
|
-
if (state.unhandledErrors.length > 0) {
|
|
18
|
-
const noun = state.unhandledErrors.length === 1 ? "unhandled error" : "unhandled errors";
|
|
19
|
-
parts.push(`${state.unhandledErrors.length} ${noun}`);
|
|
20
|
-
}
|
|
21
|
-
return `Tests: ${parts.join(", ")} (${formatDisplayDuration(durationMs)})`;
|
|
22
|
-
};
|
|
22
|
+
const formatHeader = (state) => formatTotalsLine({
|
|
23
|
+
label: "Tests",
|
|
24
|
+
...state.totals,
|
|
25
|
+
unhandledErrors: state.unhandledErrors.length
|
|
26
|
+
});
|
|
23
27
|
const formatModulesSection = (state) => {
|
|
24
28
|
const modules = state.moduleOrder.map((path) => state.modules[path]).filter((m) => m !== void 0);
|
|
25
29
|
const { passCount, failCount, skipCount, timeoutCount } = state.totals;
|
|
@@ -27,9 +31,9 @@ const formatModulesSection = (state) => {
|
|
|
27
31
|
const collected = state.collectedModules ?? modules.length;
|
|
28
32
|
if (modules.length === 0) {
|
|
29
33
|
if (collected === 0) return null;
|
|
30
|
-
return `${collected
|
|
34
|
+
return `${Fmt.plural(collected, "module")} all-passed.`;
|
|
31
35
|
}
|
|
32
|
-
if (modules.filter((m) => m.failCount > 0 || m.skipCount > 0).length === 0) return `${collected
|
|
36
|
+
if (modules.filter((m) => m.failCount > 0 || m.skipCount > 0).length === 0) return `${Fmt.plural(collected, "module")} all-passed.`;
|
|
33
37
|
const lines = ["Modules:"];
|
|
34
38
|
for (const m of modules) {
|
|
35
39
|
const parts = [];
|
|
@@ -41,12 +45,7 @@ const formatModulesSection = (state) => {
|
|
|
41
45
|
}
|
|
42
46
|
return lines.join("\n");
|
|
43
47
|
};
|
|
44
|
-
const truncate = (line, max) =>
|
|
45
|
-
if (line.length <= max) return line;
|
|
46
|
-
const slice = max - 1;
|
|
47
|
-
if (slice <= 0) return "…";
|
|
48
|
-
return `${line.slice(0, slice)}…`;
|
|
49
|
-
};
|
|
48
|
+
const truncate = (line, max) => Fmt.truncate(line, max);
|
|
50
49
|
const formatFailure = (f, width, includeStack) => {
|
|
51
50
|
const suite = f.suitePath.length > 0 ? `${f.suitePath.join(" > ")} > ` : "";
|
|
52
51
|
const classification = f.classification !== null ? ` [${f.classification}]` : "";
|
|
@@ -77,10 +76,8 @@ const formatUnhandledErrorsSection = (state, width, includeStack) => {
|
|
|
77
76
|
for (const err of state.unhandledErrors) lines.push(formatUnhandledError(err, width, includeStack));
|
|
78
77
|
return lines.join("\n");
|
|
79
78
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return Number.isInteger(rounded) ? `${rounded}%` : `${rounded}%`;
|
|
83
|
-
};
|
|
79
|
+
/** Coverage metrics arrive on istanbul's 0–100 scale. */
|
|
80
|
+
const formatPercent = (n) => Fmt.percent(n, { scale: 100 });
|
|
84
81
|
const formatCoverageSection = (state, maxGaps) => {
|
|
85
82
|
const cov = state.coverage;
|
|
86
83
|
if (cov === null) return null;
|
|
@@ -108,10 +105,7 @@ const formatCoverageSection = (state, maxGaps) => {
|
|
|
108
105
|
lines.push(`- ${g.file}${detail}`);
|
|
109
106
|
}
|
|
110
107
|
const omitted = cov.gaps.length - topN.length;
|
|
111
|
-
if (omitted > 0) {
|
|
112
|
-
const noun = omitted === 1 ? "gap" : "gaps";
|
|
113
|
-
lines.push(`- (+${omitted} more ${noun})`);
|
|
114
|
-
}
|
|
108
|
+
if (omitted > 0) lines.push(`- (+${Fmt.plural(omitted, "more gap", "more gaps")})`);
|
|
115
109
|
}
|
|
116
110
|
return lines.join("\n");
|
|
117
111
|
};
|
|
@@ -1,41 +1,42 @@
|
|
|
1
|
+
import { VitestAgentTokens, inkStyle, statusGlyph, statusInkStyle } from "../theme.js";
|
|
1
2
|
import { Text } from "ink";
|
|
3
|
+
import { useGlyphs } from "@effected/cli/ui";
|
|
2
4
|
import { jsxs } from "react/jsx-runtime";
|
|
3
5
|
|
|
4
6
|
//#region src/render-ink/CountColumns.tsx
|
|
5
7
|
/**
|
|
6
|
-
* The four-outcome count columns for an aggregate row (a project or a
|
|
7
|
-
* module): pass, fail, skip, timeout. All four always render — a zero
|
|
8
|
-
* is dimmed, never omitted — so the columns line up across rows.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
8
|
* Fixed width of the duration cell that follows the count columns on an
|
|
12
9
|
* aggregate row — sized for the formatter's widest common output
|
|
13
|
-
* (`
|
|
10
|
+
* (`59m 59s`). Longer values overflow their row rather than truncate.
|
|
14
11
|
*
|
|
15
12
|
* @public
|
|
16
13
|
*/
|
|
17
14
|
const DURATION_CELL_WIDTH = 7;
|
|
18
|
-
const cell = (count,
|
|
19
|
-
|
|
20
|
-
children: [String(count).padStart(4),
|
|
15
|
+
const cell = (count, status, glyphs) => /* @__PURE__ */ jsxs(Text, {
|
|
16
|
+
...count > 0 ? statusInkStyle(status) : inkStyle(VitestAgentTokens.zero),
|
|
17
|
+
children: [String(count).padStart(4), statusGlyph(status, glyphs)]
|
|
21
18
|
});
|
|
22
19
|
/**
|
|
23
20
|
* Renders four colored count columns (pass ✓, fail ✗, skip ↷, timeout ⧖)
|
|
24
21
|
* for an aggregate row. Counts render right-aligned in fixed 4-digit cells so
|
|
25
22
|
* columns align across rows; counts of 10,000+ overflow their row without
|
|
26
|
-
* truncation. Zeros are
|
|
23
|
+
* truncation. Zeros are greyed; all four columns always appear. Glyphs and
|
|
24
|
+
* colours come from `VitestAgentStatus` (skip is `muted`).
|
|
27
25
|
*
|
|
28
26
|
* @public
|
|
29
27
|
*/
|
|
30
|
-
const CountColumns = ({ passCount, failCount, skipCount, timeoutCount }) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
const CountColumns = ({ passCount, failCount, skipCount, timeoutCount }) => {
|
|
29
|
+
const glyphs = useGlyphs();
|
|
30
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
31
|
+
cell(passCount, "success", glyphs),
|
|
32
|
+
" ",
|
|
33
|
+
cell(failCount, "failure", glyphs),
|
|
34
|
+
" ",
|
|
35
|
+
cell(skipCount, "skip", glyphs),
|
|
36
|
+
" ",
|
|
37
|
+
cell(timeoutCount, "timeout", glyphs)
|
|
38
|
+
] });
|
|
39
|
+
};
|
|
39
40
|
|
|
40
41
|
//#endregion
|
|
41
42
|
export { CountColumns, DURATION_CELL_WIDTH };
|
|
@@ -1,16 +1,22 @@
|
|
|
1
|
+
import { inkStyle } from "../theme.js";
|
|
1
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import { Fmt } from "@effected/cli";
|
|
2
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
5
|
|
|
4
6
|
//#region src/render-ink/CoverageBlock.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Coverage section: per-metric percentage, threshold violations, top-N gaps.
|
|
9
|
+
*/
|
|
5
10
|
const METRIC_ORDER = [
|
|
6
11
|
"lines",
|
|
7
12
|
"branches",
|
|
8
13
|
"functions",
|
|
9
14
|
"statements"
|
|
10
15
|
];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
/** Coverage metrics arrive on istanbul's 0–100 scale. */
|
|
17
|
+
const formatPercent = (n) => Fmt.percent(n, { scale: 100 });
|
|
18
|
+
/** A metric below its threshold is a coverage threshold failure: the kit's `failure` token. */
|
|
19
|
+
const FAILURE = inkStyle("failure");
|
|
14
20
|
/**
|
|
15
21
|
* Renders the coverage section: per-metric percentages, threshold
|
|
16
22
|
* violations, and top-N gap entries sorted by missing line count.
|
|
@@ -35,7 +41,7 @@ const CoverageBlock = ({ coverage, maxGaps = 3 }) => {
|
|
|
35
41
|
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
36
42
|
/* @__PURE__ */ jsx(Text, { children: ` ${metric}: ` }),
|
|
37
43
|
failing ? /* @__PURE__ */ jsx(Text, {
|
|
38
|
-
|
|
44
|
+
...FAILURE,
|
|
39
45
|
children: formatPercent(actual)
|
|
40
46
|
}) : /* @__PURE__ */ jsx(Text, { children: formatPercent(actual) }),
|
|
41
47
|
threshold !== void 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
@@ -51,10 +57,10 @@ const CoverageBlock = ({ coverage, maxGaps = 3 }) => {
|
|
|
51
57
|
coverage.violations.length > 0 ? /* @__PURE__ */ jsxs(Box, {
|
|
52
58
|
flexDirection: "column",
|
|
53
59
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
54
|
-
|
|
60
|
+
...FAILURE,
|
|
55
61
|
children: "Violations"
|
|
56
62
|
}), coverage.violations.map((v) => /* @__PURE__ */ jsx(Text, {
|
|
57
|
-
|
|
63
|
+
...FAILURE,
|
|
58
64
|
children: ` ${v.metric}: ${formatPercent(v.actual)} < ${formatPercent(v.expected)}`
|
|
59
65
|
}, v.metric))]
|
|
60
66
|
}) : null,
|
|
@@ -68,7 +74,7 @@ const CoverageBlock = ({ coverage, maxGaps = 3 }) => {
|
|
|
68
74
|
topGaps.map((g) => /* @__PURE__ */ jsxs(Text, { children: [` ${g.file}`, g.uncoveredLines !== void 0 ? `: ${g.uncoveredLines}` : ""] }, g.file)),
|
|
69
75
|
elidedGaps > 0 ? /* @__PURE__ */ jsx(Text, {
|
|
70
76
|
dimColor: true,
|
|
71
|
-
children: ` (+${elidedGaps
|
|
77
|
+
children: ` (+${Fmt.plural(elidedGaps, "more gap", "more gaps")})`
|
|
72
78
|
}) : null
|
|
73
79
|
]
|
|
74
80
|
}) : null
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
+
import { inkStyle, statusGlyph, statusInkStyle } from "../theme.js";
|
|
1
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import { useGlyphs } from "@effected/cli/ui";
|
|
2
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
5
|
|
|
4
6
|
//#region src/render-ink/FailureSection.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Failure list: one block per failed test with classification, message,
|
|
9
|
+
* diff, and optionally stack trace.
|
|
10
|
+
*/
|
|
5
11
|
const FailureRow = ({ failure, includeStack }) => {
|
|
6
12
|
const suite = failure.suitePath.length > 0 ? `${failure.suitePath.join(" > ")} > ` : "";
|
|
7
13
|
const classification = failure.classification !== null ? ` [${failure.classification}]` : "";
|
|
14
|
+
const glyphs = useGlyphs();
|
|
8
15
|
return /* @__PURE__ */ jsxs(Box, {
|
|
9
16
|
flexDirection: "column",
|
|
10
17
|
children: [
|
|
11
18
|
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
12
19
|
/* @__PURE__ */ jsx(Text, {
|
|
13
|
-
|
|
14
|
-
children: "
|
|
20
|
+
...statusInkStyle("failure"),
|
|
21
|
+
children: statusGlyph("failure", glyphs)
|
|
15
22
|
}),
|
|
16
23
|
` ${failure.modulePath} > ${suite}${failure.testName}`,
|
|
17
24
|
failure.classification !== null ? /* @__PURE__ */ jsx(Text, {
|
|
@@ -20,17 +27,17 @@ const FailureRow = ({ failure, includeStack }) => {
|
|
|
20
27
|
}) : null
|
|
21
28
|
] }) }),
|
|
22
29
|
failure.error?.message !== void 0 ? /* @__PURE__ */ jsx(Text, {
|
|
23
|
-
|
|
30
|
+
...inkStyle("failure"),
|
|
24
31
|
children: ` ${failure.error.message.split("\n", 1)[0] ?? ""}`
|
|
25
32
|
}) : null,
|
|
26
33
|
failure.error?.diff !== void 0 ? failure.error.diff.split("\n").map((line, idx) => {
|
|
27
34
|
const key = `diff-${idx}`;
|
|
28
35
|
if (line.startsWith("-")) return /* @__PURE__ */ jsx(Text, {
|
|
29
|
-
|
|
36
|
+
...inkStyle("failure"),
|
|
30
37
|
children: ` ${line}`
|
|
31
38
|
}, key);
|
|
32
39
|
if (line.startsWith("+")) return /* @__PURE__ */ jsx(Text, {
|
|
33
|
-
|
|
40
|
+
...inkStyle("success"),
|
|
34
41
|
children: ` ${line}`
|
|
35
42
|
}, key);
|
|
36
43
|
return /* @__PURE__ */ jsx(Text, { children: ` ${line}` }, key);
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
+
import { VitestAgentTokens, inkStyle, statusGlyph, statusInkStyle } from "../theme.js";
|
|
1
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import { useGlyphs } from "@effected/cli/ui";
|
|
2
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
5
|
|
|
4
6
|
//#region src/render-ink/FailuresSection.tsx
|
|
7
|
+
/**
|
|
8
|
+
* The capped failures block for aggregate-row shapes (workspace,
|
|
9
|
+
* single-project) — the rows there are projects or modules and cannot
|
|
10
|
+
* expand a per-test error inline, so the failures collect here.
|
|
11
|
+
*
|
|
12
|
+
* Each entry: a glyph (⧖ for a timed-out failure, ✗ otherwise), the
|
|
13
|
+
* `module › suite › test` path, the classification tag when known, and
|
|
14
|
+
* one indented line — the first line of the error message.
|
|
15
|
+
*/
|
|
5
16
|
const pathOf = (f) => {
|
|
6
17
|
return [
|
|
7
18
|
f.modulePath,
|
|
@@ -9,6 +20,7 @@ const pathOf = (f) => {
|
|
|
9
20
|
f.testName
|
|
10
21
|
].join(" › ");
|
|
11
22
|
};
|
|
23
|
+
const failureStatus = (f) => f.timedOut === true ? "timeout" : "failure";
|
|
12
24
|
const firstLine = (message) => message.split("\n", 1)[0] ?? "";
|
|
13
25
|
const FAILURE_VALUE_LIMIT = 200;
|
|
14
26
|
/**
|
|
@@ -20,6 +32,7 @@ const FAILURE_VALUE_LIMIT = 200;
|
|
|
20
32
|
const FailuresSection = ({ failures, limit }) => {
|
|
21
33
|
const shown = failures.slice(0, limit);
|
|
22
34
|
const overflow = failures.length - shown.length;
|
|
35
|
+
const glyphs = useGlyphs();
|
|
23
36
|
return /* @__PURE__ */ jsxs(Box, {
|
|
24
37
|
flexDirection: "column",
|
|
25
38
|
children: [
|
|
@@ -37,13 +50,13 @@ const FailuresSection = ({ failures, limit }) => {
|
|
|
37
50
|
/* @__PURE__ */ jsxs(Text, { children: [
|
|
38
51
|
" ",
|
|
39
52
|
/* @__PURE__ */ jsx(Text, {
|
|
40
|
-
|
|
41
|
-
children: f
|
|
53
|
+
...statusInkStyle(failureStatus(f)),
|
|
54
|
+
children: statusGlyph(failureStatus(f), glyphs)
|
|
42
55
|
}),
|
|
43
56
|
" ",
|
|
44
57
|
pathOf(f),
|
|
45
58
|
f.classification !== null ? /* @__PURE__ */ jsxs(Text, {
|
|
46
|
-
|
|
59
|
+
...inkStyle(VitestAgentTokens.classification),
|
|
47
60
|
children: [
|
|
48
61
|
" [",
|
|
49
62
|
f.classification,
|
package/render-ink/ProjectRow.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { statusInkStyle } from "../theme.js";
|
|
1
2
|
import { formatDisplayDuration } from "../format-duration.js";
|
|
2
3
|
import { CountColumns, DURATION_CELL_WIDTH } from "./CountColumns.js";
|
|
3
4
|
import { StatusIcon } from "./StatusIcon.js";
|
|
@@ -13,7 +14,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
13
14
|
*/
|
|
14
15
|
const projectGlyph = (counts, running, timedOut, frame) => {
|
|
15
16
|
if (running && !timedOut) return /* @__PURE__ */ jsx(Text, {
|
|
16
|
-
|
|
17
|
+
...statusInkStyle("running"),
|
|
17
18
|
children: frame
|
|
18
19
|
});
|
|
19
20
|
if (counts.failCount > 0) return /* @__PURE__ */ jsx(StatusIcon, { status: "failed" });
|
package/render-ink/StatusIcon.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { statusGlyph, statusInkStyle } from "../theme.js";
|
|
1
2
|
import { Text } from "ink";
|
|
3
|
+
import { useGlyphs } from "@effected/cli/ui";
|
|
2
4
|
import { jsx } from "react/jsx-runtime";
|
|
3
5
|
|
|
4
6
|
//#region src/render-ink/StatusIcon.tsx
|
|
@@ -8,37 +10,34 @@ import { jsx } from "react/jsx-runtime";
|
|
|
8
10
|
* Ink-only primitive — no DOM-isms. Renders one colored character so
|
|
9
11
|
* the surrounding row can keep its width budget predictable.
|
|
10
12
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
pending: "cyan",
|
|
27
|
-
running: "yellow",
|
|
28
|
-
queued: "gray",
|
|
29
|
-
finished: "green",
|
|
30
|
-
threshold: "yellow",
|
|
31
|
-
"timed-out": "#e09a4e"
|
|
13
|
+
/**
|
|
14
|
+
* Each icon kind's status in the shared vocabulary. `finished` is a pass;
|
|
15
|
+
* `threshold` is a coverage threshold failure, which the kit ranks as a
|
|
16
|
+
* `failure`, not a warning.
|
|
17
|
+
*/
|
|
18
|
+
const STATUS = {
|
|
19
|
+
passed: "success",
|
|
20
|
+
failed: "failure",
|
|
21
|
+
skipped: "skip",
|
|
22
|
+
pending: "pending",
|
|
23
|
+
running: "running",
|
|
24
|
+
queued: "queued",
|
|
25
|
+
finished: "success",
|
|
26
|
+
threshold: "failure",
|
|
27
|
+
"timed-out": "timeout"
|
|
32
28
|
};
|
|
33
29
|
/**
|
|
34
30
|
* Renders a single colored status glyph for a test, module, or run.
|
|
35
31
|
*
|
|
36
32
|
* @public
|
|
37
33
|
*/
|
|
38
|
-
const StatusIcon = ({ status }) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
const StatusIcon = ({ status }) => {
|
|
35
|
+
const glyphs = useGlyphs();
|
|
36
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
37
|
+
...statusInkStyle(STATUS[status]),
|
|
38
|
+
children: statusGlyph(STATUS[status], glyphs)
|
|
39
|
+
});
|
|
40
|
+
};
|
|
42
41
|
|
|
43
42
|
//#endregion
|
|
44
43
|
export { StatusIcon };
|