@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/render-ink/StreamApp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { classifyRunShape } from "../dispatcher/classify.js";
|
|
2
|
+
import { VitestAgentTokens, inkStyle, statusGlyph, statusInkStyle } from "../theme.js";
|
|
2
3
|
import { formatDisplayDuration } from "../format-duration.js";
|
|
3
4
|
import { CountColumns, DURATION_CELL_WIDTH } from "./CountColumns.js";
|
|
4
5
|
import { StatusIcon } from "./StatusIcon.js";
|
|
@@ -8,6 +9,8 @@ import { spinnerFrame } from "./spinner.js";
|
|
|
8
9
|
import { TestRow } from "./TestRow.js";
|
|
9
10
|
import { TrendLine } from "./TrendLine.js";
|
|
10
11
|
import { Box, Text } from "ink";
|
|
12
|
+
import { Fmt } from "@effected/cli";
|
|
13
|
+
import { useGlyphs } from "@effected/cli/ui";
|
|
11
14
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
15
|
|
|
13
16
|
//#region src/render-ink/StreamApp.tsx
|
|
@@ -118,7 +121,7 @@ const ModuleStreamRow = ({ module, nowMs, frame, nameWidth, timedOut, tagUnion:
|
|
|
118
121
|
});
|
|
119
122
|
} else glyph = /* @__PURE__ */ jsx(StatusIcon, { status: "timed-out" });
|
|
120
123
|
} else if (running) glyph = /* @__PURE__ */ jsx(Text, {
|
|
121
|
-
|
|
124
|
+
...statusInkStyle("running"),
|
|
122
125
|
children: frame
|
|
123
126
|
});
|
|
124
127
|
else glyph = /* @__PURE__ */ jsx(StatusIcon, { status: moduleIcon(module) });
|
|
@@ -182,47 +185,54 @@ const InlineError = ({ failure }) => {
|
|
|
182
185
|
};
|
|
183
186
|
const failureKey = (f) => `failure:${f.modulePath}::${f.suitePath.join("/")}::${f.testName}`;
|
|
184
187
|
/** A process-level unhandled error, rendered in the aggregate Unhandled errors block. */
|
|
185
|
-
const UnhandledErrorItem = ({ error }) =>
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"
|
|
189
|
-
/* @__PURE__ */
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
188
|
+
const UnhandledErrorItem = ({ error }) => {
|
|
189
|
+
const glyphs = useGlyphs();
|
|
190
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
191
|
+
flexDirection: "column",
|
|
192
|
+
children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
193
|
+
" ",
|
|
194
|
+
/* @__PURE__ */ jsx(Text, {
|
|
195
|
+
...statusInkStyle("failure"),
|
|
196
|
+
children: statusGlyph("failure", glyphs)
|
|
197
|
+
}),
|
|
198
|
+
" unhandled error"
|
|
199
|
+
] }), /* @__PURE__ */ jsxs(Text, {
|
|
200
|
+
dimColor: true,
|
|
201
|
+
children: [" ", error.message.split("\n", 1)[0] ?? ""]
|
|
202
|
+
})]
|
|
203
|
+
});
|
|
204
|
+
};
|
|
199
205
|
const testKey = (modulePath, t) => `test:${modulePath}::${t.suitePath.join("/")}::${t.testName}`;
|
|
200
206
|
const failurePath = (f) => [
|
|
201
207
|
f.modulePath,
|
|
202
208
|
...f.suitePath,
|
|
203
209
|
f.testName
|
|
204
210
|
].join(" › ");
|
|
211
|
+
const failureStatus = (f) => f.timedOut === true ? "timeout" : "failure";
|
|
205
212
|
/** A failure entry rendered in the Live region. */
|
|
206
|
-
const FailureItem = ({ failure }) =>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
"
|
|
210
|
-
/* @__PURE__ */
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
})
|
|
213
|
+
const FailureItem = ({ failure }) => {
|
|
214
|
+
const glyphs = useGlyphs();
|
|
215
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
216
|
+
flexDirection: "column",
|
|
217
|
+
children: [/* @__PURE__ */ jsxs(Text, { children: [
|
|
218
|
+
" ",
|
|
219
|
+
/* @__PURE__ */ jsx(Text, {
|
|
220
|
+
...statusInkStyle(failureStatus(failure)),
|
|
221
|
+
children: statusGlyph(failureStatus(failure), glyphs)
|
|
222
|
+
}),
|
|
223
|
+
" ",
|
|
224
|
+
failurePath(failure),
|
|
225
|
+
failure.classification !== null ? /* @__PURE__ */ jsxs(Text, {
|
|
226
|
+
...inkStyle(VitestAgentTokens.classification),
|
|
227
|
+
children: [
|
|
228
|
+
" [",
|
|
229
|
+
failure.classification,
|
|
230
|
+
"]"
|
|
231
|
+
]
|
|
232
|
+
}) : null
|
|
233
|
+
] }), /* @__PURE__ */ jsx(InlineError, { failure })]
|
|
234
|
+
});
|
|
235
|
+
};
|
|
226
236
|
/**
|
|
227
237
|
* The bottom `Total:` rollup line. In the workspace view `labelWidth`
|
|
228
238
|
* pads the label to the project-row prefix width so the count columns
|
|
@@ -245,9 +255,15 @@ const TotalsLine = ({ totals, labelWidth }) => /* @__PURE__ */ jsxs(Text, { chil
|
|
|
245
255
|
children: [" ", formatDisplayDuration(totals.durationMs).padStart(7)]
|
|
246
256
|
})
|
|
247
257
|
] });
|
|
258
|
+
/**
|
|
259
|
+
* The coverage judgment. A threshold violation is a `failure` (the kit's
|
|
260
|
+
* decision: threshold failure is ✗, only a target shortfall is ⚠).
|
|
261
|
+
*/
|
|
248
262
|
const CoverageItem = ({ state }) => {
|
|
249
263
|
if (state.coverage === null) return null;
|
|
250
|
-
const
|
|
264
|
+
const violations = state.coverage.violations.length;
|
|
265
|
+
const status = violations === 0 ? "success" : "failure";
|
|
266
|
+
const glyphs = useGlyphs();
|
|
251
267
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
252
268
|
/* @__PURE__ */ jsx(Text, {
|
|
253
269
|
bold: true,
|
|
@@ -255,11 +271,11 @@ const CoverageItem = ({ state }) => {
|
|
|
255
271
|
}),
|
|
256
272
|
" ",
|
|
257
273
|
/* @__PURE__ */ jsx(Text, {
|
|
258
|
-
|
|
259
|
-
children:
|
|
274
|
+
...statusInkStyle(status),
|
|
275
|
+
children: statusGlyph(status, glyphs)
|
|
260
276
|
}),
|
|
261
277
|
" ",
|
|
262
|
-
|
|
278
|
+
violations === 0 ? "all metrics meet thresholds" : Fmt.plural(violations, "threshold violation")
|
|
263
279
|
] });
|
|
264
280
|
};
|
|
265
281
|
const samePath = (a, b) => {
|
|
@@ -273,7 +289,7 @@ const samePath = (a, b) => {
|
|
|
273
289
|
* run picture — all rows in discovery order, with running rows capped
|
|
274
290
|
* at MAX_LIVE_RUNNING_ROWS and finished rows always shown.
|
|
275
291
|
*/
|
|
276
|
-
const liveRegion = (state, shape, nowMs, frame) => {
|
|
292
|
+
const liveRegion = (state, shape, nowMs, frame, glyphs) => {
|
|
277
293
|
const groups = groupByProject(state);
|
|
278
294
|
const timedOut = state.phase === "timed-out";
|
|
279
295
|
const finished = state.phase === "finished" || state.phase === "timed-out";
|
|
@@ -287,14 +303,14 @@ const liveRegion = (state, shape, nowMs, frame) => {
|
|
|
287
303
|
}) : null;
|
|
288
304
|
if (shape === "single-test") {
|
|
289
305
|
const sole = ordered[0];
|
|
290
|
-
if (sole === void 0 || sole.tests.length === 0) return /* @__PURE__ */
|
|
306
|
+
if (sole === void 0 || sole.tests.length === 0) return /* @__PURE__ */ jsxs(Text, {
|
|
291
307
|
dimColor: true,
|
|
292
|
-
children: "discovering tests
|
|
308
|
+
children: ["discovering tests", glyphs.ellipsis]
|
|
293
309
|
});
|
|
294
310
|
const only = sole.tests[0];
|
|
295
|
-
if (only === void 0) return /* @__PURE__ */
|
|
311
|
+
if (only === void 0) return /* @__PURE__ */ jsxs(Text, {
|
|
296
312
|
dimColor: true,
|
|
297
|
-
children: "discovering tests
|
|
313
|
+
children: ["discovering tests", glyphs.ellipsis]
|
|
298
314
|
});
|
|
299
315
|
const failure = only.status === "failed" || only.status === "timed-out" ? state.failures.find((f) => f.testName === only.testName && f.modulePath === sole.modulePath && samePath(f.suitePath, only.suitePath)) : void 0;
|
|
300
316
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -306,9 +322,9 @@ const liveRegion = (state, shape, nowMs, frame) => {
|
|
|
306
322
|
unhandledErrorsSection
|
|
307
323
|
] });
|
|
308
324
|
}
|
|
309
|
-
if (groups.length === 0) return /* @__PURE__ */
|
|
325
|
+
if (groups.length === 0) return /* @__PURE__ */ jsxs(Text, {
|
|
310
326
|
dimColor: true,
|
|
311
|
-
children: "discovering tests
|
|
327
|
+
children: ["discovering tests", glyphs.ellipsis]
|
|
312
328
|
});
|
|
313
329
|
const failuresSection = state.failures.length > 0 ? /* @__PURE__ */ jsx(Box, {
|
|
314
330
|
flexDirection: "column",
|
|
@@ -318,10 +334,10 @@ const liveRegion = (state, shape, nowMs, frame) => {
|
|
|
318
334
|
const trendItem = state.trend !== null ? /* @__PURE__ */ jsx(TrendLine, { trend: state.trend }) : null;
|
|
319
335
|
const totalsItem = (labelWidth) => /* @__PURE__ */ jsxs(Box, {
|
|
320
336
|
flexDirection: "column",
|
|
321
|
-
children: [finished && timedOut ? /* @__PURE__ */
|
|
322
|
-
|
|
337
|
+
children: [finished && timedOut ? /* @__PURE__ */ jsxs(Text, {
|
|
338
|
+
...statusInkStyle("timeout"),
|
|
323
339
|
bold: true,
|
|
324
|
-
children: "
|
|
340
|
+
children: [statusGlyph("timeout", glyphs), " Run timed out"]
|
|
325
341
|
}) : null, /* @__PURE__ */ jsx(TotalsLine, {
|
|
326
342
|
totals: state.totals,
|
|
327
343
|
labelWidth
|
|
@@ -379,7 +395,9 @@ const liveRegion = (state, shape, nowMs, frame) => {
|
|
|
379
395
|
runningOverflow > 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
380
396
|
dimColor: true,
|
|
381
397
|
children: [
|
|
382
|
-
"
|
|
398
|
+
" ",
|
|
399
|
+
glyphs.ellipsis,
|
|
400
|
+
" and ",
|
|
383
401
|
runningOverflow,
|
|
384
402
|
" more running"
|
|
385
403
|
]
|
|
@@ -430,7 +448,9 @@ const liveRegion = (state, shape, nowMs, frame) => {
|
|
|
430
448
|
runningOverflow > 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
431
449
|
dimColor: true,
|
|
432
450
|
children: [
|
|
433
|
-
"
|
|
451
|
+
" ",
|
|
452
|
+
glyphs.ellipsis,
|
|
453
|
+
" and ",
|
|
434
454
|
runningOverflow,
|
|
435
455
|
" more running"
|
|
436
456
|
]
|
|
@@ -443,9 +463,9 @@ const liveRegion = (state, shape, nowMs, frame) => {
|
|
|
443
463
|
] });
|
|
444
464
|
}
|
|
445
465
|
const sole = ordered[0];
|
|
446
|
-
if (sole === void 0) return /* @__PURE__ */
|
|
466
|
+
if (sole === void 0) return /* @__PURE__ */ jsxs(Text, {
|
|
447
467
|
dimColor: true,
|
|
448
|
-
children: "discovering tests
|
|
468
|
+
children: ["discovering tests", glyphs.ellipsis]
|
|
449
469
|
});
|
|
450
470
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
451
471
|
/* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -483,10 +503,11 @@ const liveRegion = (state, shape, nowMs, frame) => {
|
|
|
483
503
|
*/
|
|
484
504
|
const StreamApp = ({ state, frameIndex, nowMs }) => {
|
|
485
505
|
const now = nowMs ?? Date.now();
|
|
486
|
-
const
|
|
506
|
+
const glyphs = useGlyphs();
|
|
507
|
+
const frame = spinnerFrame(frameIndex, glyphs);
|
|
487
508
|
const projects = groupByProject(state).map((g) => buildProjectSummary(g.name, sumCounts(g.modules)));
|
|
488
509
|
const shape = classifyRunShape(state, projects);
|
|
489
|
-
const liveContent = liveRegion(state, shape, now, frame);
|
|
510
|
+
const liveContent = liveRegion(state, shape, now, frame, glyphs);
|
|
490
511
|
return /* @__PURE__ */ jsx(Box, {
|
|
491
512
|
flexDirection: "column",
|
|
492
513
|
children: liveContent
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { inkStyle } from "../theme.js";
|
|
1
2
|
import { Box, Text } from "ink";
|
|
3
|
+
import { Token } from "@effected/cli";
|
|
2
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
5
|
|
|
4
6
|
//#region src/render-ink/SuggestedActions.tsx
|
|
5
|
-
const
|
|
6
|
-
info: "blue",
|
|
7
|
-
warn: "
|
|
8
|
-
blocker: "
|
|
7
|
+
const SEVERITY_TOKEN = {
|
|
8
|
+
info: Token.named("blue"),
|
|
9
|
+
warn: "warning",
|
|
10
|
+
blocker: "failure"
|
|
9
11
|
};
|
|
10
12
|
/**
|
|
11
13
|
* Renders the suggested-actions queue as severity-prefixed rows with
|
|
@@ -24,7 +26,7 @@ const SuggestedActions = ({ actions }) => {
|
|
|
24
26
|
flexDirection: "column",
|
|
25
27
|
children: [/* @__PURE__ */ jsxs(Box, { children: [
|
|
26
28
|
/* @__PURE__ */ jsx(Text, {
|
|
27
|
-
|
|
29
|
+
...inkStyle(SEVERITY_TOKEN[action.severity]),
|
|
28
30
|
bold: true,
|
|
29
31
|
children: ` ${action.severity}: `
|
|
30
32
|
}),
|
package/render-ink/TagColumns.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { VitestAgentTokens, inkStyle } from "../theme.js";
|
|
1
2
|
import { Text } from "ink";
|
|
2
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
4
|
|
|
@@ -34,7 +35,7 @@ const TagColumns = ({ tags, counts }) => {
|
|
|
34
35
|
return /* @__PURE__ */ jsx(Text, { children: tags.map((tag, i) => {
|
|
35
36
|
const count = counts?.[tag] ?? 0;
|
|
36
37
|
return /* @__PURE__ */ jsxs(Text, {
|
|
37
|
-
|
|
38
|
+
...inkStyle(count > 0 ? VitestAgentTokens.tag : VitestAgentTokens.zero),
|
|
38
39
|
children: [
|
|
39
40
|
i > 0 ? " " : "",
|
|
40
41
|
tag,
|
package/render-ink/TrendLine.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { VitestAgentTokens, inkStyle } from "../theme.js";
|
|
1
2
|
import { 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/TrendLine.tsx
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
regressing: "red",
|
|
11
|
-
stable: "gray"
|
|
7
|
+
/** A regressing trend is a `warning`, not a failure (the kit's drift decision). */
|
|
8
|
+
const TOKEN = {
|
|
9
|
+
improving: "success",
|
|
10
|
+
regressing: "warning",
|
|
11
|
+
stable: VitestAgentTokens.stable
|
|
12
12
|
};
|
|
13
13
|
/**
|
|
14
14
|
* Renders the one-line trend signal: direction (colored) and run count.
|
|
@@ -16,7 +16,7 @@ const COLOR = {
|
|
|
16
16
|
* @public
|
|
17
17
|
*/
|
|
18
18
|
const TrendLine = ({ trend }) => {
|
|
19
|
-
const runs = trend.runCount
|
|
19
|
+
const runs = Fmt.plural(trend.runCount, "run");
|
|
20
20
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
21
21
|
/* @__PURE__ */ jsx(Text, {
|
|
22
22
|
bold: true,
|
|
@@ -24,7 +24,7 @@ const TrendLine = ({ trend }) => {
|
|
|
24
24
|
}),
|
|
25
25
|
" ",
|
|
26
26
|
/* @__PURE__ */ jsx(Text, {
|
|
27
|
-
|
|
27
|
+
...inkStyle(TOKEN[trend.direction]),
|
|
28
28
|
children: trend.direction
|
|
29
29
|
}),
|
|
30
30
|
/* @__PURE__ */ jsxs(Text, {
|
package/render-ink/index.js
CHANGED
|
@@ -6,10 +6,10 @@ import { StatusIcon } from "./StatusIcon.js";
|
|
|
6
6
|
import { ModuleHeader } from "./ModuleHeader.js";
|
|
7
7
|
import { TagColumns, tagUnion } from "./TagColumns.js";
|
|
8
8
|
import { ProjectRow } from "./ProjectRow.js";
|
|
9
|
-
import { SPINNER_FRAMES, SPINNER_FRAME_MS, spinnerFrame
|
|
9
|
+
import { SPINNER_FRAMES, SPINNER_FRAME_MS, spinnerFrame } from "./spinner.js";
|
|
10
10
|
import { TestRow } from "./TestRow.js";
|
|
11
11
|
import { TrendLine } from "./TrendLine.js";
|
|
12
12
|
import { StreamApp } from "./StreamApp.js";
|
|
13
13
|
import { SuggestedActions } from "./SuggestedActions.js";
|
|
14
14
|
|
|
15
|
-
export { CountColumns, CoverageBlock, DURATION_CELL_WIDTH, FailureSection, FailuresSection, ModuleHeader, ProjectRow, SPINNER_FRAMES, SPINNER_FRAME_MS, StatusIcon, StreamApp, SuggestedActions, TagColumns, TestRow, TrendLine, spinnerFrame,
|
|
15
|
+
export { CountColumns, CoverageBlock, DURATION_CELL_WIDTH, FailureSection, FailuresSection, ModuleHeader, ProjectRow, SPINNER_FRAMES, SPINNER_FRAME_MS, StatusIcon, StreamApp, SuggestedActions, TagColumns, TestRow, TrendLine, spinnerFrame, tagUnion };
|
package/render-ink/spinner.js
CHANGED
|
@@ -1,63 +1,34 @@
|
|
|
1
|
+
import { Glyphs } from "@effected/cli";
|
|
2
|
+
|
|
1
3
|
//#region src/render-ink/spinner.ts
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* No `ink-spinner` dependency — that package is a thin wrapper over the
|
|
6
|
-
* same ten Braille characters plus a timer. The `stream` renderer
|
|
7
|
-
* already needs a frame clock for the ticking elapsed column, so the
|
|
8
|
-
* timer is shared and only the frame array lives here.
|
|
9
|
-
*
|
|
10
|
-
* The frame index is presentation state: it is derived from wall-clock
|
|
11
|
-
* time by `createLiveInk` and passed to `StreamApp` as a prop. It never
|
|
12
|
-
* enters the event-sourced `RenderState`.
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* The ten Braille spinner frames, in animation order.
|
|
5
|
+
* The Unicode spinner frames, in animation order (`Glyphs.unicode.spinner`).
|
|
16
6
|
*
|
|
17
7
|
* @public
|
|
18
8
|
*/
|
|
19
|
-
const SPINNER_FRAMES =
|
|
20
|
-
"⠋",
|
|
21
|
-
"⠙",
|
|
22
|
-
"⠹",
|
|
23
|
-
"⠸",
|
|
24
|
-
"⠼",
|
|
25
|
-
"⠴",
|
|
26
|
-
"⠦",
|
|
27
|
-
"⠧",
|
|
28
|
-
"⠇",
|
|
29
|
-
"⠏"
|
|
30
|
-
];
|
|
9
|
+
const SPINNER_FRAMES = Glyphs.unicode.spinner;
|
|
31
10
|
/**
|
|
32
|
-
* How long each spinner frame is held, in milliseconds
|
|
11
|
+
* How long each spinner frame is held, in milliseconds
|
|
12
|
+
* (`Glyphs.unicode.spinnerIntervalMs`).
|
|
33
13
|
*
|
|
34
14
|
* @public
|
|
35
15
|
*/
|
|
36
|
-
const SPINNER_FRAME_MS =
|
|
16
|
+
const SPINNER_FRAME_MS = Glyphs.unicode.spinnerIntervalMs;
|
|
37
17
|
/**
|
|
38
18
|
* Resolve the spinner glyph for a frame index. The index wraps modulo
|
|
39
19
|
* the frame count and tolerates negative values, so a wall-clock-derived
|
|
40
20
|
* index is always valid.
|
|
41
21
|
*
|
|
42
22
|
* @param index - the frame index (wraps modulo frame count)
|
|
43
|
-
* @
|
|
23
|
+
* @param glyphs - the glyph set whose frames to draw; Unicode by default
|
|
24
|
+
* @returns the spinner glyph for the given frame
|
|
44
25
|
* @public
|
|
45
26
|
*/
|
|
46
|
-
const spinnerFrame = (index) => {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
return
|
|
27
|
+
const spinnerFrame = (index, glyphs = Glyphs.unicode) => {
|
|
28
|
+
const frames = glyphs.spinner;
|
|
29
|
+
const count = frames.length;
|
|
30
|
+
return frames[(Math.trunc(index) % count + count) % count] ?? "";
|
|
50
31
|
};
|
|
51
|
-
/**
|
|
52
|
-
* Derive the spinner frame index from a wall-clock timestamp. Using the
|
|
53
|
-
* clock — rather than a monotonic counter — keeps the animation correct
|
|
54
|
-
* across watch-mode remounts with no extra closure state to reset.
|
|
55
|
-
*
|
|
56
|
-
* @param nowMs - current wall-clock time in milliseconds
|
|
57
|
-
* @returns the frame index for the given timestamp
|
|
58
|
-
* @public
|
|
59
|
-
*/
|
|
60
|
-
const spinnerFrameForTime = (nowMs) => Math.floor(nowMs / 80);
|
|
61
32
|
|
|
62
33
|
//#endregion
|
|
63
|
-
export { SPINNER_FRAMES, SPINNER_FRAME_MS, spinnerFrame
|
|
34
|
+
export { SPINNER_FRAMES, SPINNER_FRAME_MS, spinnerFrame };
|
package/theme.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Glyphs, Status, Token } from "@effected/cli";
|
|
2
|
+
import { inkProps } from "@effected/cli/ui";
|
|
3
|
+
|
|
4
|
+
//#region src/theme.ts
|
|
5
|
+
/**
|
|
6
|
+
* The status vocabulary every vitest-agent render path draws from.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Ranks slot the run statuses into the core ladder: `queued` (25) and
|
|
10
|
+
* `running` (35) sit among the not-yet-decided statuses, and `timeout` (85)
|
|
11
|
+
* is just under `failure` (90), so `Status.worst` over a project's
|
|
12
|
+
* outcomes reads failure, then timeout, then warning.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
15
|
+
*/
|
|
16
|
+
const VitestAgentStatus = Status.extend({
|
|
17
|
+
timeout: {
|
|
18
|
+
glyph: "⧖",
|
|
19
|
+
ascii: "[time]",
|
|
20
|
+
token: Token.hex("#e09a4e"),
|
|
21
|
+
rank: 85
|
|
22
|
+
},
|
|
23
|
+
running: {
|
|
24
|
+
glyph: "…",
|
|
25
|
+
ascii: "[..]",
|
|
26
|
+
token: Token.named("yellow"),
|
|
27
|
+
rank: 35
|
|
28
|
+
},
|
|
29
|
+
queued: {
|
|
30
|
+
glyph: "·",
|
|
31
|
+
ascii: "[.]",
|
|
32
|
+
token: Token.named("blackBright"),
|
|
33
|
+
rank: 25
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Named accents that are not statuses.
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
const VitestAgentTokens = {
|
|
42
|
+
/** The `[flaky]` / `[new-failure]` classification tag on a failure line. */
|
|
43
|
+
classification: Token.hex("#c98ae0"),
|
|
44
|
+
/** A zero in a count column: present for alignment, not signal. */
|
|
45
|
+
zero: Token.named("blackBright"),
|
|
46
|
+
/** The `stable` trend direction. */
|
|
47
|
+
stable: Token.named("blackBright"),
|
|
48
|
+
/** A non-zero tag count. */
|
|
49
|
+
tag: Token.named("cyan")
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Ink `<Text>` props for a semantic token or an explicit style.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Resolves the token through the kit's pure `Token.resolve` and maps the
|
|
56
|
+
* `Style` with the kit's `inkProps`. No colour level reaches this call
|
|
57
|
+
* site, so `inkProps` with no level emits every prop and Ink's own chalk
|
|
58
|
+
* level, set by `CliUi.live` or `CliUi.context`, gates what is drawn.
|
|
59
|
+
*
|
|
60
|
+
* @param token - a token name or a `Style`
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
const inkStyle = (token) => inkProps(Token.resolve(token));
|
|
64
|
+
/**
|
|
65
|
+
* The glyph of a status in a glyph set: its Unicode glyph, or its ASCII
|
|
66
|
+
* fallback when `glyphs` is the kit's ASCII set (the kit's `Status.glyph`).
|
|
67
|
+
*
|
|
68
|
+
* @param name - a status in {@link VitestAgentStatus}
|
|
69
|
+
* @param glyphs - the glyph set, from `Glyphs.select`; Unicode by default
|
|
70
|
+
* @public
|
|
71
|
+
*/
|
|
72
|
+
const statusGlyph = (name, glyphs = Glyphs.unicode) => VitestAgentStatus.glyph(name, glyphs);
|
|
73
|
+
/**
|
|
74
|
+
* Ink `<Text>` props for a status's glyph.
|
|
75
|
+
*
|
|
76
|
+
* @param name - a status in {@link VitestAgentStatus}
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
const statusInkStyle = (name) => inkStyle(VitestAgentStatus.def(name).token);
|
|
80
|
+
|
|
81
|
+
//#endregion
|
|
82
|
+
export { VitestAgentStatus, VitestAgentTokens, inkStyle, statusGlyph, statusInkStyle };
|