@flanksource/clicky-ui 0.2.12 → 0.2.13
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/Combobox.cjs +47 -33
- package/dist/components/Combobox.cjs.map +1 -1
- package/dist/components/Combobox.d.ts +12 -0
- package/dist/components/Combobox.d.ts.map +1 -1
- package/dist/components/Combobox.js +48 -34
- package/dist/components/Combobox.js.map +1 -1
- package/dist/components/IconButton.cjs +26 -0
- package/dist/components/IconButton.cjs.map +1 -0
- package/dist/components/IconButton.d.ts +25 -0
- package/dist/components/IconButton.d.ts.map +1 -0
- package/dist/components/IconButton.js +26 -0
- package/dist/components/IconButton.js.map +1 -0
- package/dist/components/SecretKeySelector.cjs +151 -0
- package/dist/components/SecretKeySelector.cjs.map +1 -0
- package/dist/components/SecretKeySelector.d.ts +47 -0
- package/dist/components/SecretKeySelector.d.ts.map +1 -0
- package/dist/components/SecretKeySelector.js +151 -0
- package/dist/components/SecretKeySelector.js.map +1 -0
- package/dist/components/WorkloadPicker.cjs +144 -0
- package/dist/components/WorkloadPicker.cjs.map +1 -0
- package/dist/components/WorkloadPicker.d.ts +52 -0
- package/dist/components/WorkloadPicker.d.ts.map +1 -0
- package/dist/components/WorkloadPicker.js +144 -0
- package/dist/components/WorkloadPicker.js.map +1 -0
- package/dist/components/icon-menu-picker.cjs +83 -112
- package/dist/components/icon-menu-picker.cjs.map +1 -1
- package/dist/components/icon-menu-picker.d.ts.map +1 -1
- package/dist/components/icon-menu-picker.js +85 -114
- package/dist/components/icon-menu-picker.js.map +1 -1
- package/dist/components.cjs +10 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.d.ts.map +1 -1
- package/dist/components.js +11 -1
- package/dist/components.js.map +1 -1
- package/dist/data/Clicky.cjs +1 -1
- package/dist/data/Clicky.cjs.map +1 -1
- package/dist/data/Clicky.js +1 -1
- package/dist/data/Clicky.js.map +1 -1
- package/dist/data/Version.cjs +42 -0
- package/dist/data/Version.cjs.map +1 -0
- package/dist/data/Version.d.ts +36 -0
- package/dist/data/Version.d.ts.map +1 -0
- package/dist/data/Version.js +42 -0
- package/dist/data/Version.js.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.cjs +165 -0
- package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.d.ts +7 -0
- package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -0
- package/dist/data/test-runner/TestDetailPanel.js +165 -0
- package/dist/data/test-runner/TestDetailPanel.js.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.cjs +78 -0
- package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.d.ts +14 -0
- package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -0
- package/dist/data/test-runner/TestFailureDetail.js +78 -0
- package/dist/data/test-runner/TestFailureDetail.js.map +1 -0
- package/dist/data/test-runner/TestFilterBar.cjs +108 -0
- package/dist/data/test-runner/TestFilterBar.cjs.map +1 -0
- package/dist/data/test-runner/TestFilterBar.d.ts +15 -0
- package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -0
- package/dist/data/test-runner/TestFilterBar.js +108 -0
- package/dist/data/test-runner/TestFilterBar.js.map +1 -0
- package/dist/data/test-runner/TestRunSummary.cjs +81 -0
- package/dist/data/test-runner/TestRunSummary.cjs.map +1 -0
- package/dist/data/test-runner/TestRunSummary.d.ts +19 -0
- package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunSummary.js +81 -0
- package/dist/data/test-runner/TestRunSummary.js.map +1 -0
- package/dist/data/test-runner/TestRunner.cjs +143 -0
- package/dist/data/test-runner/TestRunner.cjs.map +1 -0
- package/dist/data/test-runner/TestRunner.d.ts +42 -0
- package/dist/data/test-runner/TestRunner.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunner.fixtures.d.ts +26 -0
- package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -0
- package/dist/data/test-runner/TestRunner.js +143 -0
- package/dist/data/test-runner/TestRunner.js.map +1 -0
- package/dist/data/test-runner/TestTree.cjs +42 -0
- package/dist/data/test-runner/TestTree.cjs.map +1 -0
- package/dist/data/test-runner/TestTree.d.ts +9 -0
- package/dist/data/test-runner/TestTree.d.ts.map +1 -0
- package/dist/data/test-runner/TestTree.js +42 -0
- package/dist/data/test-runner/TestTree.js.map +1 -0
- package/dist/data/test-runner/TestTreeNode.cjs +99 -0
- package/dist/data/test-runner/TestTreeNode.cjs.map +1 -0
- package/dist/data/test-runner/TestTreeNode.d.ts +12 -0
- package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -0
- package/dist/data/test-runner/TestTreeNode.js +99 -0
- package/dist/data/test-runner/TestTreeNode.js.map +1 -0
- package/dist/data/test-runner/adapter.cjs +57 -0
- package/dist/data/test-runner/adapter.cjs.map +1 -0
- package/dist/data/test-runner/adapter.d.ts +60 -0
- package/dist/data/test-runner/adapter.d.ts.map +1 -0
- package/dist/data/test-runner/adapter.js +57 -0
- package/dist/data/test-runner/adapter.js.map +1 -0
- package/dist/data/test-runner/context.cjs +15 -0
- package/dist/data/test-runner/context.cjs.map +1 -0
- package/dist/data/test-runner/context.d.ts +40 -0
- package/dist/data/test-runner/context.d.ts.map +1 -0
- package/dist/data/test-runner/context.js +15 -0
- package/dist/data/test-runner/context.js.map +1 -0
- package/dist/data/test-runner/filterState.cjs +47 -0
- package/dist/data/test-runner/filterState.cjs.map +1 -0
- package/dist/data/test-runner/filterState.d.ts +14 -0
- package/dist/data/test-runner/filterState.d.ts.map +1 -0
- package/dist/data/test-runner/filterState.js +47 -0
- package/dist/data/test-runner/filterState.js.map +1 -0
- package/dist/data/test-runner/frameworkIcon.cjs +27 -0
- package/dist/data/test-runner/frameworkIcon.cjs.map +1 -0
- package/dist/data/test-runner/frameworkIcon.d.ts +3 -0
- package/dist/data/test-runner/frameworkIcon.d.ts.map +1 -0
- package/dist/data/test-runner/frameworkIcon.js +27 -0
- package/dist/data/test-runner/frameworkIcon.js.map +1 -0
- package/dist/data/test-runner/index.d.ts +14 -0
- package/dist/data/test-runner/index.d.ts.map +1 -0
- package/dist/data/test-runner/status.cjs +234 -0
- package/dist/data/test-runner/status.cjs.map +1 -0
- package/dist/data/test-runner/status.d.ts +52 -0
- package/dist/data/test-runner/status.d.ts.map +1 -0
- package/dist/data/test-runner/status.js +234 -0
- package/dist/data/test-runner/status.js.map +1 -0
- package/dist/data/test-runner/types.d.ts +148 -0
- package/dist/data/test-runner/types.d.ts.map +1 -0
- package/dist/data/version-info.cjs +26 -0
- package/dist/data/version-info.cjs.map +1 -0
- package/dist/data/version-info.d.ts +11 -0
- package/dist/data/version-info.d.ts.map +1 -0
- package/dist/data/version-info.js +26 -0
- package/dist/data/version-info.js.map +1 -0
- package/dist/data.cjs +49 -0
- package/dist/data.cjs.map +1 -1
- package/dist/data.d.ts +3 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +50 -1
- package/dist/data.js.map +1 -1
- package/dist/index.cjs +59 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay/DropdownMenu.cjs +57 -30
- package/dist/overlay/DropdownMenu.cjs.map +1 -1
- package/dist/overlay/DropdownMenu.d.ts.map +1 -1
- package/dist/overlay/DropdownMenu.js +57 -30
- package/dist/overlay/DropdownMenu.js.map +1 -1
- package/dist/overlay/Modal.cjs +62 -6
- package/dist/overlay/Modal.cjs.map +1 -1
- package/dist/overlay/Modal.d.ts +19 -1
- package/dist/overlay/Modal.d.ts.map +1 -1
- package/dist/overlay/Modal.js +62 -6
- package/dist/overlay/Modal.js.map +1 -1
- package/dist/rpc/CommandForm.cjs +2 -2
- package/dist/rpc/CommandForm.cjs.map +1 -1
- package/dist/rpc/CommandForm.d.ts.map +1 -1
- package/dist/rpc/CommandForm.js +3 -3
- package/dist/rpc/CommandForm.js.map +1 -1
- package/dist/rpc/formMetadata.cjs +5 -4
- package/dist/rpc/formMetadata.cjs.map +1 -1
- package/dist/rpc/formMetadata.d.ts.map +1 -1
- package/dist/rpc/formMetadata.js +6 -5
- package/dist/rpc/formMetadata.js.map +1 -1
- package/dist/rpc/types.cjs +4 -0
- package/dist/rpc/types.cjs.map +1 -1
- package/dist/rpc/types.d.ts +10 -0
- package/dist/rpc/types.d.ts.map +1 -1
- package/dist/rpc/types.js +5 -1
- package/dist/rpc/types.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestFailureDetail.cjs","sources":["../../../src/data/test-runner/TestFailureDetail.tsx"],"sourcesContent":["import { LogViewer } from \"../LogViewer\";\nimport { cn } from \"../../lib/utils\";\nimport type { FailureDetail, Test, TestAttempt } from \"./types\";\nimport { formatTestDuration } from \"./status\";\n\nfunction Field({\n label,\n value,\n mono,\n}: {\n label: string;\n value: string | undefined;\n mono?: boolean;\n}) {\n if (!value) return null;\n return (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{label}</div>\n <pre\n className={cn(\n \"whitespace-pre-wrap break-words rounded-md bg-muted p-2 text-xs\",\n mono && \"font-mono\",\n )}\n >\n {value}\n </pre>\n </div>\n );\n}\n\n/** Renders a structured failure (expected/actual/matcher/location/stack). */\nexport function TestFailureDetail({ failure }: { failure: FailureDetail }) {\n return (\n <div className=\"space-y-3 rounded-md border border-red-200 bg-red-50/50 p-3 dark:border-red-900 dark:bg-red-950/30\">\n {failure.summary && (\n <p className=\"text-sm font-medium text-red-700 dark:text-red-300\">{failure.summary}</p>\n )}\n <Field label=\"Matcher\" value={failure.matcher} mono />\n <Field label=\"Expected\" value={failure.expected} mono />\n <Field label=\"Actual\" value={failure.actual} mono />\n <Field label=\"Location\" value={failure.location} mono />\n {failure.stack && <LogViewer logs={failure.stack} collapsedLines={6} />}\n </div>\n );\n}\n\n/** stdout / stderr blocks, each via the collapsible LogViewer. */\nexport function TestOutput({ node }: { node: Test }) {\n if (!node.stdout && !node.stderr) return null;\n return (\n <div className=\"space-y-3\">\n {node.stdout && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">stdout</div>\n <LogViewer logs={node.stdout} />\n </div>\n )}\n {node.stderr && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">stderr</div>\n <LogViewer logs={node.stderr} borderClass=\"border-red-300 dark:border-red-900\" />\n </div>\n )}\n </div>\n );\n}\n\nfunction attemptTone(a: TestAttempt): string {\n if (a.failed || a.timed_out) return \"text-red-600 dark:text-red-400\";\n if (a.passed) return \"text-green-600 dark:text-green-400\";\n return \"text-muted-foreground\";\n}\n\n/** Prior attempts list for flaky/reran tests. */\nexport function TestAttempts({ attempts }: { attempts: TestAttempt[] }) {\n if (attempts.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n Attempts ({attempts.length})\n </div>\n <ul className=\"divide-y divide-border rounded-md border border-border text-xs\">\n {attempts.map((a) => (\n <li key={a.sequence} className=\"flex items-center justify-between gap-2 px-2 py-1\">\n <span className={cn(\"font-medium\", attemptTone(a))}>\n #{a.sequence} {a.run_kind ?? \"\"}\n </span>\n <span className=\"truncate text-muted-foreground\">{a.message}</span>\n {a.duration ? (\n <span className=\"shrink-0 text-muted-foreground\">{formatTestDuration(a.duration)}</span>\n ) : null}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n"],"names":["jsxs","jsx","cn","LogViewer","formatTestDuration"],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { FailureDetail, Test, TestAttempt } from './types';
|
|
2
|
+
/** Renders a structured failure (expected/actual/matcher/location/stack). */
|
|
3
|
+
export declare function TestFailureDetail({ failure }: {
|
|
4
|
+
failure: FailureDetail;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
/** stdout / stderr blocks, each via the collapsible LogViewer. */
|
|
7
|
+
export declare function TestOutput({ node }: {
|
|
8
|
+
node: Test;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
10
|
+
/** Prior attempts list for flaky/reran tests. */
|
|
11
|
+
export declare function TestAttempts({ attempts }: {
|
|
12
|
+
attempts: TestAttempt[];
|
|
13
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
14
|
+
//# sourceMappingURL=TestFailureDetail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestFailureDetail.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestFailureDetail.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AA4BhE,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,2CAaxE;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,kDAkBlD;AAQD,iDAAiD;AACjD,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,EAAE,CAAA;CAAE,kDAsBrE"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { LogViewer } from "../LogViewer.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { formatTestDuration } from "./status.js";
|
|
5
|
+
function Field({
|
|
6
|
+
label,
|
|
7
|
+
value,
|
|
8
|
+
mono
|
|
9
|
+
}) {
|
|
10
|
+
if (!value) return null;
|
|
11
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
12
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: label }),
|
|
13
|
+
/* @__PURE__ */ jsx(
|
|
14
|
+
"pre",
|
|
15
|
+
{
|
|
16
|
+
className: cn(
|
|
17
|
+
"whitespace-pre-wrap break-words rounded-md bg-muted p-2 text-xs",
|
|
18
|
+
mono && "font-mono"
|
|
19
|
+
),
|
|
20
|
+
children: value
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
] });
|
|
24
|
+
}
|
|
25
|
+
function TestFailureDetail({ failure }) {
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border border-red-200 bg-red-50/50 p-3 dark:border-red-900 dark:bg-red-950/30", children: [
|
|
27
|
+
failure.summary && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-red-700 dark:text-red-300", children: failure.summary }),
|
|
28
|
+
/* @__PURE__ */ jsx(Field, { label: "Matcher", value: failure.matcher, mono: true }),
|
|
29
|
+
/* @__PURE__ */ jsx(Field, { label: "Expected", value: failure.expected, mono: true }),
|
|
30
|
+
/* @__PURE__ */ jsx(Field, { label: "Actual", value: failure.actual, mono: true }),
|
|
31
|
+
/* @__PURE__ */ jsx(Field, { label: "Location", value: failure.location, mono: true }),
|
|
32
|
+
failure.stack && /* @__PURE__ */ jsx(LogViewer, { logs: failure.stack, collapsedLines: 6 })
|
|
33
|
+
] });
|
|
34
|
+
}
|
|
35
|
+
function TestOutput({ node }) {
|
|
36
|
+
if (!node.stdout && !node.stderr) return null;
|
|
37
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
38
|
+
node.stdout && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
39
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "stdout" }),
|
|
40
|
+
/* @__PURE__ */ jsx(LogViewer, { logs: node.stdout })
|
|
41
|
+
] }),
|
|
42
|
+
node.stderr && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "stderr" }),
|
|
44
|
+
/* @__PURE__ */ jsx(LogViewer, { logs: node.stderr, borderClass: "border-red-300 dark:border-red-900" })
|
|
45
|
+
] })
|
|
46
|
+
] });
|
|
47
|
+
}
|
|
48
|
+
function attemptTone(a) {
|
|
49
|
+
if (a.failed || a.timed_out) return "text-red-600 dark:text-red-400";
|
|
50
|
+
if (a.passed) return "text-green-600 dark:text-green-400";
|
|
51
|
+
return "text-muted-foreground";
|
|
52
|
+
}
|
|
53
|
+
function TestAttempts({ attempts }) {
|
|
54
|
+
if (attempts.length === 0) return null;
|
|
55
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
56
|
+
/* @__PURE__ */ jsxs("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: [
|
|
57
|
+
"Attempts (",
|
|
58
|
+
attempts.length,
|
|
59
|
+
")"
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y divide-border rounded-md border border-border text-xs", children: attempts.map((a) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-2 px-2 py-1", children: [
|
|
62
|
+
/* @__PURE__ */ jsxs("span", { className: cn("font-medium", attemptTone(a)), children: [
|
|
63
|
+
"#",
|
|
64
|
+
a.sequence,
|
|
65
|
+
" ",
|
|
66
|
+
a.run_kind ?? ""
|
|
67
|
+
] }),
|
|
68
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: a.message }),
|
|
69
|
+
a.duration ? /* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatTestDuration(a.duration) }) : null
|
|
70
|
+
] }, a.sequence)) })
|
|
71
|
+
] });
|
|
72
|
+
}
|
|
73
|
+
export {
|
|
74
|
+
TestAttempts,
|
|
75
|
+
TestFailureDetail,
|
|
76
|
+
TestOutput
|
|
77
|
+
};
|
|
78
|
+
//# sourceMappingURL=TestFailureDetail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestFailureDetail.js","sources":["../../../src/data/test-runner/TestFailureDetail.tsx"],"sourcesContent":["import { LogViewer } from \"../LogViewer\";\nimport { cn } from \"../../lib/utils\";\nimport type { FailureDetail, Test, TestAttempt } from \"./types\";\nimport { formatTestDuration } from \"./status\";\n\nfunction Field({\n label,\n value,\n mono,\n}: {\n label: string;\n value: string | undefined;\n mono?: boolean;\n}) {\n if (!value) return null;\n return (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{label}</div>\n <pre\n className={cn(\n \"whitespace-pre-wrap break-words rounded-md bg-muted p-2 text-xs\",\n mono && \"font-mono\",\n )}\n >\n {value}\n </pre>\n </div>\n );\n}\n\n/** Renders a structured failure (expected/actual/matcher/location/stack). */\nexport function TestFailureDetail({ failure }: { failure: FailureDetail }) {\n return (\n <div className=\"space-y-3 rounded-md border border-red-200 bg-red-50/50 p-3 dark:border-red-900 dark:bg-red-950/30\">\n {failure.summary && (\n <p className=\"text-sm font-medium text-red-700 dark:text-red-300\">{failure.summary}</p>\n )}\n <Field label=\"Matcher\" value={failure.matcher} mono />\n <Field label=\"Expected\" value={failure.expected} mono />\n <Field label=\"Actual\" value={failure.actual} mono />\n <Field label=\"Location\" value={failure.location} mono />\n {failure.stack && <LogViewer logs={failure.stack} collapsedLines={6} />}\n </div>\n );\n}\n\n/** stdout / stderr blocks, each via the collapsible LogViewer. */\nexport function TestOutput({ node }: { node: Test }) {\n if (!node.stdout && !node.stderr) return null;\n return (\n <div className=\"space-y-3\">\n {node.stdout && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">stdout</div>\n <LogViewer logs={node.stdout} />\n </div>\n )}\n {node.stderr && (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">stderr</div>\n <LogViewer logs={node.stderr} borderClass=\"border-red-300 dark:border-red-900\" />\n </div>\n )}\n </div>\n );\n}\n\nfunction attemptTone(a: TestAttempt): string {\n if (a.failed || a.timed_out) return \"text-red-600 dark:text-red-400\";\n if (a.passed) return \"text-green-600 dark:text-green-400\";\n return \"text-muted-foreground\";\n}\n\n/** Prior attempts list for flaky/reran tests. */\nexport function TestAttempts({ attempts }: { attempts: TestAttempt[] }) {\n if (attempts.length === 0) return null;\n return (\n <div className=\"space-y-1\">\n <div className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">\n Attempts ({attempts.length})\n </div>\n <ul className=\"divide-y divide-border rounded-md border border-border text-xs\">\n {attempts.map((a) => (\n <li key={a.sequence} className=\"flex items-center justify-between gap-2 px-2 py-1\">\n <span className={cn(\"font-medium\", attemptTone(a))}>\n #{a.sequence} {a.run_kind ?? \"\"}\n </span>\n <span className=\"truncate text-muted-foreground\">{a.message}</span>\n {a.duration ? (\n <span className=\"shrink-0 text-muted-foreground\">{formatTestDuration(a.duration)}</span>\n ) : null}\n </li>\n ))}\n </ul>\n </div>\n );\n}\n"],"names":[],"mappings":";;;;AAKA,SAAS,MAAM;AAAA,EACb;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,CAAC,MAAO,QAAO;AACnB,SACE,qBAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,WAAU,6DAA6D,UAAA,OAAM;AAAA,IAClF;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,QAAQ;AAAA,QAAA;AAAA,QAGT,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EACH,GACF;AAEJ;AAGO,SAAS,kBAAkB,EAAE,WAAuC;AACzE,SACE,qBAAC,OAAA,EAAI,WAAU,sGACZ,UAAA;AAAA,IAAA,QAAQ,WACP,oBAAC,KAAA,EAAE,WAAU,sDAAsD,kBAAQ,SAAQ;AAAA,IAErF,oBAAC,SAAM,OAAM,WAAU,OAAO,QAAQ,SAAS,MAAI,MAAC;AAAA,IACpD,oBAAC,SAAM,OAAM,YAAW,OAAO,QAAQ,UAAU,MAAI,MAAC;AAAA,IACtD,oBAAC,SAAM,OAAM,UAAS,OAAO,QAAQ,QAAQ,MAAI,MAAC;AAAA,IAClD,oBAAC,SAAM,OAAM,YAAW,OAAO,QAAQ,UAAU,MAAI,MAAC;AAAA,IACrD,QAAQ,SAAS,oBAAC,WAAA,EAAU,MAAM,QAAQ,OAAO,gBAAgB,EAAA,CAAG;AAAA,EAAA,GACvE;AAEJ;AAGO,SAAS,WAAW,EAAE,QAAwB;AACnD,MAAI,CAAC,KAAK,UAAU,CAAC,KAAK,OAAQ,QAAO;AACzC,SACE,qBAAC,OAAA,EAAI,WAAU,aACZ,UAAA;AAAA,IAAA,KAAK,UACJ,qBAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAI,WAAU,6DAA4D,UAAA,UAAM;AAAA,MACjF,oBAAC,WAAA,EAAU,MAAM,KAAK,OAAA,CAAQ;AAAA,IAAA,GAChC;AAAA,IAED,KAAK,UACJ,qBAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,MAAA,oBAAC,OAAA,EAAI,WAAU,6DAA4D,UAAA,UAAM;AAAA,0BAChF,WAAA,EAAU,MAAM,KAAK,QAAQ,aAAY,qCAAA,CAAqC;AAAA,IAAA,EAAA,CACjF;AAAA,EAAA,GAEJ;AAEJ;AAEA,SAAS,YAAY,GAAwB;AAC3C,MAAI,EAAE,UAAU,EAAE,UAAW,QAAO;AACpC,MAAI,EAAE,OAAQ,QAAO;AACrB,SAAO;AACT;AAGO,SAAS,aAAa,EAAE,YAAyC;AACtE,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,SACE,qBAAC,OAAA,EAAI,WAAU,aACb,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,6DAA4D,UAAA;AAAA,MAAA;AAAA,MAC9D,SAAS;AAAA,MAAO;AAAA,IAAA,GAC7B;AAAA,IACA,oBAAC,MAAA,EAAG,WAAU,kEACX,UAAA,SAAS,IAAI,CAAC,MACb,qBAAC,MAAA,EAAoB,WAAU,qDAC7B,UAAA;AAAA,MAAA,qBAAC,UAAK,WAAW,GAAG,eAAe,YAAY,CAAC,CAAC,GAAG,UAAA;AAAA,QAAA;AAAA,QAChD,EAAE;AAAA,QAAS;AAAA,QAAE,EAAE,YAAY;AAAA,MAAA,GAC/B;AAAA,MACA,oBAAC,QAAA,EAAK,WAAU,kCAAkC,YAAE,SAAQ;AAAA,MAC3D,EAAE,WACD,oBAAC,QAAA,EAAK,WAAU,kCAAkC,UAAA,mBAAmB,EAAE,QAAQ,EAAA,CAAE,IAC/E;AAAA,IAAA,KAPG,EAAE,QAQX,CACD,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const Icon = require("../Icon.cjs");
|
|
5
|
+
const utils = require("../../lib/utils.cjs");
|
|
6
|
+
const frameworkIcon = require("./frameworkIcon.cjs");
|
|
7
|
+
const status = require("./status.cjs");
|
|
8
|
+
const filterState = require("./filterState.cjs");
|
|
9
|
+
const UiAdd = require("../../icons/components/UiAdd.cjs");
|
|
10
|
+
const UiRemove = require("../../icons/components/UiRemove.cjs");
|
|
11
|
+
const STATUS_DEFS = [
|
|
12
|
+
{ key: "failed", label: "Failed", badge: "bg-red-500" },
|
|
13
|
+
{ key: "warned", label: "Warned", badge: "bg-amber-400" },
|
|
14
|
+
{ key: "timedout", label: "Timed out", badge: "bg-amber-500" },
|
|
15
|
+
{ key: "passed", label: "Passed", badge: "bg-green-500" },
|
|
16
|
+
{ key: "skipped", label: "Skipped", badge: "bg-yellow-400" },
|
|
17
|
+
{ key: "running", label: "Running", badge: "bg-blue-500" },
|
|
18
|
+
{ key: "pending", label: "Queued", badge: "bg-muted-foreground" }
|
|
19
|
+
];
|
|
20
|
+
function pillClass(mode) {
|
|
21
|
+
if (mode === "include") return "bg-accent border-border font-medium text-foreground";
|
|
22
|
+
if (mode === "exclude") return "bg-foreground text-background border-foreground font-medium";
|
|
23
|
+
return "border-border text-muted-foreground hover:bg-accent";
|
|
24
|
+
}
|
|
25
|
+
function StateMarker({ mode }) {
|
|
26
|
+
if (mode === "include") return /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiAdd.UiAdd, className: "text-xs" });
|
|
27
|
+
if (mode === "exclude") return /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiRemove.UiRemove, className: "text-xs" });
|
|
28
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-2 w-2 rounded-full bg-current opacity-30", "aria-hidden": true });
|
|
29
|
+
}
|
|
30
|
+
function TestFilterBar({
|
|
31
|
+
filters,
|
|
32
|
+
onChange,
|
|
33
|
+
counts,
|
|
34
|
+
frameworks,
|
|
35
|
+
className
|
|
36
|
+
}) {
|
|
37
|
+
const hasActive = filters.status.size > 0 || filters.framework.size > 0;
|
|
38
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex flex-wrap items-center gap-1.5", className), children: [
|
|
39
|
+
STATUS_DEFS.map((sf) => {
|
|
40
|
+
const count = counts[sf.key];
|
|
41
|
+
if (count === 0) return null;
|
|
42
|
+
const mode = filters.status.get(sf.key);
|
|
43
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
type: "button",
|
|
47
|
+
className: utils.cn(
|
|
48
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs transition-colors",
|
|
49
|
+
pillClass(mode)
|
|
50
|
+
),
|
|
51
|
+
onClick: () => onChange({ ...filters, status: filterState.cycleFilterState(filters.status, sf.key) }),
|
|
52
|
+
title: `${sf.label}: ${mode ?? "neutral"}`,
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
55
|
+
"span",
|
|
56
|
+
{
|
|
57
|
+
className: utils.cn(
|
|
58
|
+
"inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-bold text-white",
|
|
59
|
+
sf.badge
|
|
60
|
+
),
|
|
61
|
+
children: status.formatCount(count)
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
/* @__PURE__ */ jsxRuntime.jsx(StateMarker, { mode }),
|
|
65
|
+
sf.label
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
sf.key
|
|
69
|
+
);
|
|
70
|
+
}),
|
|
71
|
+
frameworks.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
72
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "mx-0.5 text-border", children: "|" }),
|
|
73
|
+
frameworks.map((fw) => {
|
|
74
|
+
const icon = frameworkIcon.frameworkIcon(fw);
|
|
75
|
+
const mode = filters.framework.get(fw);
|
|
76
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
77
|
+
"button",
|
|
78
|
+
{
|
|
79
|
+
type: "button",
|
|
80
|
+
className: utils.cn(
|
|
81
|
+
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition-colors",
|
|
82
|
+
pillClass(mode)
|
|
83
|
+
),
|
|
84
|
+
onClick: () => onChange({ ...filters, framework: filterState.cycleFilterState(filters.framework, fw) }),
|
|
85
|
+
title: `${fw}: ${mode ?? "neutral"}`,
|
|
86
|
+
children: [
|
|
87
|
+
/* @__PURE__ */ jsxRuntime.jsx(StateMarker, { mode }),
|
|
88
|
+
icon && /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon, className: "text-sm" }),
|
|
89
|
+
fw
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
fw
|
|
93
|
+
);
|
|
94
|
+
})
|
|
95
|
+
] }),
|
|
96
|
+
hasActive && /* @__PURE__ */ jsxRuntime.jsx(
|
|
97
|
+
"button",
|
|
98
|
+
{
|
|
99
|
+
type: "button",
|
|
100
|
+
className: "ml-1 text-xs text-muted-foreground hover:text-foreground",
|
|
101
|
+
onClick: () => onChange({ status: /* @__PURE__ */ new Map(), framework: /* @__PURE__ */ new Map() }),
|
|
102
|
+
children: "Clear"
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
] });
|
|
106
|
+
}
|
|
107
|
+
exports.TestFilterBar = TestFilterBar;
|
|
108
|
+
//# sourceMappingURL=TestFilterBar.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestFilterBar.cjs","sources":["../../../src/data/test-runner/TestFilterBar.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { cn } from \"../../lib/utils\";\nimport { UiAdd, UiRemove } from \"../../icons\";\nimport { frameworkIcon } from \"./frameworkIcon\";\nimport { formatCount, type StatusCounts } from \"./status\";\nimport { cycleFilterState, type FilterMode, type TestFilters } from \"./filterState\";\n\nexport type TestFilterBarProps = {\n filters: TestFilters;\n onChange: (next: TestFilters) => void;\n counts: StatusCounts;\n frameworks: string[];\n className?: string;\n};\n\ntype StatusDef = { key: keyof StatusCounts; label: string; badge: string };\n\nconst STATUS_DEFS: StatusDef[] = [\n { key: \"failed\", label: \"Failed\", badge: \"bg-red-500\" },\n { key: \"warned\", label: \"Warned\", badge: \"bg-amber-400\" },\n { key: \"timedout\", label: \"Timed out\", badge: \"bg-amber-500\" },\n { key: \"passed\", label: \"Passed\", badge: \"bg-green-500\" },\n { key: \"skipped\", label: \"Skipped\", badge: \"bg-yellow-400\" },\n { key: \"running\", label: \"Running\", badge: \"bg-blue-500\" },\n { key: \"pending\", label: \"Queued\", badge: \"bg-muted-foreground\" },\n];\n\nfunction pillClass(mode: FilterMode | undefined): string {\n if (mode === \"include\") return \"bg-accent border-border font-medium text-foreground\";\n if (mode === \"exclude\") return \"bg-foreground text-background border-foreground font-medium\";\n return \"border-border text-muted-foreground hover:bg-accent\";\n}\n\nfunction StateMarker({ mode }: { mode: FilterMode | undefined }) {\n if (mode === \"include\") return <Icon icon={UiAdd} className=\"text-xs\" />;\n if (mode === \"exclude\") return <Icon icon={UiRemove} className=\"text-xs\" />;\n return <span className=\"h-2 w-2 rounded-full bg-current opacity-30\" aria-hidden />;\n}\n\n/**\n * Tri-state status + framework filter pills. Named TestFilterBar to avoid the\n * pre-existing generic `FilterBar` export in clicky-ui/components.\n */\nexport function TestFilterBar({\n filters,\n onChange,\n counts,\n frameworks,\n className,\n}: TestFilterBarProps) {\n const hasActive = filters.status.size > 0 || filters.framework.size > 0;\n\n return (\n <div className={cn(\"flex flex-wrap items-center gap-1.5\", className)}>\n {STATUS_DEFS.map((sf) => {\n const count = counts[sf.key];\n if (count === 0) return null;\n const mode = filters.status.get(sf.key);\n return (\n <button\n key={sf.key}\n type=\"button\"\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs transition-colors\",\n pillClass(mode),\n )}\n onClick={() => onChange({ ...filters, status: cycleFilterState(filters.status, sf.key) })}\n title={`${sf.label}: ${mode ?? \"neutral\"}`}\n >\n <span\n className={cn(\n \"inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-bold text-white\",\n sf.badge,\n )}\n >\n {formatCount(count)}\n </span>\n <StateMarker mode={mode} />\n {sf.label}\n </button>\n );\n })}\n\n {frameworks.length > 1 && (\n <>\n <span className=\"mx-0.5 text-border\">|</span>\n {frameworks.map((fw) => {\n const icon = frameworkIcon(fw);\n const mode = filters.framework.get(fw);\n return (\n <button\n key={fw}\n type=\"button\"\n className={cn(\n \"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition-colors\",\n pillClass(mode),\n )}\n onClick={() =>\n onChange({ ...filters, framework: cycleFilterState(filters.framework, fw) })\n }\n title={`${fw}: ${mode ?? \"neutral\"}`}\n >\n <StateMarker mode={mode} />\n {icon && <Icon icon={icon} className=\"text-sm\" />}\n {fw}\n </button>\n );\n })}\n </>\n )}\n\n {hasActive && (\n <button\n type=\"button\"\n className=\"ml-1 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => onChange({ status: new Map(), framework: new Map() })}\n >\n Clear\n </button>\n )}\n </div>\n );\n}\n"],"names":["jsx","Icon","UiAdd","UiRemove","cn","jsxs","cycleFilterState","Fragment","frameworkIcon"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StatusCounts } from './status';
|
|
2
|
+
import { TestFilters } from './filterState';
|
|
3
|
+
export type TestFilterBarProps = {
|
|
4
|
+
filters: TestFilters;
|
|
5
|
+
onChange: (next: TestFilters) => void;
|
|
6
|
+
counts: StatusCounts;
|
|
7
|
+
frameworks: string[];
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Tri-state status + framework filter pills. Named TestFilterBar to avoid the
|
|
12
|
+
* pre-existing generic `FilterBar` export in clicky-ui/components.
|
|
13
|
+
*/
|
|
14
|
+
export declare function TestFilterBar({ filters, onChange, counts, frameworks, className, }: TestFilterBarProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=TestFilterBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestFilterBar.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestFilterBar.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAC1D,OAAO,EAAqC,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAEpF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA0BF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,QAAQ,EACR,MAAM,EACN,UAAU,EACV,SAAS,GACV,EAAE,kBAAkB,2CAyEpB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from "../Icon.js";
|
|
3
|
+
import { cn } from "../../lib/utils.js";
|
|
4
|
+
import { frameworkIcon } from "./frameworkIcon.js";
|
|
5
|
+
import { formatCount } from "./status.js";
|
|
6
|
+
import { cycleFilterState } from "./filterState.js";
|
|
7
|
+
import { UiAdd } from "../../icons/components/UiAdd.js";
|
|
8
|
+
import { UiRemove } from "../../icons/components/UiRemove.js";
|
|
9
|
+
const STATUS_DEFS = [
|
|
10
|
+
{ key: "failed", label: "Failed", badge: "bg-red-500" },
|
|
11
|
+
{ key: "warned", label: "Warned", badge: "bg-amber-400" },
|
|
12
|
+
{ key: "timedout", label: "Timed out", badge: "bg-amber-500" },
|
|
13
|
+
{ key: "passed", label: "Passed", badge: "bg-green-500" },
|
|
14
|
+
{ key: "skipped", label: "Skipped", badge: "bg-yellow-400" },
|
|
15
|
+
{ key: "running", label: "Running", badge: "bg-blue-500" },
|
|
16
|
+
{ key: "pending", label: "Queued", badge: "bg-muted-foreground" }
|
|
17
|
+
];
|
|
18
|
+
function pillClass(mode) {
|
|
19
|
+
if (mode === "include") return "bg-accent border-border font-medium text-foreground";
|
|
20
|
+
if (mode === "exclude") return "bg-foreground text-background border-foreground font-medium";
|
|
21
|
+
return "border-border text-muted-foreground hover:bg-accent";
|
|
22
|
+
}
|
|
23
|
+
function StateMarker({ mode }) {
|
|
24
|
+
if (mode === "include") return /* @__PURE__ */ jsx(Icon, { icon: UiAdd, className: "text-xs" });
|
|
25
|
+
if (mode === "exclude") return /* @__PURE__ */ jsx(Icon, { icon: UiRemove, className: "text-xs" });
|
|
26
|
+
return /* @__PURE__ */ jsx("span", { className: "h-2 w-2 rounded-full bg-current opacity-30", "aria-hidden": true });
|
|
27
|
+
}
|
|
28
|
+
function TestFilterBar({
|
|
29
|
+
filters,
|
|
30
|
+
onChange,
|
|
31
|
+
counts,
|
|
32
|
+
frameworks,
|
|
33
|
+
className
|
|
34
|
+
}) {
|
|
35
|
+
const hasActive = filters.status.size > 0 || filters.framework.size > 0;
|
|
36
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center gap-1.5", className), children: [
|
|
37
|
+
STATUS_DEFS.map((sf) => {
|
|
38
|
+
const count = counts[sf.key];
|
|
39
|
+
if (count === 0) return null;
|
|
40
|
+
const mode = filters.status.get(sf.key);
|
|
41
|
+
return /* @__PURE__ */ jsxs(
|
|
42
|
+
"button",
|
|
43
|
+
{
|
|
44
|
+
type: "button",
|
|
45
|
+
className: cn(
|
|
46
|
+
"inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs transition-colors",
|
|
47
|
+
pillClass(mode)
|
|
48
|
+
),
|
|
49
|
+
onClick: () => onChange({ ...filters, status: cycleFilterState(filters.status, sf.key) }),
|
|
50
|
+
title: `${sf.label}: ${mode ?? "neutral"}`,
|
|
51
|
+
children: [
|
|
52
|
+
/* @__PURE__ */ jsx(
|
|
53
|
+
"span",
|
|
54
|
+
{
|
|
55
|
+
className: cn(
|
|
56
|
+
"inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-bold text-white",
|
|
57
|
+
sf.badge
|
|
58
|
+
),
|
|
59
|
+
children: formatCount(count)
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
/* @__PURE__ */ jsx(StateMarker, { mode }),
|
|
63
|
+
sf.label
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
sf.key
|
|
67
|
+
);
|
|
68
|
+
}),
|
|
69
|
+
frameworks.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
70
|
+
/* @__PURE__ */ jsx("span", { className: "mx-0.5 text-border", children: "|" }),
|
|
71
|
+
frameworks.map((fw) => {
|
|
72
|
+
const icon = frameworkIcon(fw);
|
|
73
|
+
const mode = filters.framework.get(fw);
|
|
74
|
+
return /* @__PURE__ */ jsxs(
|
|
75
|
+
"button",
|
|
76
|
+
{
|
|
77
|
+
type: "button",
|
|
78
|
+
className: cn(
|
|
79
|
+
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition-colors",
|
|
80
|
+
pillClass(mode)
|
|
81
|
+
),
|
|
82
|
+
onClick: () => onChange({ ...filters, framework: cycleFilterState(filters.framework, fw) }),
|
|
83
|
+
title: `${fw}: ${mode ?? "neutral"}`,
|
|
84
|
+
children: [
|
|
85
|
+
/* @__PURE__ */ jsx(StateMarker, { mode }),
|
|
86
|
+
icon && /* @__PURE__ */ jsx(Icon, { icon, className: "text-sm" }),
|
|
87
|
+
fw
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
fw
|
|
91
|
+
);
|
|
92
|
+
})
|
|
93
|
+
] }),
|
|
94
|
+
hasActive && /* @__PURE__ */ jsx(
|
|
95
|
+
"button",
|
|
96
|
+
{
|
|
97
|
+
type: "button",
|
|
98
|
+
className: "ml-1 text-xs text-muted-foreground hover:text-foreground",
|
|
99
|
+
onClick: () => onChange({ status: /* @__PURE__ */ new Map(), framework: /* @__PURE__ */ new Map() }),
|
|
100
|
+
children: "Clear"
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
] });
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
TestFilterBar
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=TestFilterBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestFilterBar.js","sources":["../../../src/data/test-runner/TestFilterBar.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { cn } from \"../../lib/utils\";\nimport { UiAdd, UiRemove } from \"../../icons\";\nimport { frameworkIcon } from \"./frameworkIcon\";\nimport { formatCount, type StatusCounts } from \"./status\";\nimport { cycleFilterState, type FilterMode, type TestFilters } from \"./filterState\";\n\nexport type TestFilterBarProps = {\n filters: TestFilters;\n onChange: (next: TestFilters) => void;\n counts: StatusCounts;\n frameworks: string[];\n className?: string;\n};\n\ntype StatusDef = { key: keyof StatusCounts; label: string; badge: string };\n\nconst STATUS_DEFS: StatusDef[] = [\n { key: \"failed\", label: \"Failed\", badge: \"bg-red-500\" },\n { key: \"warned\", label: \"Warned\", badge: \"bg-amber-400\" },\n { key: \"timedout\", label: \"Timed out\", badge: \"bg-amber-500\" },\n { key: \"passed\", label: \"Passed\", badge: \"bg-green-500\" },\n { key: \"skipped\", label: \"Skipped\", badge: \"bg-yellow-400\" },\n { key: \"running\", label: \"Running\", badge: \"bg-blue-500\" },\n { key: \"pending\", label: \"Queued\", badge: \"bg-muted-foreground\" },\n];\n\nfunction pillClass(mode: FilterMode | undefined): string {\n if (mode === \"include\") return \"bg-accent border-border font-medium text-foreground\";\n if (mode === \"exclude\") return \"bg-foreground text-background border-foreground font-medium\";\n return \"border-border text-muted-foreground hover:bg-accent\";\n}\n\nfunction StateMarker({ mode }: { mode: FilterMode | undefined }) {\n if (mode === \"include\") return <Icon icon={UiAdd} className=\"text-xs\" />;\n if (mode === \"exclude\") return <Icon icon={UiRemove} className=\"text-xs\" />;\n return <span className=\"h-2 w-2 rounded-full bg-current opacity-30\" aria-hidden />;\n}\n\n/**\n * Tri-state status + framework filter pills. Named TestFilterBar to avoid the\n * pre-existing generic `FilterBar` export in clicky-ui/components.\n */\nexport function TestFilterBar({\n filters,\n onChange,\n counts,\n frameworks,\n className,\n}: TestFilterBarProps) {\n const hasActive = filters.status.size > 0 || filters.framework.size > 0;\n\n return (\n <div className={cn(\"flex flex-wrap items-center gap-1.5\", className)}>\n {STATUS_DEFS.map((sf) => {\n const count = counts[sf.key];\n if (count === 0) return null;\n const mode = filters.status.get(sf.key);\n return (\n <button\n key={sf.key}\n type=\"button\"\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs transition-colors\",\n pillClass(mode),\n )}\n onClick={() => onChange({ ...filters, status: cycleFilterState(filters.status, sf.key) })}\n title={`${sf.label}: ${mode ?? \"neutral\"}`}\n >\n <span\n className={cn(\n \"inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-bold text-white\",\n sf.badge,\n )}\n >\n {formatCount(count)}\n </span>\n <StateMarker mode={mode} />\n {sf.label}\n </button>\n );\n })}\n\n {frameworks.length > 1 && (\n <>\n <span className=\"mx-0.5 text-border\">|</span>\n {frameworks.map((fw) => {\n const icon = frameworkIcon(fw);\n const mode = filters.framework.get(fw);\n return (\n <button\n key={fw}\n type=\"button\"\n className={cn(\n \"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition-colors\",\n pillClass(mode),\n )}\n onClick={() =>\n onChange({ ...filters, framework: cycleFilterState(filters.framework, fw) })\n }\n title={`${fw}: ${mode ?? \"neutral\"}`}\n >\n <StateMarker mode={mode} />\n {icon && <Icon icon={icon} className=\"text-sm\" />}\n {fw}\n </button>\n );\n })}\n </>\n )}\n\n {hasActive && (\n <button\n type=\"button\"\n className=\"ml-1 text-xs text-muted-foreground hover:text-foreground\"\n onClick={() => onChange({ status: new Map(), framework: new Map() })}\n >\n Clear\n </button>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const Icon = require("../Icon.cjs");
|
|
5
|
+
const ProgressBar = require("../ProgressBar.cjs");
|
|
6
|
+
const utils = require("../../lib/utils.cjs");
|
|
7
|
+
const status = require("./status.cjs");
|
|
8
|
+
const UiClock = require("../../icons/components/UiClock.cjs");
|
|
9
|
+
const UiCheck = require("../../icons/components/UiCheck.cjs");
|
|
10
|
+
const UiLoader = require("../../icons/components/UiLoader.cjs");
|
|
11
|
+
function totals(tests) {
|
|
12
|
+
return tests.reduce(
|
|
13
|
+
(acc, t) => {
|
|
14
|
+
const s = status.sumNonTaskTests(t);
|
|
15
|
+
Object.keys(acc).forEach((k) => acc[k] += s[k]);
|
|
16
|
+
return acc;
|
|
17
|
+
},
|
|
18
|
+
{ total: 0, passed: 0, failed: 0, warned: 0, skipped: 0, pending: 0, running: 0, timedout: 0 }
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
function pct(part, total) {
|
|
22
|
+
return total > 0 ? Math.round(part / total * 100) : 0;
|
|
23
|
+
}
|
|
24
|
+
function TestRunSummary({
|
|
25
|
+
tests,
|
|
26
|
+
startTime,
|
|
27
|
+
endTime,
|
|
28
|
+
done,
|
|
29
|
+
now,
|
|
30
|
+
runMeta,
|
|
31
|
+
className
|
|
32
|
+
}) {
|
|
33
|
+
const t = totals(tests);
|
|
34
|
+
const clock = now ?? 0;
|
|
35
|
+
const end = done && endTime ? endTime : clock;
|
|
36
|
+
const elapsed = startTime && end >= startTime ? `${((end - startTime) / 1e3).toFixed(1)}s` : "";
|
|
37
|
+
const segments = [
|
|
38
|
+
{ count: t.passed, color: "bg-green-500", label: "passed" },
|
|
39
|
+
{ count: t.skipped, color: "bg-yellow-400", label: "skipped" },
|
|
40
|
+
{ count: t.failed, color: "bg-red-500", label: "failed" },
|
|
41
|
+
{ count: t.timedout, color: "bg-amber-500", label: "timed out" },
|
|
42
|
+
{ count: t.running, color: "bg-blue-400", label: "running" },
|
|
43
|
+
{ count: t.pending, color: "bg-muted-foreground/40", label: "queued" }
|
|
44
|
+
];
|
|
45
|
+
const chips = [
|
|
46
|
+
{ n: t.passed, label: "passed", tone: "text-green-600 dark:text-green-400" },
|
|
47
|
+
{ n: t.failed, label: "failed", tone: "text-red-600 dark:text-red-400" },
|
|
48
|
+
{ n: t.timedout, label: "timed out", tone: "text-amber-600 dark:text-amber-400" },
|
|
49
|
+
{ n: t.skipped, label: "skipped", tone: "text-yellow-700 dark:text-yellow-500" },
|
|
50
|
+
{ n: t.running, label: "running", tone: "text-blue-600 dark:text-blue-400" },
|
|
51
|
+
{ n: t.pending, label: "queued", tone: "text-muted-foreground" }
|
|
52
|
+
];
|
|
53
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: utils.cn("flex min-w-[18rem] flex-col items-end gap-1.5", className), children: [
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-end gap-x-3 gap-y-0.5 text-sm text-muted-foreground", children: [
|
|
55
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium text-foreground", children: [
|
|
56
|
+
t.total,
|
|
57
|
+
" tests",
|
|
58
|
+
(runMeta == null ? void 0 : runMeta.kind) === "rerun" ? ` · rerun #${runMeta.sequence}` : ""
|
|
59
|
+
] }),
|
|
60
|
+
chips.filter((c) => c.n > 0).map((c) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: c.tone, children: [
|
|
61
|
+
c.n,
|
|
62
|
+
" ",
|
|
63
|
+
c.label
|
|
64
|
+
] }, c.label)),
|
|
65
|
+
elapsed && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
66
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiClock.UiClock, className: "text-xs" }),
|
|
67
|
+
elapsed
|
|
68
|
+
] }),
|
|
69
|
+
done ? /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiCheck.UiCheck, className: "text-green-600 dark:text-green-400" }) : /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: UiLoader.UiLoader, className: "animate-spin text-blue-500" })
|
|
70
|
+
] }),
|
|
71
|
+
t.total > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-72 items-center gap-2", children: [
|
|
72
|
+
/* @__PURE__ */ jsxRuntime.jsx(ProgressBar.ProgressBar, { className: "flex-1", segments, total: t.total, height: "h-2" }),
|
|
73
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-xs font-medium text-green-600 dark:text-green-400", children: [
|
|
74
|
+
pct(t.passed, t.total),
|
|
75
|
+
"%"
|
|
76
|
+
] })
|
|
77
|
+
] })
|
|
78
|
+
] });
|
|
79
|
+
}
|
|
80
|
+
exports.TestRunSummary = TestRunSummary;
|
|
81
|
+
//# sourceMappingURL=TestRunSummary.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestRunSummary.cjs","sources":["../../../src/data/test-runner/TestRunSummary.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { ProgressBar, type ProgressSegment } from \"../ProgressBar\";\nimport { cn } from \"../../lib/utils\";\nimport { UiCheck, UiClock, UiLoader } from \"../../icons\";\nimport type { RunMeta, Test } from \"./types\";\nimport { sumNonTaskTests, type StatusCounts } from \"./status\";\n\nexport type TestRunSummaryProps = {\n tests: Test[];\n /** Run start, epoch ms (host-provided). */\n startTime?: number | null | undefined;\n /** Run end, epoch ms; falls back to `now` while running. */\n endTime?: number | null | undefined;\n done: boolean;\n /** Injected clock (epoch ms) so the component stays pure. */\n now?: number | undefined;\n runMeta?: RunMeta | undefined;\n className?: string | undefined;\n};\n\nfunction totals(tests: Test[]): StatusCounts {\n return tests.reduce(\n (acc, t) => {\n const s = sumNonTaskTests(t);\n (Object.keys(acc) as (keyof StatusCounts)[]).forEach((k) => (acc[k] += s[k]));\n return acc;\n },\n { total: 0, passed: 0, failed: 0, warned: 0, skipped: 0, pending: 0, running: 0, timedout: 0 },\n );\n}\n\nfunction pct(part: number, total: number): number {\n return total > 0 ? Math.round((part / total) * 100) : 0;\n}\n\n/**\n * Header summary: per-status counts, elapsed time, a stacked progress bar, and\n * pass/fail/pending gauge cards. Pure — elapsed time comes from injected `now`.\n */\nexport function TestRunSummary({\n tests,\n startTime,\n endTime,\n done,\n now,\n runMeta,\n className,\n}: TestRunSummaryProps) {\n const t = totals(tests);\n const clock = now ?? 0;\n const end = done && endTime ? endTime : clock;\n const elapsed = startTime && end >= startTime ? `${((end - startTime) / 1000).toFixed(1)}s` : \"\";\n\n const segments: ProgressSegment[] = [\n { count: t.passed, color: \"bg-green-500\", label: \"passed\" },\n { count: t.skipped, color: \"bg-yellow-400\", label: \"skipped\" },\n { count: t.failed, color: \"bg-red-500\", label: \"failed\" },\n { count: t.timedout, color: \"bg-amber-500\", label: \"timed out\" },\n { count: t.running, color: \"bg-blue-400\", label: \"running\" },\n { count: t.pending, color: \"bg-muted-foreground/40\", label: \"queued\" },\n ];\n\n // Each status appears once, as a coloured count chip — no separate gauge-card\n // row repeating the same numbers as percentages.\n const chips: { n: number; label: string; tone: string }[] = [\n { n: t.passed, label: \"passed\", tone: \"text-green-600 dark:text-green-400\" },\n { n: t.failed, label: \"failed\", tone: \"text-red-600 dark:text-red-400\" },\n { n: t.timedout, label: \"timed out\", tone: \"text-amber-600 dark:text-amber-400\" },\n { n: t.skipped, label: \"skipped\", tone: \"text-yellow-700 dark:text-yellow-500\" },\n { n: t.running, label: \"running\", tone: \"text-blue-600 dark:text-blue-400\" },\n { n: t.pending, label: \"queued\", tone: \"text-muted-foreground\" },\n ];\n\n return (\n <div className={cn(\"flex min-w-[18rem] flex-col items-end gap-1.5\", className)}>\n <div className=\"flex flex-wrap items-center justify-end gap-x-3 gap-y-0.5 text-sm text-muted-foreground\">\n <span className=\"font-medium text-foreground\">\n {t.total} tests\n {runMeta?.kind === \"rerun\" ? ` · rerun #${runMeta.sequence}` : \"\"}\n </span>\n {chips\n .filter((c) => c.n > 0)\n .map((c) => (\n <span key={c.label} className={c.tone}>\n {c.n} {c.label}\n </span>\n ))}\n {elapsed && (\n <span className=\"inline-flex items-center gap-1\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {elapsed}\n </span>\n )}\n {done ? (\n <Icon icon={UiCheck} className=\"text-green-600 dark:text-green-400\" />\n ) : (\n <Icon icon={UiLoader} className=\"animate-spin text-blue-500\" />\n )}\n </div>\n\n {t.total > 0 && (\n <div className=\"flex w-72 items-center gap-2\">\n <ProgressBar className=\"flex-1\" segments={segments} total={t.total} height=\"h-2\" />\n <span className=\"shrink-0 text-xs font-medium text-green-600 dark:text-green-400\">\n {pct(t.passed, t.total)}%\n </span>\n </div>\n )}\n </div>\n );\n}\n"],"names":["sumNonTaskTests","cn","jsxs","jsx","Icon","UiClock","UiCheck","UiLoader","ProgressBar"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RunMeta, Test } from './types';
|
|
2
|
+
export type TestRunSummaryProps = {
|
|
3
|
+
tests: Test[];
|
|
4
|
+
/** Run start, epoch ms (host-provided). */
|
|
5
|
+
startTime?: number | null | undefined;
|
|
6
|
+
/** Run end, epoch ms; falls back to `now` while running. */
|
|
7
|
+
endTime?: number | null | undefined;
|
|
8
|
+
done: boolean;
|
|
9
|
+
/** Injected clock (epoch ms) so the component stays pure. */
|
|
10
|
+
now?: number | undefined;
|
|
11
|
+
runMeta?: RunMeta | undefined;
|
|
12
|
+
className?: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Header summary: per-status counts, elapsed time, a stacked progress bar, and
|
|
16
|
+
* pass/fail/pending gauge cards. Pure — elapsed time comes from injected `now`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function TestRunSummary({ tests, startTime, endTime, done, now, runMeta, className, }: TestRunSummaryProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=TestRunSummary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestRunSummary.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestRunSummary.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG7C,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAiBF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,SAAS,EACT,OAAO,EACP,IAAI,EACJ,GAAG,EACH,OAAO,EACP,SAAS,GACV,EAAE,mBAAmB,2CA+DrB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Icon } from "../Icon.js";
|
|
3
|
+
import { ProgressBar } from "../ProgressBar.js";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { sumNonTaskTests } from "./status.js";
|
|
6
|
+
import { UiClock } from "../../icons/components/UiClock.js";
|
|
7
|
+
import { UiCheck } from "../../icons/components/UiCheck.js";
|
|
8
|
+
import { UiLoader } from "../../icons/components/UiLoader.js";
|
|
9
|
+
function totals(tests) {
|
|
10
|
+
return tests.reduce(
|
|
11
|
+
(acc, t) => {
|
|
12
|
+
const s = sumNonTaskTests(t);
|
|
13
|
+
Object.keys(acc).forEach((k) => acc[k] += s[k]);
|
|
14
|
+
return acc;
|
|
15
|
+
},
|
|
16
|
+
{ total: 0, passed: 0, failed: 0, warned: 0, skipped: 0, pending: 0, running: 0, timedout: 0 }
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function pct(part, total) {
|
|
20
|
+
return total > 0 ? Math.round(part / total * 100) : 0;
|
|
21
|
+
}
|
|
22
|
+
function TestRunSummary({
|
|
23
|
+
tests,
|
|
24
|
+
startTime,
|
|
25
|
+
endTime,
|
|
26
|
+
done,
|
|
27
|
+
now,
|
|
28
|
+
runMeta,
|
|
29
|
+
className
|
|
30
|
+
}) {
|
|
31
|
+
const t = totals(tests);
|
|
32
|
+
const clock = now ?? 0;
|
|
33
|
+
const end = done && endTime ? endTime : clock;
|
|
34
|
+
const elapsed = startTime && end >= startTime ? `${((end - startTime) / 1e3).toFixed(1)}s` : "";
|
|
35
|
+
const segments = [
|
|
36
|
+
{ count: t.passed, color: "bg-green-500", label: "passed" },
|
|
37
|
+
{ count: t.skipped, color: "bg-yellow-400", label: "skipped" },
|
|
38
|
+
{ count: t.failed, color: "bg-red-500", label: "failed" },
|
|
39
|
+
{ count: t.timedout, color: "bg-amber-500", label: "timed out" },
|
|
40
|
+
{ count: t.running, color: "bg-blue-400", label: "running" },
|
|
41
|
+
{ count: t.pending, color: "bg-muted-foreground/40", label: "queued" }
|
|
42
|
+
];
|
|
43
|
+
const chips = [
|
|
44
|
+
{ n: t.passed, label: "passed", tone: "text-green-600 dark:text-green-400" },
|
|
45
|
+
{ n: t.failed, label: "failed", tone: "text-red-600 dark:text-red-400" },
|
|
46
|
+
{ n: t.timedout, label: "timed out", tone: "text-amber-600 dark:text-amber-400" },
|
|
47
|
+
{ n: t.skipped, label: "skipped", tone: "text-yellow-700 dark:text-yellow-500" },
|
|
48
|
+
{ n: t.running, label: "running", tone: "text-blue-600 dark:text-blue-400" },
|
|
49
|
+
{ n: t.pending, label: "queued", tone: "text-muted-foreground" }
|
|
50
|
+
];
|
|
51
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-[18rem] flex-col items-end gap-1.5", className), children: [
|
|
52
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-end gap-x-3 gap-y-0.5 text-sm text-muted-foreground", children: [
|
|
53
|
+
/* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground", children: [
|
|
54
|
+
t.total,
|
|
55
|
+
" tests",
|
|
56
|
+
(runMeta == null ? void 0 : runMeta.kind) === "rerun" ? ` · rerun #${runMeta.sequence}` : ""
|
|
57
|
+
] }),
|
|
58
|
+
chips.filter((c) => c.n > 0).map((c) => /* @__PURE__ */ jsxs("span", { className: c.tone, children: [
|
|
59
|
+
c.n,
|
|
60
|
+
" ",
|
|
61
|
+
c.label
|
|
62
|
+
] }, c.label)),
|
|
63
|
+
elapsed && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
64
|
+
/* @__PURE__ */ jsx(Icon, { icon: UiClock, className: "text-xs" }),
|
|
65
|
+
elapsed
|
|
66
|
+
] }),
|
|
67
|
+
done ? /* @__PURE__ */ jsx(Icon, { icon: UiCheck, className: "text-green-600 dark:text-green-400" }) : /* @__PURE__ */ jsx(Icon, { icon: UiLoader, className: "animate-spin text-blue-500" })
|
|
68
|
+
] }),
|
|
69
|
+
t.total > 0 && /* @__PURE__ */ jsxs("div", { className: "flex w-72 items-center gap-2", children: [
|
|
70
|
+
/* @__PURE__ */ jsx(ProgressBar, { className: "flex-1", segments, total: t.total, height: "h-2" }),
|
|
71
|
+
/* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs font-medium text-green-600 dark:text-green-400", children: [
|
|
72
|
+
pct(t.passed, t.total),
|
|
73
|
+
"%"
|
|
74
|
+
] })
|
|
75
|
+
] })
|
|
76
|
+
] });
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
TestRunSummary
|
|
80
|
+
};
|
|
81
|
+
//# sourceMappingURL=TestRunSummary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestRunSummary.js","sources":["../../../src/data/test-runner/TestRunSummary.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { ProgressBar, type ProgressSegment } from \"../ProgressBar\";\nimport { cn } from \"../../lib/utils\";\nimport { UiCheck, UiClock, UiLoader } from \"../../icons\";\nimport type { RunMeta, Test } from \"./types\";\nimport { sumNonTaskTests, type StatusCounts } from \"./status\";\n\nexport type TestRunSummaryProps = {\n tests: Test[];\n /** Run start, epoch ms (host-provided). */\n startTime?: number | null | undefined;\n /** Run end, epoch ms; falls back to `now` while running. */\n endTime?: number | null | undefined;\n done: boolean;\n /** Injected clock (epoch ms) so the component stays pure. */\n now?: number | undefined;\n runMeta?: RunMeta | undefined;\n className?: string | undefined;\n};\n\nfunction totals(tests: Test[]): StatusCounts {\n return tests.reduce(\n (acc, t) => {\n const s = sumNonTaskTests(t);\n (Object.keys(acc) as (keyof StatusCounts)[]).forEach((k) => (acc[k] += s[k]));\n return acc;\n },\n { total: 0, passed: 0, failed: 0, warned: 0, skipped: 0, pending: 0, running: 0, timedout: 0 },\n );\n}\n\nfunction pct(part: number, total: number): number {\n return total > 0 ? Math.round((part / total) * 100) : 0;\n}\n\n/**\n * Header summary: per-status counts, elapsed time, a stacked progress bar, and\n * pass/fail/pending gauge cards. Pure — elapsed time comes from injected `now`.\n */\nexport function TestRunSummary({\n tests,\n startTime,\n endTime,\n done,\n now,\n runMeta,\n className,\n}: TestRunSummaryProps) {\n const t = totals(tests);\n const clock = now ?? 0;\n const end = done && endTime ? endTime : clock;\n const elapsed = startTime && end >= startTime ? `${((end - startTime) / 1000).toFixed(1)}s` : \"\";\n\n const segments: ProgressSegment[] = [\n { count: t.passed, color: \"bg-green-500\", label: \"passed\" },\n { count: t.skipped, color: \"bg-yellow-400\", label: \"skipped\" },\n { count: t.failed, color: \"bg-red-500\", label: \"failed\" },\n { count: t.timedout, color: \"bg-amber-500\", label: \"timed out\" },\n { count: t.running, color: \"bg-blue-400\", label: \"running\" },\n { count: t.pending, color: \"bg-muted-foreground/40\", label: \"queued\" },\n ];\n\n // Each status appears once, as a coloured count chip — no separate gauge-card\n // row repeating the same numbers as percentages.\n const chips: { n: number; label: string; tone: string }[] = [\n { n: t.passed, label: \"passed\", tone: \"text-green-600 dark:text-green-400\" },\n { n: t.failed, label: \"failed\", tone: \"text-red-600 dark:text-red-400\" },\n { n: t.timedout, label: \"timed out\", tone: \"text-amber-600 dark:text-amber-400\" },\n { n: t.skipped, label: \"skipped\", tone: \"text-yellow-700 dark:text-yellow-500\" },\n { n: t.running, label: \"running\", tone: \"text-blue-600 dark:text-blue-400\" },\n { n: t.pending, label: \"queued\", tone: \"text-muted-foreground\" },\n ];\n\n return (\n <div className={cn(\"flex min-w-[18rem] flex-col items-end gap-1.5\", className)}>\n <div className=\"flex flex-wrap items-center justify-end gap-x-3 gap-y-0.5 text-sm text-muted-foreground\">\n <span className=\"font-medium text-foreground\">\n {t.total} tests\n {runMeta?.kind === \"rerun\" ? ` · rerun #${runMeta.sequence}` : \"\"}\n </span>\n {chips\n .filter((c) => c.n > 0)\n .map((c) => (\n <span key={c.label} className={c.tone}>\n {c.n} {c.label}\n </span>\n ))}\n {elapsed && (\n <span className=\"inline-flex items-center gap-1\">\n <Icon icon={UiClock} className=\"text-xs\" />\n {elapsed}\n </span>\n )}\n {done ? (\n <Icon icon={UiCheck} className=\"text-green-600 dark:text-green-400\" />\n ) : (\n <Icon icon={UiLoader} className=\"animate-spin text-blue-500\" />\n )}\n </div>\n\n {t.total > 0 && (\n <div className=\"flex w-72 items-center gap-2\">\n <ProgressBar className=\"flex-1\" segments={segments} total={t.total} height=\"h-2\" />\n <span className=\"shrink-0 text-xs font-medium text-green-600 dark:text-green-400\">\n {pct(t.passed, t.total)}%\n </span>\n </div>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAoBA,SAAS,OAAO,OAA6B;AAC3C,SAAO,MAAM;AAAA,IACX,CAAC,KAAK,MAAM;AACV,YAAM,IAAI,gBAAgB,CAAC;AAC1B,aAAO,KAAK,GAAG,EAA6B,QAAQ,CAAC,MAAO,IAAI,CAAC,KAAK,EAAE,CAAC,CAAE;AAC5E,aAAO;AAAA,IACT;AAAA,IACA,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,EAAA;AAAA,EAAE;AAEjG;AAEA,SAAS,IAAI,MAAc,OAAuB;AAChD,SAAO,QAAQ,IAAI,KAAK,MAAO,OAAO,QAAS,GAAG,IAAI;AACxD;AAMO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,IAAI,OAAO,KAAK;AACtB,QAAM,QAAQ,OAAO;AACrB,QAAM,MAAM,QAAQ,UAAU,UAAU;AACxC,QAAM,UAAU,aAAa,OAAO,YAAY,KAAK,MAAM,aAAa,KAAM,QAAQ,CAAC,CAAC,MAAM;AAE9F,QAAM,WAA8B;AAAA,IAClC,EAAE,OAAO,EAAE,QAAQ,OAAO,gBAAgB,OAAO,SAAA;AAAA,IACjD,EAAE,OAAO,EAAE,SAAS,OAAO,iBAAiB,OAAO,UAAA;AAAA,IACnD,EAAE,OAAO,EAAE,QAAQ,OAAO,cAAc,OAAO,SAAA;AAAA,IAC/C,EAAE,OAAO,EAAE,UAAU,OAAO,gBAAgB,OAAO,YAAA;AAAA,IACnD,EAAE,OAAO,EAAE,SAAS,OAAO,eAAe,OAAO,UAAA;AAAA,IACjD,EAAE,OAAO,EAAE,SAAS,OAAO,0BAA0B,OAAO,SAAA;AAAA,EAAS;AAKvE,QAAM,QAAsD;AAAA,IAC1D,EAAE,GAAG,EAAE,QAAQ,OAAO,UAAU,MAAM,qCAAA;AAAA,IACtC,EAAE,GAAG,EAAE,QAAQ,OAAO,UAAU,MAAM,iCAAA;AAAA,IACtC,EAAE,GAAG,EAAE,UAAU,OAAO,aAAa,MAAM,qCAAA;AAAA,IAC3C,EAAE,GAAG,EAAE,SAAS,OAAO,WAAW,MAAM,uCAAA;AAAA,IACxC,EAAE,GAAG,EAAE,SAAS,OAAO,WAAW,MAAM,mCAAA;AAAA,IACxC,EAAE,GAAG,EAAE,SAAS,OAAO,UAAU,MAAM,wBAAA;AAAA,EAAwB;AAGjE,8BACG,OAAA,EAAI,WAAW,GAAG,iDAAiD,SAAS,GAC3E,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,2FACb,UAAA;AAAA,MAAA,qBAAC,QAAA,EAAK,WAAU,+BACb,UAAA;AAAA,QAAA,EAAE;AAAA,QAAM;AAAA,SACR,mCAAS,UAAS,UAAU,aAAa,QAAQ,QAAQ,KAAK;AAAA,MAAA,GACjE;AAAA,MACC,MACE,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,EACrB,IAAI,CAAC,MACJ,qBAAC,QAAA,EAAmB,WAAW,EAAE,MAC9B,UAAA;AAAA,QAAA,EAAE;AAAA,QAAE;AAAA,QAAE,EAAE;AAAA,MAAA,KADA,EAAE,KAEb,CACD;AAAA,MACF,WACC,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,MAAA,EAAK,MAAM,SAAS,WAAU,WAAU;AAAA,QACxC;AAAA,MAAA,GACH;AAAA,MAED,OACC,oBAAC,MAAA,EAAK,MAAM,SAAS,WAAU,qCAAA,CAAqC,IAEpE,oBAAC,MAAA,EAAK,MAAM,UAAU,WAAU,6BAAA,CAA6B;AAAA,IAAA,GAEjE;AAAA,IAEC,EAAE,QAAQ,KACT,qBAAC,OAAA,EAAI,WAAU,gCACb,UAAA;AAAA,MAAA,oBAAC,aAAA,EAAY,WAAU,UAAS,UAAoB,OAAO,EAAE,OAAO,QAAO,MAAA,CAAM;AAAA,MACjF,qBAAC,QAAA,EAAK,WAAU,mEACb,UAAA;AAAA,QAAA,IAAI,EAAE,QAAQ,EAAE,KAAK;AAAA,QAAE;AAAA,MAAA,EAAA,CAC1B;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
|