@flanksource/clicky-ui 0.3.52 → 0.3.54
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/dist/components/clipboard.cjs +76 -11
- package/dist/components/clipboard.cjs.map +1 -1
- package/dist/components/clipboard.d.ts +37 -6
- package/dist/components/clipboard.d.ts.map +1 -1
- package/dist/components/clipboard.js +75 -12
- package/dist/components/clipboard.js.map +1 -1
- package/dist/components.cjs +2 -0
- package/dist/components.d.ts +1 -1
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +2 -2
- package/dist/data/MatrixTreeTable.cjs +94 -0
- package/dist/data/MatrixTreeTable.cjs.map +1 -0
- package/dist/data/MatrixTreeTable.d.ts +40 -0
- package/dist/data/MatrixTreeTable.d.ts.map +1 -0
- package/dist/data/MatrixTreeTable.js +92 -0
- package/dist/data/MatrixTreeTable.js.map +1 -0
- package/dist/data/TaskManager.cjs +39 -32
- package/dist/data/TaskManager.cjs.map +1 -1
- package/dist/data/TaskManager.d.ts +6 -1
- package/dist/data/TaskManager.d.ts.map +1 -1
- package/dist/data/TaskManager.js +39 -32
- package/dist/data/TaskManager.js.map +1 -1
- package/dist/data/TreeDataTable.cjs +52 -0
- package/dist/data/TreeDataTable.cjs.map +1 -0
- package/dist/data/TreeDataTable.d.ts +17 -0
- package/dist/data/TreeDataTable.d.ts.map +1 -0
- package/dist/data/TreeDataTable.js +52 -0
- package/dist/data/TreeDataTable.js.map +1 -0
- package/dist/data/clicky-export/ClickyExportDialog.cjs +4 -3
- package/dist/data/clicky-export/ClickyExportDialog.cjs.map +1 -1
- package/dist/data/clicky-export/ClickyExportDialog.d.ts +4 -1
- package/dist/data/clicky-export/ClickyExportDialog.d.ts.map +1 -1
- package/dist/data/clicky-export/ClickyExportDialog.js +4 -3
- package/dist/data/clicky-export/ClickyExportDialog.js.map +1 -1
- package/dist/data/test-runner/MatrixMarks.cjs +212 -0
- package/dist/data/test-runner/MatrixMarks.cjs.map +1 -0
- package/dist/data/test-runner/MatrixMarks.d.ts +76 -0
- package/dist/data/test-runner/MatrixMarks.d.ts.map +1 -0
- package/dist/data/test-runner/MatrixMarks.js +205 -0
- package/dist/data/test-runner/MatrixMarks.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +3 -0
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -1
- package/dist/data/test-runner/TestDetailPanel.js +3 -0
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -1
- package/dist/data/test-runner/index.d.ts +2 -0
- package/dist/data/test-runner/index.d.ts.map +1 -1
- package/dist/data/test-runner/matrix-marks.cjs +119 -0
- package/dist/data/test-runner/matrix-marks.cjs.map +1 -0
- package/dist/data/test-runner/matrix-marks.d.ts +154 -0
- package/dist/data/test-runner/matrix-marks.d.ts.map +1 -0
- package/dist/data/test-runner/matrix-marks.js +113 -0
- package/dist/data/test-runner/matrix-marks.js.map +1 -0
- package/dist/data/version-info.cjs +3 -3
- package/dist/data/version-info.js +3 -3
- package/dist/data.cjs +27 -0
- package/dist/data.d.ts +3 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +6 -1
- package/dist/hooks/use-task-run.cjs +22 -9
- package/dist/hooks/use-task-run.cjs.map +1 -1
- package/dist/hooks/use-task-run.d.ts +2 -0
- package/dist/hooks/use-task-run.d.ts.map +1 -1
- package/dist/hooks/use-task-run.js +22 -9
- package/dist/hooks/use-task-run.js.map +1 -1
- package/dist/index.cjs +29 -0
- package/dist/index.js +7 -2
- package/dist/lib/tree-rows.cjs +63 -0
- package/dist/lib/tree-rows.cjs.map +1 -0
- package/dist/lib/tree-rows.d.ts +36 -0
- package/dist/lib/tree-rows.d.ts.map +1 -0
- package/dist/lib/tree-rows.js +60 -0
- package/dist/lib/tree-rows.js.map +1 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { MatrixCounts, MatrixEnvCounts, MatrixMarkKey } from './matrix-marks.js';
|
|
3
|
+
export type MatrixMarkProps = {
|
|
4
|
+
mark: MatrixMarkKey;
|
|
5
|
+
/** Hover title; "" leaves the glyph untitled where the surrounding element already names it. */
|
|
6
|
+
label?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
/** Print: never spins. */
|
|
9
|
+
static?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function MatrixMark({ mark, label, className, static: isStatic }: MatrixMarkProps): import("react").JSX.Element;
|
|
12
|
+
export type MatrixMixProps = {
|
|
13
|
+
counts: MatrixCounts;
|
|
14
|
+
unit: "steps" | "plans";
|
|
15
|
+
/** Outcomes drawn even at zero; every other zero count is dropped. */
|
|
16
|
+
always?: readonly MatrixMarkKey[];
|
|
17
|
+
static?: boolean;
|
|
18
|
+
};
|
|
19
|
+
/** Outcome counts as glyph + number, in matrix order. */
|
|
20
|
+
export declare function MatrixMix({ counts, unit, always, static: isStatic }: MatrixMixProps): import("react").JSX.Element;
|
|
21
|
+
export type MatrixElapsedProps = {
|
|
22
|
+
ms: number;
|
|
23
|
+
/** Still counting: drawn blue and titled "elapsed so far". */
|
|
24
|
+
running: boolean;
|
|
25
|
+
/** Prefix a clock glyph. */
|
|
26
|
+
clock?: boolean;
|
|
27
|
+
};
|
|
28
|
+
export declare function MatrixElapsed({ ms, running, clock }: MatrixElapsedProps): import("react").JSX.Element;
|
|
29
|
+
export type EmptyMatrixCellProps = {
|
|
30
|
+
/** The cell outcome: "queued", "absent", or "error" with detail "never started". Anything else throws. */
|
|
31
|
+
outcome: string;
|
|
32
|
+
/** Why a cell has no run: "never started". */
|
|
33
|
+
detail?: string;
|
|
34
|
+
/** Hover title of the cell. */
|
|
35
|
+
title: string;
|
|
36
|
+
/** The glyph alone (sub-step rows), its meaning on hover. */
|
|
37
|
+
bare?: boolean;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A plan with no run in an environment: pending while queued, error when it
|
|
41
|
+
* never started, hollow when not part of that run.
|
|
42
|
+
*/
|
|
43
|
+
export declare function EmptyMatrixCell({ outcome, detail, title, bare }: EmptyMatrixCellProps): import("react").JSX.Element;
|
|
44
|
+
export type MatrixLegendProps = {
|
|
45
|
+
static?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export declare function MatrixLegend({ static: isStatic }: MatrixLegendProps): import("react").JSX.Element;
|
|
48
|
+
export type EnvChipProps = {
|
|
49
|
+
context: string;
|
|
50
|
+
className?: string;
|
|
51
|
+
};
|
|
52
|
+
/** A context name, always in the same chip, so an environment never reads as a plan or a status. */
|
|
53
|
+
export declare function EnvChip({ context, className }: EnvChipProps): import("react").JSX.Element;
|
|
54
|
+
export type MatrixEnvHeaderProps = {
|
|
55
|
+
context: string;
|
|
56
|
+
counts: MatrixEnvCounts;
|
|
57
|
+
durationMs: number;
|
|
58
|
+
/** The environment's run is still running: its elapsed time is still counting. */
|
|
59
|
+
running: boolean;
|
|
60
|
+
/** Wraps the chip, e.g. in the host router's link to the member run. Never called when static. */
|
|
61
|
+
renderChip?: (chip: ReactNode) => ReactNode;
|
|
62
|
+
/** Drawn beside the chip, e.g. how often the member was retried. */
|
|
63
|
+
aside?: ReactNode;
|
|
64
|
+
static?: boolean;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* One environment's column head in three fixed rows: its chip, the plans'
|
|
68
|
+
* progress with the elapsed time, and the plan verdict naming its units.
|
|
69
|
+
*/
|
|
70
|
+
export declare function MatrixEnvHeader({ context, counts, durationMs, running, renderChip, aside, static: isStatic }: MatrixEnvHeaderProps): import("react").JSX.Element;
|
|
71
|
+
export type MatrixUnavailableEnvHeaderProps = {
|
|
72
|
+
context: string;
|
|
73
|
+
};
|
|
74
|
+
/** An environment whose member run did not load, in the same three rows as MatrixEnvHeader. */
|
|
75
|
+
export declare function MatrixUnavailableEnvHeader({ context }: MatrixUnavailableEnvHeaderProps): import("react").JSX.Element;
|
|
76
|
+
//# sourceMappingURL=MatrixMarks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MatrixMarks.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/MatrixMarks.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import { UiServer } from "../../icons/components/UiServer.js";
|
|
3
|
+
import { UiClock } from "../../icons/components/UiClock.js";
|
|
4
|
+
import { ProgressBar } from "../ProgressBar.js";
|
|
5
|
+
import { MATRIX_MARKS, MATRIX_ORDER, MATRIX_PLAN_BAR, formatMatrixDuration, matrixBarClass } from "./matrix-marks.js";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
// src/data/test-runner/MatrixMarks.tsx
|
|
8
|
+
function MatrixMark({ mark, label, className, static: isStatic = false }) {
|
|
9
|
+
const token = MATRIX_MARKS[mark];
|
|
10
|
+
const Glyph = token.icon;
|
|
11
|
+
return /* @__PURE__ */ jsx(Glyph, {
|
|
12
|
+
...label === "" ? {} : { title: label ?? token.label },
|
|
13
|
+
className: cn("size-3.5 shrink-0", token.tone, token.spin && !isStatic && "animate-spin", className),
|
|
14
|
+
"data-mark": mark
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/** Outcome counts as glyph + number, in matrix order. */
|
|
18
|
+
function MatrixMix({ counts, unit, always = [], static: isStatic = false }) {
|
|
19
|
+
return /* @__PURE__ */ jsx("span", {
|
|
20
|
+
role: "group",
|
|
21
|
+
"aria-label": `${unit} by outcome`,
|
|
22
|
+
className: "flex shrink-0 items-center gap-2 text-xs tabular-nums",
|
|
23
|
+
"data-mix": unit,
|
|
24
|
+
children: MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0 || always.includes(key)).map((key) => {
|
|
25
|
+
const text = `${counts[key] ?? 0} ${MATRIX_MARKS[key].label} ${unit}`;
|
|
26
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
27
|
+
role: "img",
|
|
28
|
+
"aria-label": text,
|
|
29
|
+
title: text,
|
|
30
|
+
className: cn("inline-flex items-center gap-0.5", MATRIX_MARKS[key].tone),
|
|
31
|
+
"data-mix-outcome": key,
|
|
32
|
+
children: [/* @__PURE__ */ jsx(MatrixMark, {
|
|
33
|
+
mark: key,
|
|
34
|
+
label: "",
|
|
35
|
+
static: isStatic
|
|
36
|
+
}), counts[key] ?? 0]
|
|
37
|
+
}, key);
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function MatrixElapsed({ ms, running, clock = false }) {
|
|
42
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
43
|
+
className: cn("inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-[11px] tabular-nums", running ? "text-blue-700 dark:text-blue-300" : "text-muted-foreground"),
|
|
44
|
+
title: running ? "elapsed so far" : "total duration",
|
|
45
|
+
"data-duration": "",
|
|
46
|
+
children: [clock && /* @__PURE__ */ jsx(UiClock, {
|
|
47
|
+
className: "size-3",
|
|
48
|
+
"aria-hidden": "true"
|
|
49
|
+
}), formatMatrixDuration(ms)]
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A plan with no run in an environment: pending while queued, error when it
|
|
54
|
+
* never started, hollow when not part of that run.
|
|
55
|
+
*/
|
|
56
|
+
function EmptyMatrixCell({ outcome, detail, title, bare = false }) {
|
|
57
|
+
const neverStarted = detail === "never started";
|
|
58
|
+
if (!neverStarted && outcome !== "queued" && outcome !== "absent") throw new Error(`unexpected empty matrix cell ${outcome}: ${title}`);
|
|
59
|
+
const mark = neverStarted ? "error" : outcome === "queued" ? "pending" : "absent";
|
|
60
|
+
const label = neverStarted ? "never started" : MATRIX_MARKS[mark].label;
|
|
61
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
62
|
+
title,
|
|
63
|
+
className: "flex h-5 min-w-0 items-center gap-1.5 text-xs",
|
|
64
|
+
"data-empty-cell": mark,
|
|
65
|
+
children: [/* @__PURE__ */ jsx(MatrixMark, {
|
|
66
|
+
mark,
|
|
67
|
+
label
|
|
68
|
+
}), !bare && /* @__PURE__ */ jsx("span", {
|
|
69
|
+
className: MATRIX_MARKS[mark].tone,
|
|
70
|
+
children: label
|
|
71
|
+
})]
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
function MatrixLegend({ static: isStatic = false }) {
|
|
75
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
76
|
+
className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground",
|
|
77
|
+
"data-batch-legend": "",
|
|
78
|
+
children: [MATRIX_ORDER.map((key) => {
|
|
79
|
+
const token = MATRIX_MARKS[key];
|
|
80
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
81
|
+
className: "inline-flex items-center gap-1",
|
|
82
|
+
"data-legend-mark": key,
|
|
83
|
+
children: [/* @__PURE__ */ jsx(MatrixMark, {
|
|
84
|
+
mark: key,
|
|
85
|
+
label: "",
|
|
86
|
+
static: isStatic
|
|
87
|
+
}), token.legend ?? token.label]
|
|
88
|
+
}, key);
|
|
89
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
90
|
+
className: "inline-flex items-center gap-1",
|
|
91
|
+
children: [/* @__PURE__ */ jsx(UiClock, {
|
|
92
|
+
className: "size-3",
|
|
93
|
+
"aria-hidden": "true"
|
|
94
|
+
}), "elapsed · total duration"]
|
|
95
|
+
})]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** A context name, always in the same chip, so an environment never reads as a plan or a status. */
|
|
99
|
+
function EnvChip({ context, className }) {
|
|
100
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
101
|
+
className: cn("inline-flex items-center gap-1 rounded border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] text-foreground", className),
|
|
102
|
+
"data-env-chip": context,
|
|
103
|
+
children: [/* @__PURE__ */ jsx(UiServer, {
|
|
104
|
+
className: "size-3 text-muted-foreground",
|
|
105
|
+
"aria-hidden": "true"
|
|
106
|
+
}), context]
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
var HEADER_GRID = "grid min-w-48 grid-rows-[auto_auto_auto] gap-1.5";
|
|
110
|
+
var HEADER_ROW = "flex h-5 min-w-0 items-center gap-2";
|
|
111
|
+
/**
|
|
112
|
+
* One environment's column head in three fixed rows: its chip, the plans'
|
|
113
|
+
* progress with the elapsed time, and the plan verdict naming its units.
|
|
114
|
+
*/
|
|
115
|
+
function MatrixEnvHeader({ context, counts, durationMs, running, renderChip, aside, static: isStatic = false }) {
|
|
116
|
+
const done = counts.passed + counts.warned + counts.failed + counts.error;
|
|
117
|
+
const chip = /* @__PURE__ */ jsx(EnvChip, { context });
|
|
118
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
119
|
+
className: HEADER_GRID,
|
|
120
|
+
"data-env-header": context,
|
|
121
|
+
children: [
|
|
122
|
+
/* @__PURE__ */ jsxs("div", {
|
|
123
|
+
className: HEADER_ROW,
|
|
124
|
+
children: [renderChip && !isStatic ? renderChip(chip) : chip, aside]
|
|
125
|
+
}),
|
|
126
|
+
/* @__PURE__ */ jsxs("div", {
|
|
127
|
+
className: HEADER_ROW,
|
|
128
|
+
title: `${done} of ${counts.total} plans finished`,
|
|
129
|
+
"data-env-progress": "",
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ jsx("span", {
|
|
132
|
+
className: "min-w-0 flex-1",
|
|
133
|
+
children: /* @__PURE__ */ jsx(ProgressBar, {
|
|
134
|
+
total: counts.total,
|
|
135
|
+
height: "h-1.5",
|
|
136
|
+
segments: MATRIX_PLAN_BAR.map((key) => ({
|
|
137
|
+
count: counts[key],
|
|
138
|
+
color: matrixBarClass(key, isStatic),
|
|
139
|
+
label: `${MATRIX_MARKS[key].label} plans`
|
|
140
|
+
}))
|
|
141
|
+
})
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ jsxs("span", {
|
|
144
|
+
className: "shrink-0 whitespace-nowrap text-[11px] tabular-nums text-foreground",
|
|
145
|
+
children: [
|
|
146
|
+
done,
|
|
147
|
+
"/",
|
|
148
|
+
counts.total,
|
|
149
|
+
" plans"
|
|
150
|
+
]
|
|
151
|
+
}),
|
|
152
|
+
/* @__PURE__ */ jsx(MatrixElapsed, {
|
|
153
|
+
ms: durationMs,
|
|
154
|
+
running,
|
|
155
|
+
clock: true
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ jsx("div", {
|
|
160
|
+
className: HEADER_ROW,
|
|
161
|
+
"data-env-verdict": "",
|
|
162
|
+
children: /* @__PURE__ */ jsx(MatrixMix, {
|
|
163
|
+
counts: {
|
|
164
|
+
passed: counts.passed,
|
|
165
|
+
warned: counts.warned,
|
|
166
|
+
running: counts.running,
|
|
167
|
+
pending: counts.queued,
|
|
168
|
+
failed: counts.failed,
|
|
169
|
+
error: counts.error
|
|
170
|
+
},
|
|
171
|
+
unit: "plans",
|
|
172
|
+
always: ["failed"],
|
|
173
|
+
static: isStatic
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** An environment whose member run did not load, in the same three rows as MatrixEnvHeader. */
|
|
180
|
+
function MatrixUnavailableEnvHeader({ context }) {
|
|
181
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
182
|
+
className: HEADER_GRID,
|
|
183
|
+
"data-env-header": context,
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsx("div", {
|
|
186
|
+
className: HEADER_ROW,
|
|
187
|
+
children: /* @__PURE__ */ jsx(EnvChip, { context })
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ jsxs("div", {
|
|
190
|
+
className: `${HEADER_ROW} text-[11px] font-medium ${MATRIX_MARKS.error.tone}`,
|
|
191
|
+
children: [/* @__PURE__ */ jsx(MatrixMark, {
|
|
192
|
+
mark: "error",
|
|
193
|
+
label: ""
|
|
194
|
+
}), "did not load"]
|
|
195
|
+
}),
|
|
196
|
+
/* @__PURE__ */ jsx("div", {
|
|
197
|
+
className: HEADER_ROW,
|
|
198
|
+
"aria-hidden": "true"
|
|
199
|
+
})
|
|
200
|
+
]
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
export { EmptyMatrixCell, EnvChip, MatrixElapsed, MatrixEnvHeader, MatrixLegend, MatrixMark, MatrixMix, MatrixUnavailableEnvHeader };
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=MatrixMarks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MatrixMarks.js","names":[],"sources":["../../../src/data/test-runner/MatrixMarks.tsx"],"sourcesContent":["// Renders the matrix-marks.ts vocabulary: one outcome's glyph, outcome mixes,\n// durations, empty cells, the legend, environment chips and the environment\n// column header. Props-only, so a live web matrix and a static print template\n// draw the same marks; `static` drops animation and links for print.\n\nimport type { ReactNode } from \"react\";\nimport { UiClock, UiServer } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport { ProgressBar } from \"../ProgressBar\";\nimport {\n MATRIX_MARKS,\n MATRIX_ORDER,\n MATRIX_PLAN_BAR,\n formatMatrixDuration,\n matrixBarClass,\n type MatrixCounts,\n type MatrixEnvCounts,\n type MatrixMarkKey,\n type MatrixMarkToken,\n} from \"./matrix-marks\";\n\nexport type MatrixMarkProps = {\n mark: MatrixMarkKey;\n /** Hover title; \"\" leaves the glyph untitled where the surrounding element already names it. */\n label?: string;\n className?: string;\n /** Print: never spins. */\n static?: boolean;\n};\n\nexport function MatrixMark({ mark, label, className, static: isStatic = false }: MatrixMarkProps) {\n const token: MatrixMarkToken = MATRIX_MARKS[mark];\n const Glyph = token.icon;\n return (\n <Glyph\n {...(label === \"\" ? {} : { title: label ?? token.label })}\n className={cn(\"size-3.5 shrink-0\", token.tone, token.spin && !isStatic && \"animate-spin\", className)}\n data-mark={mark}\n />\n );\n}\n\nexport type MatrixMixProps = {\n counts: MatrixCounts;\n unit: \"steps\" | \"plans\";\n /** Outcomes drawn even at zero; every other zero count is dropped. */\n always?: readonly MatrixMarkKey[];\n static?: boolean;\n};\n\n/** Outcome counts as glyph + number, in matrix order. */\nexport function MatrixMix({ counts, unit, always = [], static: isStatic = false }: MatrixMixProps) {\n return (\n <span role=\"group\" aria-label={`${unit} by outcome`} className=\"flex shrink-0 items-center gap-2 text-xs tabular-nums\" data-mix={unit}>\n {MATRIX_ORDER.filter((key) => (counts[key] ?? 0) > 0 || always.includes(key)).map((key) => {\n const text = `${counts[key] ?? 0} ${MATRIX_MARKS[key].label} ${unit}`;\n return (\n <span key={key} role=\"img\" aria-label={text} title={text} className={cn(\"inline-flex items-center gap-0.5\", MATRIX_MARKS[key].tone)} data-mix-outcome={key}>\n <MatrixMark mark={key} label=\"\" static={isStatic} />\n {counts[key] ?? 0}\n </span>\n );\n })}\n </span>\n );\n}\n\nexport type MatrixElapsedProps = {\n ms: number;\n /** Still counting: drawn blue and titled \"elapsed so far\". */\n running: boolean;\n /** Prefix a clock glyph. */\n clock?: boolean;\n};\n\nexport function MatrixElapsed({ ms, running, clock = false }: MatrixElapsedProps) {\n return (\n <span\n className={cn(\"inline-flex shrink-0 items-center gap-1 whitespace-nowrap text-[11px] tabular-nums\", running ? \"text-blue-700 dark:text-blue-300\" : \"text-muted-foreground\")}\n title={running ? \"elapsed so far\" : \"total duration\"}\n data-duration=\"\"\n >\n {clock && <UiClock className=\"size-3\" aria-hidden=\"true\" />}\n {formatMatrixDuration(ms)}\n </span>\n );\n}\n\nexport type EmptyMatrixCellProps = {\n /** The cell outcome: \"queued\", \"absent\", or \"error\" with detail \"never started\". Anything else throws. */\n outcome: string;\n /** Why a cell has no run: \"never started\". */\n detail?: string;\n /** Hover title of the cell. */\n title: string;\n /** The glyph alone (sub-step rows), its meaning on hover. */\n bare?: boolean;\n};\n\n/**\n * A plan with no run in an environment: pending while queued, error when it\n * never started, hollow when not part of that run.\n */\nexport function EmptyMatrixCell({ outcome, detail, title, bare = false }: EmptyMatrixCellProps) {\n const neverStarted = detail === \"never started\";\n if (!neverStarted && outcome !== \"queued\" && outcome !== \"absent\") throw new Error(`unexpected empty matrix cell ${outcome}: ${title}`);\n const mark: MatrixMarkKey = neverStarted ? \"error\" : outcome === \"queued\" ? \"pending\" : \"absent\";\n const label = neverStarted ? \"never started\" : MATRIX_MARKS[mark].label;\n return (\n <span title={title} className=\"flex h-5 min-w-0 items-center gap-1.5 text-xs\" data-empty-cell={mark}>\n <MatrixMark mark={mark} label={label} />\n {!bare && <span className={MATRIX_MARKS[mark].tone}>{label}</span>}\n </span>\n );\n}\n\nexport type MatrixLegendProps = { static?: boolean };\n\nexport function MatrixLegend({ static: isStatic = false }: MatrixLegendProps) {\n return (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground\" data-batch-legend=\"\">\n {MATRIX_ORDER.map((key) => {\n const token: MatrixMarkToken = MATRIX_MARKS[key];\n return (\n <span key={key} className=\"inline-flex items-center gap-1\" data-legend-mark={key}>\n <MatrixMark mark={key} label=\"\" static={isStatic} />\n {token.legend ?? token.label}\n </span>\n );\n })}\n <span className=\"inline-flex items-center gap-1\">\n <UiClock className=\"size-3\" aria-hidden=\"true\" />\n elapsed · total duration\n </span>\n </div>\n );\n}\n\nexport type EnvChipProps = { context: string; className?: string };\n\n/** A context name, always in the same chip, so an environment never reads as a plan or a status. */\nexport function EnvChip({ context, className }: EnvChipProps) {\n return (\n <span\n className={cn(\"inline-flex items-center gap-1 rounded border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] text-foreground\", className)}\n data-env-chip={context}\n >\n <UiServer className=\"size-3 text-muted-foreground\" aria-hidden=\"true\" />\n {context}\n </span>\n );\n}\n\nconst HEADER_GRID = \"grid min-w-48 grid-rows-[auto_auto_auto] gap-1.5\";\nconst HEADER_ROW = \"flex h-5 min-w-0 items-center gap-2\";\n\nexport type MatrixEnvHeaderProps = {\n context: string;\n counts: MatrixEnvCounts;\n durationMs: number;\n /** The environment's run is still running: its elapsed time is still counting. */\n running: boolean;\n /** Wraps the chip, e.g. in the host router's link to the member run. Never called when static. */\n renderChip?: (chip: ReactNode) => ReactNode;\n /** Drawn beside the chip, e.g. how often the member was retried. */\n aside?: ReactNode;\n static?: boolean;\n};\n\n/**\n * One environment's column head in three fixed rows: its chip, the plans'\n * progress with the elapsed time, and the plan verdict naming its units.\n */\nexport function MatrixEnvHeader({ context, counts, durationMs, running, renderChip, aside, static: isStatic = false }: MatrixEnvHeaderProps) {\n const done = counts.passed + counts.warned + counts.failed + counts.error;\n const chip = <EnvChip context={context} />;\n return (\n <div className={HEADER_GRID} data-env-header={context}>\n <div className={HEADER_ROW}>\n {renderChip && !isStatic ? renderChip(chip) : chip}\n {aside}\n </div>\n <div className={HEADER_ROW} title={`${done} of ${counts.total} plans finished`} data-env-progress=\"\">\n <span className=\"min-w-0 flex-1\">\n <ProgressBar\n total={counts.total}\n height=\"h-1.5\"\n segments={MATRIX_PLAN_BAR.map((key) => ({ count: counts[key], color: matrixBarClass(key, isStatic), label: `${MATRIX_MARKS[key].label} plans` }))}\n />\n </span>\n <span className=\"shrink-0 whitespace-nowrap text-[11px] tabular-nums text-foreground\">\n {done}/{counts.total} plans\n </span>\n <MatrixElapsed ms={durationMs} running={running} clock />\n </div>\n <div className={HEADER_ROW} data-env-verdict=\"\">\n <MatrixMix\n counts={{ passed: counts.passed, warned: counts.warned, running: counts.running, pending: counts.queued, failed: counts.failed, error: counts.error }}\n unit=\"plans\"\n always={[\"failed\"]}\n static={isStatic}\n />\n </div>\n </div>\n );\n}\n\nexport type MatrixUnavailableEnvHeaderProps = { context: string };\n\n/** An environment whose member run did not load, in the same three rows as MatrixEnvHeader. */\nexport function MatrixUnavailableEnvHeader({ context }: MatrixUnavailableEnvHeaderProps) {\n return (\n <div className={HEADER_GRID} data-env-header={context}>\n <div className={HEADER_ROW}>\n <EnvChip context={context} />\n </div>\n <div className={`${HEADER_ROW} text-[11px] font-medium ${MATRIX_MARKS.error.tone}`}>\n <MatrixMark mark=\"error\" label=\"\" />\n did not load\n </div>\n <div className={HEADER_ROW} aria-hidden=\"true\" />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ const require_UiJson = require("../../icons/components/UiJson.cjs");
|
|
|
7
7
|
const require_UiCopy = require("../../icons/components/UiCopy.cjs");
|
|
8
8
|
const require_UiRefresh = require("../../icons/components/UiRefresh.cjs");
|
|
9
9
|
const require_UiClock = require("../../icons/components/UiClock.cjs");
|
|
10
|
+
const require_UiHourglass = require("../../icons/components/UiHourglass.cjs");
|
|
10
11
|
const require_Icon = require("../Icon.cjs");
|
|
11
12
|
const require_button = require("../../components/button.cjs");
|
|
12
13
|
const require_clipboard = require("../../components/clipboard.cjs");
|
|
@@ -79,11 +80,13 @@ function copyMenuItem(item, copy) {
|
|
|
79
80
|
}
|
|
80
81
|
var COPY_LABEL = {
|
|
81
82
|
idle: "Copy details",
|
|
83
|
+
pending: "Copying…",
|
|
82
84
|
copied: "Copied",
|
|
83
85
|
error: "Copy failed"
|
|
84
86
|
};
|
|
85
87
|
var COPY_ICON = {
|
|
86
88
|
idle: require_UiCopy.UiCopy,
|
|
89
|
+
pending: require_UiHourglass.UiHourglass,
|
|
87
90
|
copied: require_UiCheck.UiCheck,
|
|
88
91
|
error: require_UiWarningCircle.UiWarningCircle
|
|
89
92
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestDetailPanel.cjs","names":["useTestRunner","sum","TestFailureDetail","TestOutput","TestAttemptDetail","JsonView","UiCopy","UiCheck","UiWarningCircle","useCopyFlash","useMemo","UiJson","buildNodeJson","SplitButton","buildNodeBrief","outcomeFor","OutcomeIcon","Icon","UiClass","ToolIcon","toolKeyFor","UiClock","formatTestDuration","Button","UiRefresh","UiStop","useState","Select","DetailTabSelectProvider","cn","TabButton"],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TestDetailPanel.cjs","names":["useTestRunner","sum","TestFailureDetail","TestOutput","TestAttemptDetail","JsonView","UiCopy","UiHourglass","UiCheck","UiWarningCircle","useCopyFlash","useMemo","UiJson","buildNodeJson","SplitButton","buildNodeBrief","outcomeFor","OutcomeIcon","Icon","UiClass","ToolIcon","toolKeyFor","UiClock","formatTestDuration","Button","UiRefresh","UiStop","useState","Select","DetailTabSelectProvider","cn","TabButton"],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash, type CopyFlashState } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiHourglass,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = {\n idle: \"Copy details\",\n pending: \"Copying…\",\n copied: \"Copied\",\n error: \"Copy failed\",\n} as const satisfies Record<CopyFlashState, string>;\nconst COPY_ICON = {\n idle: UiCopy,\n pending: UiHourglass,\n copied: UiCheck,\n error: UiWarningCircle,\n} as const satisfies Record<CopyFlashState, unknown>;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestDetailPanel.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TestDetailPanel.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"names":[],"mappings":"AA6LA;;;;GAIG;AACH,wBAAgB,eAAe,gCAwF9B"}
|
|
@@ -7,6 +7,7 @@ import { UiJson } from "../../icons/components/UiJson.js";
|
|
|
7
7
|
import { UiCopy } from "../../icons/components/UiCopy.js";
|
|
8
8
|
import { UiRefresh } from "../../icons/components/UiRefresh.js";
|
|
9
9
|
import { UiClock } from "../../icons/components/UiClock.js";
|
|
10
|
+
import { UiHourglass } from "../../icons/components/UiHourglass.js";
|
|
10
11
|
import { Icon } from "../Icon.js";
|
|
11
12
|
import { Button } from "../../components/button.js";
|
|
12
13
|
import { useCopyFlash } from "../../components/clipboard.js";
|
|
@@ -79,11 +80,13 @@ function copyMenuItem(item, copy) {
|
|
|
79
80
|
}
|
|
80
81
|
var COPY_LABEL = {
|
|
81
82
|
idle: "Copy details",
|
|
83
|
+
pending: "Copying…",
|
|
82
84
|
copied: "Copied",
|
|
83
85
|
error: "Copy failed"
|
|
84
86
|
};
|
|
85
87
|
var COPY_ICON = {
|
|
86
88
|
idle: UiCopy,
|
|
89
|
+
pending: UiHourglass,
|
|
87
90
|
copied: UiCheck,
|
|
88
91
|
error: UiWarningCircle
|
|
89
92
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestDetailPanel.js","names":[],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = { idle: \"Copy details\", copied: \"Copied\", error: \"Copy failed\" } as const;\nconst COPY_ICON = { idle: UiCopy, copied: UiCheck, error: UiWarningCircle } as const;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TestDetailPanel.js","names":[],"sources":["../../../src/data/test-runner/TestDetailPanel.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { Button } from \"../../components/button\";\nimport { Select } from \"../../components/select\";\nimport { SplitButton } from \"../../components/SplitButton\";\nimport { useCopyFlash, type CopyFlashState } from \"../../components/clipboard\";\nimport type { DropdownMenuItem } from \"../../overlay/DropdownMenu\";\nimport { cn } from \"../../lib/utils\";\nimport { JsonView } from \"../JsonView\";\nimport { TabButton } from \"../TabButton\";\nimport {\n UiCheck,\n UiClass,\n UiClock,\n UiCopy,\n UiHourglass,\n UiJson,\n UiRefresh,\n UiStop,\n UiWarningCircle,\n} from \"../../icons\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { DetailTabSelectProvider } from \"./detailTabContext\";\nimport { buildNodeBrief, buildNodeJson, type CopyItem } from \"./copy\";\nimport { formatTestDuration, outcomeFor, sum } from \"./status\";\nimport { toolKeyFor } from \"./marks\";\nimport { OutcomeIcon, ToolIcon } from \"./MarkIcons\";\nimport { TestFailureDetail, TestOutput } from \"./TestFailureDetail\";\nimport { TestAttemptDetail } from \"./TestAttemptDetail\";\n\n/** Built-in detail body — used when no adapter contributes `renderDetail`. */\nfunction DefaultDetailBody({ node }: { node: Test }) {\n const runner = useTestRunner();\n const attempt = node.attempts?.at(-1);\n const hasChildren = (node.children?.length ?? 0) > 0;\n const counts = hasChildren ? sum(node) : null;\n return (\n <div className=\"space-y-4 p-density-4\">\n {node.message && !node.failure_detail && (\n <p className=\"whitespace-pre-wrap break-words text-sm\">{node.message}</p>\n )}\n {node.failure_detail && <TestFailureDetail failure={node.failure_detail} />}\n <TestOutput node={node} />\n {attempt && (\n <TestAttemptDetail\n key={`${attempt.run_kind ?? \"\"}:${attempt.sequence}`}\n attempt={attempt}\n {...(runner.fetchProcessMetrics ? { fetchProcessMetrics: runner.fetchProcessMetrics } : {})}\n />\n )}\n {counts && counts.total > 0 && (\n <div className=\"flex flex-wrap gap-3 text-xs text-muted-foreground\">\n <span>{counts.total} total</span>\n {counts.passed > 0 && <span>{counts.passed} passed</span>}\n {counts.failed > 0 && <span>{counts.failed} failed</span>}\n {counts.skipped > 0 && <span>{counts.skipped} skipped</span>}\n </div>\n )}\n {node.detail != null && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Detail</div>\n <JsonView data={node.detail} />\n </div>\n )}\n </div>\n );\n}\n\nfunction copyMenuItem(item: CopyItem, copy: (text: string) => void): DropdownMenuItem {\n const icon = item.icon ? { icon: item.icon } : {};\n if (\"reason\" in item) {\n return { label: item.label, ...icon, disabled: true, title: item.reason, onSelect: () => {} };\n }\n return { label: item.label, ...icon, onSelect: () => copy(item.value) };\n}\n\nconst COPY_LABEL = {\n idle: \"Copy details\",\n pending: \"Copying…\",\n copied: \"Copied\",\n error: \"Copy failed\",\n} as const satisfies Record<CopyFlashState, string>;\nconst COPY_ICON = {\n idle: UiCopy,\n pending: UiHourglass,\n copied: UiCheck,\n error: UiWarningCircle,\n} as const satisfies Record<CopyFlashState, unknown>;\n\n/**\n * \"Copy details\" split button: the primary action copies a Markdown brief of\n * the generic sections plus every matching adapter's `copy` sections; the menu\n * offers the node as JSON and the adapters' items, disabled with a reason when\n * they have nothing to copy. The label flips to \"Copied\" or \"Copy failed\".\n */\nfunction CopyDetailsButton({ node }: { node: Test }) {\n const runner = useTestRunner();\n const { state, copy } = useCopyFlash();\n const sections = useMemo(() => runner.adapters.resolveCopy(node, runner), [node, runner]);\n const items: DropdownMenuItem[] = [\n { label: \"Copy as JSON\", icon: UiJson, onSelect: () => copy(buildNodeJson(node)) },\n ...sections.items.map((item) => copyMenuItem(item, copy)),\n ];\n return (\n <SplitButton\n label={COPY_LABEL[state]}\n icon={COPY_ICON[state]}\n variant=\"outline\"\n size=\"sm\"\n align=\"right\"\n title=\"More copy options\"\n onClick={() => copy(buildNodeBrief(node, sections))}\n items={items}\n />\n );\n}\n\nfunction DetailHeader({ node, actionNode, actions, attemptChooser }: { node: Test; actionNode: Test; actions?: ReactNode; attemptChooser?: ReactNode }) {\n const runner = useTestRunner();\n const outcome = outcomeFor(node);\n const canRerun = !!runner.onRerun && actionNode.framework !== \"task\";\n const canStop = !!runner.onStop && !!actionNode.can_stop && !!actionNode.task_id;\n const location = node.file ? `${node.file}${node.line ? `:${node.line}` : \"\"}` : undefined;\n return (\n <div\n className=\"flex min-w-0 items-center gap-2 border-b border-border px-density-4 py-density-2\"\n data-detail-header=\"\"\n >\n {outcome ? (\n <OutcomeIcon outcome={outcome} className=\"text-lg\" />\n ) : (\n <Icon icon={UiClass} className=\"shrink-0 text-lg text-muted-foreground\" />\n )}\n <h2 className=\"min-w-0 shrink truncate text-base font-semibold\" title={node.name}>\n {node.name}\n </h2>\n {node.framework && (\n <span className=\"inline-flex shrink-0 items-center gap-1 rounded bg-muted px-1.5 py-0.5 text-xs text-muted-foreground\">\n <ToolIcon tool={toolKeyFor(node)} className=\"text-sm\" />\n {node.framework}\n </span>\n )}\n {node.duration ? (\n <span className=\"inline-flex shrink-0 items-center gap-1 text-xs text-muted-foreground\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {formatTestDuration(node.duration)}\n </span>\n ) : null}\n {location && (\n <span\n className=\"min-w-0 shrink-[100] truncate font-mono text-xs text-muted-foreground\"\n title={location}\n >\n {location}\n </span>\n )}\n <div className=\"ml-auto flex shrink-0 items-center gap-1\">\n {attemptChooser}\n {actions}\n <CopyDetailsButton node={node} />\n {canRerun && (\n <Button\n size=\"sm\"\n disabled={runner.busy.rerun}\n onClick={() => runner.onRerun!(actionNode)}\n title=\"Rerun this test or subtree\"\n >\n <Icon icon={UiRefresh} className=\"mr-1 text-sm\" />\n {runner.busy.rerun ? \"Running...\" : \"Rerun\"}\n </Button>\n )}\n {canStop && (\n <Button\n size=\"sm\"\n variant=\"destructive\"\n disabled={runner.busy.stop}\n onClick={() => runner.onStop!(actionNode)}\n title=\"Stop this running task\"\n >\n <Icon icon={UiStop} className=\"mr-1 text-sm\" />\n {runner.busy.stop ? \"Stopping...\" : \"Stop\"}\n </Button>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The right pane. Resolves the node's adapter for body / tabs / header actions,\n * falling back to the built-in default body. Shows an empty state when nothing\n * is selected.\n */\nexport function TestDetailPanel() {\n const runner = useTestRunner();\n const selected = runner.selected;\n const [attemptSelection, setAttemptSelection] = useState<{ key: string; id: number } | null>(null);\n // Tab state is controlled when the host supplies activeTab (e.g. to mirror it\n // into the URL); otherwise the panel owns it. The render below tolerates an\n // unknown id by falling back to the built-in \"detail\" tab.\n const controlled = runner.activeTab !== undefined;\n const [internalTab, setInternalTab] = useState<string>(\"detail\");\n const activeTab = controlled ? runner.activeTab! : internalTab;\n const setActiveTab = (id: string) => {\n if (controlled) runner.onTabChange?.(id);\n else setInternalTab(id);\n };\n\n if (!selected) {\n return (\n <div className=\"flex h-full items-center justify-center text-sm text-muted-foreground\">\n <div className=\"text-center\">\n <Icon icon={UiClass} className=\"mb-2 block text-4xl\" />\n Select a test to view its details.\n </div>\n </div>\n );\n }\n\n const attempts = runner.adapters.resolveAttempts(selected, runner);\n const key = selected.task_id ?? selected.name;\n const attemptId = runner.activeAttempt ?? (attemptSelection?.key === key ? attemptSelection.id : undefined);\n const attempt = attempts.find((a) => a.id === attemptId) ?? attempts.at(-1);\n const node = attempts.length > 1 && attempt ? attempt.node : selected;\n const attemptChooser = attempts.length > 1 && attempt ? (\n <div className=\"w-44 shrink-0\">\n <Select aria-label=\"Attempt\" value={String(attempt.id)} className=\"h-7 text-xs\" onChange={(event) => {\n const id = Number(event.target.value);\n if (runner.onAttemptChange) runner.onAttemptChange(id);\n else setAttemptSelection({ key, id });\n }} options={attempts.map((a) => ({ value: String(a.id), label: `Attempt ${a.id}/${attempts.length} · ${a.status}${a.durationMs !== undefined ? ` · ${formatTestDuration(a.durationMs * 1e6)}` : \"\"}` }))} />\n </div>\n ) : null;\n\n const detailAdapter = runner.adapters.resolveDetail(node, runner);\n const actionAdapter = runner.adapters.resolveActions(selected, runner);\n const tabs = runner.adapters.resolveTabs(node, runner);\n const ownsScroll = runner.adapters.ownsScroll(node, runner);\n\n const body = (\n <DetailTabSelectProvider value={setActiveTab}>\n {detailAdapter?.renderDetail\n ? detailAdapter.renderDetail({ node, runner })\n : <DefaultDetailBody node={node} />}\n </DetailTabSelectProvider>\n );\n const actions = actionAdapter?.nodeActions?.({ node: selected, runner });\n\n // A size container, so log blocks can cap themselves at a share of the pane (cqh).\n const detailRegion = cn(\"min-h-0 flex-1 [container-type:size]\", !ownsScroll && \"overflow-y-auto\");\n\n // No extra tabs → just the body under the header.\n if (tabs.length === 0) {\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className={detailRegion}>{body}</div>\n </div>\n );\n }\n\n const detailTab = { id: \"detail\", label: \"Detail\", render: () => body };\n const allTabs = [detailTab, ...tabs];\n const active = allTabs.find((t) => t.id === activeTab) ?? detailTab;\n\n return (\n <div className=\"flex h-full min-h-0 flex-col\">\n <DetailHeader node={node} actionNode={selected} actions={actions} attemptChooser={attemptChooser} />\n <div className=\"flex shrink-0 items-center gap-1 border-b border-border px-density-2 py-density-1\">\n {allTabs.map((t) => (\n <TabButton\n key={t.id}\n active={active.id === t.id}\n onClick={() => setActiveTab(t.id)}\n label={t.label}\n />\n ))}\n </div>\n <div className={detailRegion}>{active.render()}</div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,8 @@ export { genericCopySections, mergeCopySections, buildBrief, buildNodeBrief, bui
|
|
|
17
17
|
export { type StatusCounts, type TestStatus, sum, sumNonTaskTests, aggregateStatusCounts, STATUS_LABELS, hasFailed, hasPending, testStatus, totalDuration, outcomeFor, type TestOutcome, statusToneFor, formatTestDuration, formatCount, humanizeName, collectFrameworks, collapseSingleChildChains, filterTests, } from './status.js';
|
|
18
18
|
export { TEST_OUTCOMES, LINT_SEVERITIES, TEST_TOOLS, toolMark, toolKeyFor, type ResultMark, type LintSeverity, type ToolKind, type ToolMark, } from './marks.js';
|
|
19
19
|
export { OutcomeIcon, LintSeverityIcon, ToolIcon } from './MarkIcons.js';
|
|
20
|
+
export { MATRIX_MARKS, MATRIX_ORDER, MATRIX_PLAN_BAR, matrixMarkKey, repeatsMark, matrixBarClass, formatMatrixDuration, type MatrixMarkToken, type MatrixMarkKey, type MatrixCounts, type MatrixEnvCounts, } from './matrix-marks.js';
|
|
21
|
+
export { MatrixMark, MatrixMix, MatrixElapsed, EmptyMatrixCell, MatrixLegend, EnvChip, MatrixEnvHeader, MatrixUnavailableEnvHeader, type MatrixMarkProps, type MatrixMixProps, type MatrixElapsedProps, type EmptyMatrixCellProps, type MatrixLegendProps, type EnvChipProps, type MatrixEnvHeaderProps, type MatrixUnavailableEnvHeaderProps, } from './MatrixMarks.js';
|
|
20
22
|
export { slugify, annotateRoutePaths, findNodeByRoutePath } from './routePath.js';
|
|
21
23
|
export { type FilterMode, type FilterState, type TestFilters, emptyTestFilters, decodeFilterState, encodeFilterState, cycleFilterState, matchesFilterState, filterStateToRecord, recordToFilterState, } from './filterState.js';
|
|
22
24
|
export type { Test, TestSummary, TestAttempt, FailureDetail, FailureKind, GoTestContext, GinkgoContext, FixtureContext, TaskContext, RunMeta, Snapshot, SnapshotGit, SnapshotStatus, } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACL,oBAAoB,EACpB,KAAK,mBAAmB,EACxB,KAAK,cAAc,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEjF,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,cAAc,EACd,aAAa,EACb,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,YAAY,GAClB,MAAM,QAAQ,CAAC;AAEhB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,GAAG,EACH,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,UAAU,EACV,UAAU,EACV,aAAa,EACb,UAAU,EACV,KAAK,WAAW,EAChB,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,yBAAyB,EACzB,WAAW,GACZ,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,EACV,QAAQ,EACR,UAAU,EACV,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,QAAQ,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE/E,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,IAAI,EACJ,WAAW,EACX,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACX,OAAO,EACP,QAAQ,EACR,WAAW,EACX,cAAc,GACf,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/index.ts"],"names":[],"mappings":"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"}
|