@flanksource/clicky-ui 0.2.12 → 0.2.14

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.
Files changed (173) hide show
  1. package/dist/components/Combobox.cjs +47 -33
  2. package/dist/components/Combobox.cjs.map +1 -1
  3. package/dist/components/Combobox.d.ts +12 -0
  4. package/dist/components/Combobox.d.ts.map +1 -1
  5. package/dist/components/Combobox.js +48 -34
  6. package/dist/components/Combobox.js.map +1 -1
  7. package/dist/components/IconButton.cjs +26 -0
  8. package/dist/components/IconButton.cjs.map +1 -0
  9. package/dist/components/IconButton.d.ts +25 -0
  10. package/dist/components/IconButton.d.ts.map +1 -0
  11. package/dist/components/IconButton.js +26 -0
  12. package/dist/components/IconButton.js.map +1 -0
  13. package/dist/components/SecretKeySelector.cjs +151 -0
  14. package/dist/components/SecretKeySelector.cjs.map +1 -0
  15. package/dist/components/SecretKeySelector.d.ts +51 -0
  16. package/dist/components/SecretKeySelector.d.ts.map +1 -0
  17. package/dist/components/SecretKeySelector.js +151 -0
  18. package/dist/components/SecretKeySelector.js.map +1 -0
  19. package/dist/components/WorkloadPicker.cjs +144 -0
  20. package/dist/components/WorkloadPicker.cjs.map +1 -0
  21. package/dist/components/WorkloadPicker.d.ts +52 -0
  22. package/dist/components/WorkloadPicker.d.ts.map +1 -0
  23. package/dist/components/WorkloadPicker.js +144 -0
  24. package/dist/components/WorkloadPicker.js.map +1 -0
  25. package/dist/components/icon-menu-picker.cjs +83 -112
  26. package/dist/components/icon-menu-picker.cjs.map +1 -1
  27. package/dist/components/icon-menu-picker.d.ts.map +1 -1
  28. package/dist/components/icon-menu-picker.js +85 -114
  29. package/dist/components/icon-menu-picker.js.map +1 -1
  30. package/dist/components.cjs +10 -0
  31. package/dist/components.cjs.map +1 -1
  32. package/dist/components.d.ts +3 -0
  33. package/dist/components.d.ts.map +1 -1
  34. package/dist/components.js +11 -1
  35. package/dist/components.js.map +1 -1
  36. package/dist/data/Clicky.cjs +1 -1
  37. package/dist/data/Clicky.cjs.map +1 -1
  38. package/dist/data/Clicky.js +1 -1
  39. package/dist/data/Clicky.js.map +1 -1
  40. package/dist/data/TimeseriesCoreBars.cjs +8 -1
  41. package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
  42. package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
  43. package/dist/data/TimeseriesCoreBars.js +8 -1
  44. package/dist/data/TimeseriesCoreBars.js.map +1 -1
  45. package/dist/data/Version.cjs +42 -0
  46. package/dist/data/Version.cjs.map +1 -0
  47. package/dist/data/Version.d.ts +36 -0
  48. package/dist/data/Version.d.ts.map +1 -0
  49. package/dist/data/Version.js +42 -0
  50. package/dist/data/Version.js.map +1 -0
  51. package/dist/data/test-runner/TestDetailPanel.cjs +165 -0
  52. package/dist/data/test-runner/TestDetailPanel.cjs.map +1 -0
  53. package/dist/data/test-runner/TestDetailPanel.d.ts +7 -0
  54. package/dist/data/test-runner/TestDetailPanel.d.ts.map +1 -0
  55. package/dist/data/test-runner/TestDetailPanel.js +165 -0
  56. package/dist/data/test-runner/TestDetailPanel.js.map +1 -0
  57. package/dist/data/test-runner/TestFailureDetail.cjs +78 -0
  58. package/dist/data/test-runner/TestFailureDetail.cjs.map +1 -0
  59. package/dist/data/test-runner/TestFailureDetail.d.ts +14 -0
  60. package/dist/data/test-runner/TestFailureDetail.d.ts.map +1 -0
  61. package/dist/data/test-runner/TestFailureDetail.js +78 -0
  62. package/dist/data/test-runner/TestFailureDetail.js.map +1 -0
  63. package/dist/data/test-runner/TestFilterBar.cjs +108 -0
  64. package/dist/data/test-runner/TestFilterBar.cjs.map +1 -0
  65. package/dist/data/test-runner/TestFilterBar.d.ts +15 -0
  66. package/dist/data/test-runner/TestFilterBar.d.ts.map +1 -0
  67. package/dist/data/test-runner/TestFilterBar.js +108 -0
  68. package/dist/data/test-runner/TestFilterBar.js.map +1 -0
  69. package/dist/data/test-runner/TestRunSummary.cjs +81 -0
  70. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -0
  71. package/dist/data/test-runner/TestRunSummary.d.ts +19 -0
  72. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -0
  73. package/dist/data/test-runner/TestRunSummary.js +81 -0
  74. package/dist/data/test-runner/TestRunSummary.js.map +1 -0
  75. package/dist/data/test-runner/TestRunner.cjs +143 -0
  76. package/dist/data/test-runner/TestRunner.cjs.map +1 -0
  77. package/dist/data/test-runner/TestRunner.d.ts +42 -0
  78. package/dist/data/test-runner/TestRunner.d.ts.map +1 -0
  79. package/dist/data/test-runner/TestRunner.fixtures.d.ts +26 -0
  80. package/dist/data/test-runner/TestRunner.fixtures.d.ts.map +1 -0
  81. package/dist/data/test-runner/TestRunner.js +143 -0
  82. package/dist/data/test-runner/TestRunner.js.map +1 -0
  83. package/dist/data/test-runner/TestTree.cjs +42 -0
  84. package/dist/data/test-runner/TestTree.cjs.map +1 -0
  85. package/dist/data/test-runner/TestTree.d.ts +9 -0
  86. package/dist/data/test-runner/TestTree.d.ts.map +1 -0
  87. package/dist/data/test-runner/TestTree.js +42 -0
  88. package/dist/data/test-runner/TestTree.js.map +1 -0
  89. package/dist/data/test-runner/TestTreeNode.cjs +99 -0
  90. package/dist/data/test-runner/TestTreeNode.cjs.map +1 -0
  91. package/dist/data/test-runner/TestTreeNode.d.ts +12 -0
  92. package/dist/data/test-runner/TestTreeNode.d.ts.map +1 -0
  93. package/dist/data/test-runner/TestTreeNode.js +99 -0
  94. package/dist/data/test-runner/TestTreeNode.js.map +1 -0
  95. package/dist/data/test-runner/adapter.cjs +57 -0
  96. package/dist/data/test-runner/adapter.cjs.map +1 -0
  97. package/dist/data/test-runner/adapter.d.ts +60 -0
  98. package/dist/data/test-runner/adapter.d.ts.map +1 -0
  99. package/dist/data/test-runner/adapter.js +57 -0
  100. package/dist/data/test-runner/adapter.js.map +1 -0
  101. package/dist/data/test-runner/context.cjs +15 -0
  102. package/dist/data/test-runner/context.cjs.map +1 -0
  103. package/dist/data/test-runner/context.d.ts +40 -0
  104. package/dist/data/test-runner/context.d.ts.map +1 -0
  105. package/dist/data/test-runner/context.js +15 -0
  106. package/dist/data/test-runner/context.js.map +1 -0
  107. package/dist/data/test-runner/filterState.cjs +47 -0
  108. package/dist/data/test-runner/filterState.cjs.map +1 -0
  109. package/dist/data/test-runner/filterState.d.ts +14 -0
  110. package/dist/data/test-runner/filterState.d.ts.map +1 -0
  111. package/dist/data/test-runner/filterState.js +47 -0
  112. package/dist/data/test-runner/filterState.js.map +1 -0
  113. package/dist/data/test-runner/frameworkIcon.cjs +27 -0
  114. package/dist/data/test-runner/frameworkIcon.cjs.map +1 -0
  115. package/dist/data/test-runner/frameworkIcon.d.ts +3 -0
  116. package/dist/data/test-runner/frameworkIcon.d.ts.map +1 -0
  117. package/dist/data/test-runner/frameworkIcon.js +27 -0
  118. package/dist/data/test-runner/frameworkIcon.js.map +1 -0
  119. package/dist/data/test-runner/index.d.ts +14 -0
  120. package/dist/data/test-runner/index.d.ts.map +1 -0
  121. package/dist/data/test-runner/status.cjs +234 -0
  122. package/dist/data/test-runner/status.cjs.map +1 -0
  123. package/dist/data/test-runner/status.d.ts +52 -0
  124. package/dist/data/test-runner/status.d.ts.map +1 -0
  125. package/dist/data/test-runner/status.js +234 -0
  126. package/dist/data/test-runner/status.js.map +1 -0
  127. package/dist/data/test-runner/types.d.ts +148 -0
  128. package/dist/data/test-runner/types.d.ts.map +1 -0
  129. package/dist/data/version-info.cjs +26 -0
  130. package/dist/data/version-info.cjs.map +1 -0
  131. package/dist/data/version-info.d.ts +11 -0
  132. package/dist/data/version-info.d.ts.map +1 -0
  133. package/dist/data/version-info.js +26 -0
  134. package/dist/data/version-info.js.map +1 -0
  135. package/dist/data.cjs +49 -0
  136. package/dist/data.cjs.map +1 -1
  137. package/dist/data.d.ts +3 -0
  138. package/dist/data.d.ts.map +1 -1
  139. package/dist/data.js +50 -1
  140. package/dist/data.js.map +1 -1
  141. package/dist/index.cjs +59 -0
  142. package/dist/index.cjs.map +1 -1
  143. package/dist/index.js +60 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/overlay/DropdownMenu.cjs +57 -30
  146. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  147. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  148. package/dist/overlay/DropdownMenu.js +57 -30
  149. package/dist/overlay/DropdownMenu.js.map +1 -1
  150. package/dist/overlay/Modal.cjs +62 -6
  151. package/dist/overlay/Modal.cjs.map +1 -1
  152. package/dist/overlay/Modal.d.ts +19 -1
  153. package/dist/overlay/Modal.d.ts.map +1 -1
  154. package/dist/overlay/Modal.js +62 -6
  155. package/dist/overlay/Modal.js.map +1 -1
  156. package/dist/rpc/CommandForm.cjs +2 -2
  157. package/dist/rpc/CommandForm.cjs.map +1 -1
  158. package/dist/rpc/CommandForm.d.ts.map +1 -1
  159. package/dist/rpc/CommandForm.js +3 -3
  160. package/dist/rpc/CommandForm.js.map +1 -1
  161. package/dist/rpc/formMetadata.cjs +5 -4
  162. package/dist/rpc/formMetadata.cjs.map +1 -1
  163. package/dist/rpc/formMetadata.d.ts.map +1 -1
  164. package/dist/rpc/formMetadata.js +6 -5
  165. package/dist/rpc/formMetadata.js.map +1 -1
  166. package/dist/rpc/types.cjs +4 -0
  167. package/dist/rpc/types.cjs.map +1 -1
  168. package/dist/rpc/types.d.ts +10 -0
  169. package/dist/rpc/types.d.ts.map +1 -1
  170. package/dist/rpc/types.js +5 -1
  171. package/dist/rpc/types.js.map +1 -1
  172. package/dist/styles.css +1 -1
  173. package/package.json +3 -1
@@ -0,0 +1,143 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { Icon } from "../Icon.js";
4
+ import { SplitPane } from "../../layout/SplitPane.js";
5
+ import { cn } from "../../lib/utils.js";
6
+ import { collectFrameworks, sumNonTaskTests } from "./status.js";
7
+ import { TestNodeAdapterRegistry, createTestRunnerRegistry } from "./adapter.js";
8
+ import { TestRunnerProvider } from "./context.js";
9
+ import { TestTree } from "./TestTree.js";
10
+ import { TestDetailPanel } from "./TestDetailPanel.js";
11
+ import { TestRunSummary } from "./TestRunSummary.js";
12
+ import { TestFilterBar } from "./TestFilterBar.js";
13
+ import { UiBeaker } from "../../icons/components/UiBeaker.js";
14
+ function counts(tests) {
15
+ return tests.reduce(
16
+ (acc, t) => {
17
+ const s = sumNonTaskTests(t);
18
+ Object.keys(acc).forEach((k) => acc[k] += s[k]);
19
+ return acc;
20
+ },
21
+ { total: 0, passed: 0, failed: 0, warned: 0, skipped: 0, pending: 0, running: 0, timedout: 0 }
22
+ );
23
+ }
24
+ function toRegistry(adapters) {
25
+ if (adapters instanceof TestNodeAdapterRegistry) return adapters;
26
+ return createTestRunnerRegistry(adapters ?? []);
27
+ }
28
+ function TestRunner({
29
+ tests,
30
+ selected,
31
+ filters,
32
+ expandAll,
33
+ done,
34
+ runMeta,
35
+ status,
36
+ statusText,
37
+ now,
38
+ busy,
39
+ onSelect,
40
+ onFiltersChange,
41
+ onExpandAllChange,
42
+ onRerun,
43
+ onStop,
44
+ adapters,
45
+ headerSlot,
46
+ defaultSplit = 45,
47
+ startTime,
48
+ endTime,
49
+ className
50
+ }) {
51
+ const registry = useMemo(() => toRegistry(adapters), [adapters]);
52
+ const ctx = useMemo(
53
+ () => ({
54
+ tests,
55
+ selected,
56
+ runMeta,
57
+ status,
58
+ done,
59
+ now,
60
+ filters,
61
+ expandAll,
62
+ busy: busy ?? {},
63
+ onSelect,
64
+ onFiltersChange,
65
+ onExpandAllChange,
66
+ onRerun,
67
+ onStop,
68
+ adapters: registry
69
+ }),
70
+ [
71
+ tests,
72
+ selected,
73
+ runMeta,
74
+ status,
75
+ done,
76
+ now,
77
+ filters,
78
+ expandAll,
79
+ busy,
80
+ onSelect,
81
+ onFiltersChange,
82
+ onExpandAllChange,
83
+ onRerun,
84
+ onStop,
85
+ registry
86
+ ]
87
+ );
88
+ const frameworks = useMemo(() => collectFrameworks(tests), [tests]);
89
+ const filterCounts = useMemo(() => counts(tests), [tests]);
90
+ const hasContent = tests.length > 0;
91
+ return /* @__PURE__ */ jsx(TestRunnerProvider, { value: ctx, children: /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 flex-col bg-background", className), children: [
92
+ /* @__PURE__ */ jsxs("div", { className: "border-b border-border bg-card px-density-4 py-density-3", children: [
93
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
94
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
95
+ /* @__PURE__ */ jsxs("h1", { className: "inline-flex items-center gap-1.5 text-lg font-bold", children: [
96
+ /* @__PURE__ */ jsx(Icon, { icon: UiBeaker, className: "text-primary" }),
97
+ "Test Results"
98
+ ] }),
99
+ statusText && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: statusText })
100
+ ] }),
101
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
102
+ headerSlot,
103
+ /* @__PURE__ */ jsx(
104
+ TestRunSummary,
105
+ {
106
+ tests,
107
+ startTime,
108
+ endTime,
109
+ done,
110
+ now,
111
+ runMeta
112
+ }
113
+ )
114
+ ] })
115
+ ] }),
116
+ hasContent && /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
117
+ TestFilterBar,
118
+ {
119
+ filters,
120
+ onChange: onFiltersChange,
121
+ counts: filterCounts,
122
+ frameworks
123
+ }
124
+ ) })
125
+ ] }),
126
+ /* @__PURE__ */ jsx(
127
+ SplitPane,
128
+ {
129
+ className: "min-h-0 flex-1",
130
+ defaultSplit,
131
+ minLeft: 25,
132
+ minRight: 30,
133
+ leftClass: "border-r-2 border-border bg-muted/30",
134
+ left: /* @__PURE__ */ jsx(TestTree, {}),
135
+ right: /* @__PURE__ */ jsx(TestDetailPanel, {})
136
+ }
137
+ )
138
+ ] }) });
139
+ }
140
+ export {
141
+ TestRunner
142
+ };
143
+ //# sourceMappingURL=TestRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestRunner.js","sources":["../../../src/data/test-runner/TestRunner.tsx"],"sourcesContent":["import { useMemo, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { SplitPane } from \"../../layout/SplitPane\";\nimport { cn } from \"../../lib/utils\";\nimport { UiBeaker } from \"../../icons\";\nimport type { RunMeta, SnapshotStatus, Test } from \"./types\";\nimport { collectFrameworks, sumNonTaskTests, type StatusCounts } from \"./status\";\nimport { type TestFilters } from \"./filterState\";\nimport {\n createTestRunnerRegistry,\n type TestNodeAdapter,\n TestNodeAdapterRegistry,\n} from \"./adapter\";\nimport { TestRunnerProvider, type TestRunnerContext } from \"./context\";\nimport { TestTree } from \"./TestTree\";\nimport { TestDetailPanel } from \"./TestDetailPanel\";\nimport { TestRunSummary } from \"./TestRunSummary\";\nimport { TestFilterBar } from \"./TestFilterBar\";\n\nexport type TestRunnerProps = {\n tests: Test[];\n selected: Test | null;\n filters: TestFilters;\n expandAll: boolean | null;\n done: boolean;\n runMeta?: RunMeta;\n status?: SnapshotStatus;\n /** Free-form status line shown next to the title (e.g. \"Running tests...\"). */\n statusText?: string;\n now?: number;\n busy?: { rerun?: boolean; stop?: boolean };\n\n onSelect: (node: Test | null) => void;\n onFiltersChange: (next: TestFilters) => void;\n onExpandAllChange: (next: boolean | null) => void;\n onRerun?: (node: Test) => void;\n onStop?: (node: Test) => void;\n\n /** Domain node adapters, or a pre-built registry. Defaults to default-only. */\n adapters?: TestNodeAdapter[] | TestNodeAdapterRegistry;\n /** Extra header content (e.g. export/download controls owned by the host). */\n headerSlot?: ReactNode;\n /** Initial left-pane width percentage. */\n defaultSplit?: number;\n startTime?: number | null;\n endTime?: number | null;\n className?: string;\n};\n\nfunction counts(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 toRegistry(\n adapters: TestNodeAdapter[] | TestNodeAdapterRegistry | undefined,\n): TestNodeAdapterRegistry {\n if (adapters instanceof TestNodeAdapterRegistry) return adapters;\n return createTestRunnerRegistry(adapters ?? []);\n}\n\n/**\n * Top-level, pure-presentational test runner: a summary/filter header over a\n * resizable tree + detail split. All data and handlers flow in via props and\n * are exposed to sub-components through a single `TestRunnerContext`. Domain\n * rendering is pluggable via node adapters; no fetch/SSE/routing lives here.\n */\nexport function TestRunner({\n tests,\n selected,\n filters,\n expandAll,\n done,\n runMeta,\n status,\n statusText,\n now,\n busy,\n onSelect,\n onFiltersChange,\n onExpandAllChange,\n onRerun,\n onStop,\n adapters,\n headerSlot,\n defaultSplit = 45,\n startTime,\n endTime,\n className,\n}: TestRunnerProps) {\n const registry = useMemo(() => toRegistry(adapters), [adapters]);\n\n const ctx: TestRunnerContext = useMemo(\n () => ({\n tests,\n selected,\n runMeta,\n status,\n done,\n now,\n filters,\n expandAll,\n busy: busy ?? {},\n onSelect,\n onFiltersChange,\n onExpandAllChange,\n onRerun,\n onStop,\n adapters: registry,\n }),\n [\n tests,\n selected,\n runMeta,\n status,\n done,\n now,\n filters,\n expandAll,\n busy,\n onSelect,\n onFiltersChange,\n onExpandAllChange,\n onRerun,\n onStop,\n registry,\n ],\n );\n\n const frameworks = useMemo(() => collectFrameworks(tests), [tests]);\n const filterCounts = useMemo(() => counts(tests), [tests]);\n const hasContent = tests.length > 0;\n\n return (\n <TestRunnerProvider value={ctx}>\n <div className={cn(\"flex h-full min-h-0 flex-col bg-background\", className)}>\n <div className=\"border-b border-border bg-card px-density-4 py-density-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <h1 className=\"inline-flex items-center gap-1.5 text-lg font-bold\">\n <Icon icon={UiBeaker} className=\"text-primary\" />\n Test Results\n </h1>\n {statusText && <span className=\"text-sm text-muted-foreground\">{statusText}</span>}\n </div>\n <div className=\"flex items-center gap-3\">\n {headerSlot}\n <TestRunSummary\n tests={tests}\n startTime={startTime}\n endTime={endTime}\n done={done}\n now={now}\n runMeta={runMeta}\n />\n </div>\n </div>\n {hasContent && (\n <div className=\"mt-2\">\n <TestFilterBar\n filters={filters}\n onChange={onFiltersChange}\n counts={filterCounts}\n frameworks={frameworks}\n />\n </div>\n )}\n </div>\n\n <SplitPane\n className=\"min-h-0 flex-1\"\n defaultSplit={defaultSplit}\n minLeft={25}\n minRight={30}\n leftClass=\"border-r-2 border-border bg-muted/30\"\n left={<TestTree />}\n right={<TestDetailPanel />}\n />\n </div>\n </TestRunnerProvider>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const Tree = require("../Tree.cjs");
5
+ const utils = require("../../lib/utils.cjs");
6
+ const context = require("./context.cjs");
7
+ const status = require("./status.cjs");
8
+ const TestTreeNode = require("./TestTreeNode.cjs");
9
+ const nodeKey = /* @__PURE__ */ (() => {
10
+ const cache = /* @__PURE__ */ new WeakMap();
11
+ let counter = 0;
12
+ return (node) => {
13
+ if (node.route_path) return node.route_path;
14
+ let key = cache.get(node);
15
+ if (!key) {
16
+ key = `tn-${counter++}-${node.name}`;
17
+ cache.set(node, key);
18
+ }
19
+ return key;
20
+ };
21
+ })();
22
+ function TestTree({ className }) {
23
+ const runner = context.useTestRunner();
24
+ return /* @__PURE__ */ jsxRuntime.jsx(
25
+ Tree.Tree,
26
+ {
27
+ className: utils.cn("min-h-0", className),
28
+ roots: runner.tests,
29
+ getKey: nodeKey,
30
+ getChildren: (node) => node.children,
31
+ selected: runner.selected,
32
+ onSelect: (node) => runner.onSelect(node === runner.selected ? null : node),
33
+ expandAll: runner.expandAll,
34
+ onExpandAllChange: runner.onExpandAllChange,
35
+ defaultOpen: (node, depth) => depth < 1 || status.hasFailed(node),
36
+ getSearchText: (node) => `${node.name} ${node.framework ?? ""}`,
37
+ renderRow: ({ node, selected }) => /* @__PURE__ */ jsxRuntime.jsx(TestTreeNode.TestTreeNode, { node, selected })
38
+ }
39
+ );
40
+ }
41
+ exports.TestTree = TestTree;
42
+ //# sourceMappingURL=TestTree.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTree.cjs","sources":["../../../src/data/test-runner/TestTree.tsx"],"sourcesContent":["import { Tree } from \"../Tree\";\nimport { cn } from \"../../lib/utils\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { hasFailed } from \"./status\";\nimport { TestTreeNode } from \"./TestTreeNode\";\n\nconst nodeKey = (() => {\n // Tests carry no guaranteed id; derive a stable per-render key from route_path\n // when present, else the node's name path. A WeakMap keeps keys stable across\n // re-renders for the same object identity.\n const cache = new WeakMap<Test, string>();\n let counter = 0;\n return (node: Test): string => {\n if (node.route_path) return node.route_path;\n let key = cache.get(node);\n if (!key) {\n key = `tn-${counter++}-${node.name}`;\n cache.set(node, key);\n }\n return key;\n };\n})();\n\n/**\n * The left pane: the test forest rendered through the clicky `Tree`. Selection,\n * handlers and adapters all come from the runner context; failing branches and\n * the top level open by default.\n */\nexport function TestTree({ className }: { className?: string }) {\n const runner = useTestRunner();\n return (\n <Tree<Test>\n className={cn(\"min-h-0\", className)}\n roots={runner.tests}\n getKey={nodeKey}\n getChildren={(node) => node.children}\n selected={runner.selected}\n onSelect={(node) => runner.onSelect(node === runner.selected ? null : node)}\n expandAll={runner.expandAll}\n onExpandAllChange={runner.onExpandAllChange}\n defaultOpen={(node, depth) => depth < 1 || hasFailed(node)}\n getSearchText={(node) => `${node.name} ${node.framework ?? \"\"}`}\n renderRow={({ node, selected }) => <TestTreeNode node={node} selected={selected} />}\n />\n );\n}\n"],"names":["useTestRunner","jsx","Tree","cn","hasFailed","TestTreeNode"],"mappings":";;;;;;;;AAOA,MAAM,UAAW,uBAAM;AAIrB,QAAM,4BAAY,QAAA;AAClB,MAAI,UAAU;AACd,SAAO,CAAC,SAAuB;AAC7B,QAAI,KAAK,WAAY,QAAO,KAAK;AACjC,QAAI,MAAM,MAAM,IAAI,IAAI;AACxB,QAAI,CAAC,KAAK;AACR,YAAM,MAAM,SAAS,IAAI,KAAK,IAAI;AAClC,YAAM,IAAI,MAAM,GAAG;AAAA,IACrB;AACA,WAAO;AAAA,EACT;AACF,GAAA;AAOO,SAAS,SAAS,EAAE,aAAqC;AAC9D,QAAM,SAASA,QAAAA,cAAA;AACf,SACEC,2BAAAA;AAAAA,IAACC,KAAAA;AAAAA,IAAA;AAAA,MACC,WAAWC,MAAAA,GAAG,WAAW,SAAS;AAAA,MAClC,OAAO,OAAO;AAAA,MACd,QAAQ;AAAA,MACR,aAAa,CAAC,SAAS,KAAK;AAAA,MAC5B,UAAU,OAAO;AAAA,MACjB,UAAU,CAAC,SAAS,OAAO,SAAS,SAAS,OAAO,WAAW,OAAO,IAAI;AAAA,MAC1E,WAAW,OAAO;AAAA,MAClB,mBAAmB,OAAO;AAAA,MAC1B,aAAa,CAAC,MAAM,UAAU,QAAQ,KAAKC,OAAAA,UAAU,IAAI;AAAA,MACzD,eAAe,CAAC,SAAS,GAAG,KAAK,IAAI,IAAI,KAAK,aAAa,EAAE;AAAA,MAC7D,WAAW,CAAC,EAAE,MAAM,eAAeH,2BAAAA,IAACI,aAAAA,cAAA,EAAa,MAAY,SAAA,CAAoB;AAAA,IAAA;AAAA,EAAA;AAGvF;;"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The left pane: the test forest rendered through the clicky `Tree`. Selection,
3
+ * handlers and adapters all come from the runner context; failing branches and
4
+ * the top level open by default.
5
+ */
6
+ export declare function TestTree({ className }: {
7
+ className?: string;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=TestTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTree.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestTree.tsx"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,2CAiB7D"}
@@ -0,0 +1,42 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Tree } from "../Tree.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { useTestRunner } from "./context.js";
5
+ import { hasFailed } from "./status.js";
6
+ import { TestTreeNode } from "./TestTreeNode.js";
7
+ const nodeKey = /* @__PURE__ */ (() => {
8
+ const cache = /* @__PURE__ */ new WeakMap();
9
+ let counter = 0;
10
+ return (node) => {
11
+ if (node.route_path) return node.route_path;
12
+ let key = cache.get(node);
13
+ if (!key) {
14
+ key = `tn-${counter++}-${node.name}`;
15
+ cache.set(node, key);
16
+ }
17
+ return key;
18
+ };
19
+ })();
20
+ function TestTree({ className }) {
21
+ const runner = useTestRunner();
22
+ return /* @__PURE__ */ jsx(
23
+ Tree,
24
+ {
25
+ className: cn("min-h-0", className),
26
+ roots: runner.tests,
27
+ getKey: nodeKey,
28
+ getChildren: (node) => node.children,
29
+ selected: runner.selected,
30
+ onSelect: (node) => runner.onSelect(node === runner.selected ? null : node),
31
+ expandAll: runner.expandAll,
32
+ onExpandAllChange: runner.onExpandAllChange,
33
+ defaultOpen: (node, depth) => depth < 1 || hasFailed(node),
34
+ getSearchText: (node) => `${node.name} ${node.framework ?? ""}`,
35
+ renderRow: ({ node, selected }) => /* @__PURE__ */ jsx(TestTreeNode, { node, selected })
36
+ }
37
+ );
38
+ }
39
+ export {
40
+ TestTree
41
+ };
42
+ //# sourceMappingURL=TestTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTree.js","sources":["../../../src/data/test-runner/TestTree.tsx"],"sourcesContent":["import { Tree } from \"../Tree\";\nimport { cn } from \"../../lib/utils\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport { hasFailed } from \"./status\";\nimport { TestTreeNode } from \"./TestTreeNode\";\n\nconst nodeKey = (() => {\n // Tests carry no guaranteed id; derive a stable per-render key from route_path\n // when present, else the node's name path. A WeakMap keeps keys stable across\n // re-renders for the same object identity.\n const cache = new WeakMap<Test, string>();\n let counter = 0;\n return (node: Test): string => {\n if (node.route_path) return node.route_path;\n let key = cache.get(node);\n if (!key) {\n key = `tn-${counter++}-${node.name}`;\n cache.set(node, key);\n }\n return key;\n };\n})();\n\n/**\n * The left pane: the test forest rendered through the clicky `Tree`. Selection,\n * handlers and adapters all come from the runner context; failing branches and\n * the top level open by default.\n */\nexport function TestTree({ className }: { className?: string }) {\n const runner = useTestRunner();\n return (\n <Tree<Test>\n className={cn(\"min-h-0\", className)}\n roots={runner.tests}\n getKey={nodeKey}\n getChildren={(node) => node.children}\n selected={runner.selected}\n onSelect={(node) => runner.onSelect(node === runner.selected ? null : node)}\n expandAll={runner.expandAll}\n onExpandAllChange={runner.onExpandAllChange}\n defaultOpen={(node, depth) => depth < 1 || hasFailed(node)}\n getSearchText={(node) => `${node.name} ${node.framework ?? \"\"}`}\n renderRow={({ node, selected }) => <TestTreeNode node={node} selected={selected} />}\n />\n );\n}\n"],"names":[],"mappings":";;;;;;AAOA,MAAM,UAAW,uBAAM;AAIrB,QAAM,4BAAY,QAAA;AAClB,MAAI,UAAU;AACd,SAAO,CAAC,SAAuB;AAC7B,QAAI,KAAK,WAAY,QAAO,KAAK;AACjC,QAAI,MAAM,MAAM,IAAI,IAAI;AACxB,QAAI,CAAC,KAAK;AACR,YAAM,MAAM,SAAS,IAAI,KAAK,IAAI;AAClC,YAAM,IAAI,MAAM,GAAG;AAAA,IACrB;AACA,WAAO;AAAA,EACT;AACF,GAAA;AAOO,SAAS,SAAS,EAAE,aAAqC;AAC9D,QAAM,SAAS,cAAA;AACf,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAW,GAAG,WAAW,SAAS;AAAA,MAClC,OAAO,OAAO;AAAA,MACd,QAAQ;AAAA,MACR,aAAa,CAAC,SAAS,KAAK;AAAA,MAC5B,UAAU,OAAO;AAAA,MACjB,UAAU,CAAC,SAAS,OAAO,SAAS,SAAS,OAAO,WAAW,OAAO,IAAI;AAAA,MAC1E,WAAW,OAAO;AAAA,MAClB,mBAAmB,OAAO;AAAA,MAC1B,aAAa,CAAC,MAAM,UAAU,QAAQ,KAAK,UAAU,IAAI;AAAA,MACzD,eAAe,CAAC,SAAS,GAAG,KAAK,IAAI,IAAI,KAAK,aAAa,EAAE;AAAA,MAC7D,WAAW,CAAC,EAAE,MAAM,eAAe,oBAAC,cAAA,EAAa,MAAY,SAAA,CAAoB;AAAA,IAAA;AAAA,EAAA;AAGvF;"}
@@ -0,0 +1,99 @@
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 Badge = require("../Badge.cjs");
6
+ const IconButton = require("../../components/IconButton.cjs");
7
+ const utils = require("../../lib/utils.cjs");
8
+ const context = require("./context.cjs");
9
+ const status = require("./status.cjs");
10
+ const frameworkIcon = require("./frameworkIcon.cjs");
11
+ const UiStop = require("../../icons/components/UiStop.cjs");
12
+ const UiRefresh = require("../../icons/components/UiRefresh.cjs");
13
+ const NAME_TONE = {
14
+ failed: "text-red-600 dark:text-red-400",
15
+ timedout: "text-amber-600 dark:text-amber-400",
16
+ warned: "text-amber-600 dark:text-amber-400",
17
+ running: "text-blue-600 dark:text-blue-400",
18
+ skipped: "text-yellow-700 dark:text-yellow-500",
19
+ pending: "text-muted-foreground",
20
+ passed: "text-foreground"
21
+ };
22
+ function CountBadges({ node }) {
23
+ const s = status.sum(node);
24
+ if (s.total === 0) return null;
25
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
26
+ s.passed > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { tone: "success", variant: "solid", size: "xxs", count: s.passed }),
27
+ s.failed > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { tone: "danger", variant: "solid", size: "xxs", count: s.failed }),
28
+ s.timedout > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { tone: "warning", variant: "solid", size: "xxs", count: s.timedout }),
29
+ s.skipped > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { tone: "warning", variant: "soft", size: "xxs", count: s.skipped }),
30
+ s.running > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { tone: "info", variant: "solid", size: "xxs", count: s.running }),
31
+ s.pending > 0 && /* @__PURE__ */ jsxRuntime.jsx(Badge.Badge, { tone: "neutral", variant: "soft", size: "xxs", count: s.pending })
32
+ ] });
33
+ }
34
+ function TestTreeNode({ node, selected }) {
35
+ var _a, _b, _c;
36
+ const runner = context.useTestRunner();
37
+ const hasChildren = (((_a = node.children) == null ? void 0 : _a.length) ?? 0) > 0;
38
+ const status$1 = status.testStatus(node);
39
+ const fwIcon = frameworkIcon.frameworkIcon(node.framework);
40
+ const fwAdapter = runner.adapters.resolveFrameworkIcon(node, runner);
41
+ const fwNode = (_b = fwAdapter == null ? void 0 : fwAdapter.renderFrameworkIcon) == null ? void 0 : _b.call(fwAdapter, { node, runner });
42
+ const leadingAdapter = runner.adapters.resolveRowLeading(node, runner);
43
+ const dur = node.duration || (hasChildren ? status.totalDuration(node) : 0);
44
+ const canRerun = !!runner.onRerun && node.framework !== "task";
45
+ const canStop = !!runner.onStop && !!node.can_stop && !!node.task_id;
46
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
47
+ /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: status.statusIconFor(node), className: utils.cn("shrink-0", node.running && "animate-spin") }),
48
+ fwNode != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex shrink-0 items-center text-sm opacity-60", children: fwNode }) : fwIcon && /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { icon: fwIcon, className: "shrink-0 text-sm opacity-60" }),
49
+ (_c = leadingAdapter == null ? void 0 : leadingAdapter.renderRowLeading) == null ? void 0 : _c.call(leadingAdapter, { node, runner }),
50
+ /* @__PURE__ */ jsxRuntime.jsx(
51
+ "span",
52
+ {
53
+ className: utils.cn(
54
+ "truncate",
55
+ selected ? "font-semibold" : "font-medium",
56
+ status$1 && NAME_TONE[status$1] || "text-foreground"
57
+ ),
58
+ children: status.humanizeName(node.name, node.framework)
59
+ }
60
+ ),
61
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1" }),
62
+ node.progress && node.progress.total > 0 && !node.passed && !node.failed && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-blue-600 dark:text-blue-400", children: [
63
+ node.progress.phase && /* @__PURE__ */ jsxRuntime.jsx("span", { children: node.progress.phase }),
64
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tabular-nums", children: [
65
+ node.progress.done,
66
+ "/",
67
+ node.progress.total
68
+ ] })
69
+ ] }),
70
+ dur > 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: status.formatTestDuration(dur) }),
71
+ hasChildren && /* @__PURE__ */ jsxRuntime.jsx(CountBadges, { node }),
72
+ canStop && /* @__PURE__ */ jsxRuntime.jsx(
73
+ IconButton.IconButton,
74
+ {
75
+ icon: UiStop.UiStop,
76
+ label: "Stop",
77
+ disabled: runner.busy.stop,
78
+ onClick: (e) => {
79
+ e.stopPropagation();
80
+ runner.onStop(node);
81
+ }
82
+ }
83
+ ),
84
+ canRerun && /* @__PURE__ */ jsxRuntime.jsx(
85
+ IconButton.IconButton,
86
+ {
87
+ icon: UiRefresh.UiRefresh,
88
+ label: "Rerun",
89
+ disabled: runner.busy.rerun,
90
+ onClick: (e) => {
91
+ e.stopPropagation();
92
+ runner.onRerun(node);
93
+ }
94
+ }
95
+ )
96
+ ] });
97
+ }
98
+ exports.TestTreeNode = TestTreeNode;
99
+ //# sourceMappingURL=TestTreeNode.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTreeNode.cjs","sources":["../../../src/data/test-runner/TestTreeNode.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { Badge } from \"../Badge\";\nimport { IconButton } from \"../../components/IconButton\";\nimport { UiRefresh, UiStop } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport {\n formatTestDuration,\n humanizeName,\n statusIconFor,\n sum,\n testStatus,\n totalDuration,\n} from \"./status\";\nimport { frameworkIcon } from \"./frameworkIcon\";\n\nconst NAME_TONE: Record<string, string> = {\n failed: \"text-red-600 dark:text-red-400\",\n timedout: \"text-amber-600 dark:text-amber-400\",\n warned: \"text-amber-600 dark:text-amber-400\",\n running: \"text-blue-600 dark:text-blue-400\",\n skipped: \"text-yellow-700 dark:text-yellow-500\",\n pending: \"text-muted-foreground\",\n passed: \"text-foreground\",\n};\n\n/** Per-status roll-up count badges shown on a container row. */\nfunction CountBadges({ node }: { node: Test }) {\n const s = sum(node);\n if (s.total === 0) return null;\n return (\n <span className=\"flex shrink-0 items-center gap-1\">\n {s.passed > 0 && <Badge tone=\"success\" variant=\"solid\" size=\"xxs\" count={s.passed} />}\n {s.failed > 0 && <Badge tone=\"danger\" variant=\"solid\" size=\"xxs\" count={s.failed} />}\n {s.timedout > 0 && <Badge tone=\"warning\" variant=\"solid\" size=\"xxs\" count={s.timedout} />}\n {s.skipped > 0 && <Badge tone=\"warning\" variant=\"soft\" size=\"xxs\" count={s.skipped} />}\n {s.running > 0 && <Badge tone=\"info\" variant=\"solid\" size=\"xxs\" count={s.running} />}\n {s.pending > 0 && <Badge tone=\"neutral\" variant=\"soft\" size=\"xxs\" count={s.pending} />}\n </span>\n );\n}\n\n/**\n * Renders one test tree row: status icon, optional adapter decoration + framework\n * icon, status-colored name, duration, per-status count badges, and (when the\n * runner exposes the handlers) rerun/stop buttons. Designed as the `renderRow`\n * for the clicky `Tree`.\n */\nexport function TestTreeNode({ node, selected }: { node: Test; selected: boolean }) {\n const runner = useTestRunner();\n const hasChildren = (node.children?.length ?? 0) > 0;\n const status = testStatus(node);\n const fwIcon = frameworkIcon(node.framework);\n const fwAdapter = runner.adapters.resolveFrameworkIcon(node, runner);\n const fwNode = fwAdapter?.renderFrameworkIcon?.({ node, runner });\n const leadingAdapter = runner.adapters.resolveRowLeading(node, runner);\n const dur = node.duration || (hasChildren ? totalDuration(node) : 0);\n const canRerun = !!runner.onRerun && node.framework !== \"task\";\n const canStop = !!runner.onStop && !!node.can_stop && !!node.task_id;\n\n return (\n <>\n <Icon icon={statusIconFor(node)} className={cn(\"shrink-0\", node.running && \"animate-spin\")} />\n\n {fwNode != null ? (\n <span className=\"flex shrink-0 items-center text-sm opacity-60\">{fwNode}</span>\n ) : (\n fwIcon && <Icon icon={fwIcon} className=\"shrink-0 text-sm opacity-60\" />\n )}\n\n {leadingAdapter?.renderRowLeading?.({ node, runner })}\n\n <span\n className={cn(\n \"truncate\",\n selected ? \"font-semibold\" : \"font-medium\",\n (status && NAME_TONE[status]) || \"text-foreground\",\n )}\n >\n {humanizeName(node.name, node.framework)}\n </span>\n\n <span className=\"flex-1\" />\n\n {node.progress && node.progress.total > 0 && !node.passed && !node.failed && (\n <span className=\"flex shrink-0 items-center gap-1 text-xs text-blue-600 dark:text-blue-400\">\n {node.progress.phase && <span>{node.progress.phase}</span>}\n <span className=\"tabular-nums\">\n {node.progress.done}/{node.progress.total}\n </span>\n </span>\n )}\n\n {dur > 0 && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{formatTestDuration(dur)}</span>\n )}\n\n {hasChildren && <CountBadges node={node} />}\n\n {canStop && (\n <IconButton\n icon={UiStop}\n label=\"Stop\"\n disabled={runner.busy.stop}\n onClick={(e) => {\n e.stopPropagation();\n runner.onStop!(node);\n }}\n />\n )}\n\n {canRerun && (\n <IconButton\n icon={UiRefresh}\n label=\"Rerun\"\n disabled={runner.busy.rerun}\n onClick={(e) => {\n e.stopPropagation();\n runner.onRerun!(node);\n }}\n />\n )}\n </>\n );\n}\n"],"names":["sum","jsxs","jsx","Badge","useTestRunner","status","testStatus","frameworkIcon","totalDuration","Fragment","Icon","statusIconFor","cn","humanizeName","formatTestDuration","IconButton","UiStop","UiRefresh"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { Test } from './types';
2
+ /**
3
+ * Renders one test tree row: status icon, optional adapter decoration + framework
4
+ * icon, status-colored name, duration, per-status count badges, and (when the
5
+ * runner exposes the handlers) rerun/stop buttons. Designed as the `renderRow`
6
+ * for the clicky `Tree`.
7
+ */
8
+ export declare function TestTreeNode({ node, selected }: {
9
+ node: Test;
10
+ selected: boolean;
11
+ }): import("react/jsx-runtime").JSX.Element;
12
+ //# sourceMappingURL=TestTreeNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTreeNode.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/TestTreeNode.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAsCpC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,2CA4EjF"}
@@ -0,0 +1,99 @@
1
+ import { jsxs, Fragment, jsx } from "react/jsx-runtime";
2
+ import { Icon } from "../Icon.js";
3
+ import { Badge } from "../Badge.js";
4
+ import { IconButton } from "../../components/IconButton.js";
5
+ import { cn } from "../../lib/utils.js";
6
+ import { useTestRunner } from "./context.js";
7
+ import { testStatus, totalDuration, statusIconFor, humanizeName, formatTestDuration, sum } from "./status.js";
8
+ import { frameworkIcon } from "./frameworkIcon.js";
9
+ import { UiStop } from "../../icons/components/UiStop.js";
10
+ import { UiRefresh } from "../../icons/components/UiRefresh.js";
11
+ const NAME_TONE = {
12
+ failed: "text-red-600 dark:text-red-400",
13
+ timedout: "text-amber-600 dark:text-amber-400",
14
+ warned: "text-amber-600 dark:text-amber-400",
15
+ running: "text-blue-600 dark:text-blue-400",
16
+ skipped: "text-yellow-700 dark:text-yellow-500",
17
+ pending: "text-muted-foreground",
18
+ passed: "text-foreground"
19
+ };
20
+ function CountBadges({ node }) {
21
+ const s = sum(node);
22
+ if (s.total === 0) return null;
23
+ return /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1", children: [
24
+ s.passed > 0 && /* @__PURE__ */ jsx(Badge, { tone: "success", variant: "solid", size: "xxs", count: s.passed }),
25
+ s.failed > 0 && /* @__PURE__ */ jsx(Badge, { tone: "danger", variant: "solid", size: "xxs", count: s.failed }),
26
+ s.timedout > 0 && /* @__PURE__ */ jsx(Badge, { tone: "warning", variant: "solid", size: "xxs", count: s.timedout }),
27
+ s.skipped > 0 && /* @__PURE__ */ jsx(Badge, { tone: "warning", variant: "soft", size: "xxs", count: s.skipped }),
28
+ s.running > 0 && /* @__PURE__ */ jsx(Badge, { tone: "info", variant: "solid", size: "xxs", count: s.running }),
29
+ s.pending > 0 && /* @__PURE__ */ jsx(Badge, { tone: "neutral", variant: "soft", size: "xxs", count: s.pending })
30
+ ] });
31
+ }
32
+ function TestTreeNode({ node, selected }) {
33
+ var _a, _b, _c;
34
+ const runner = useTestRunner();
35
+ const hasChildren = (((_a = node.children) == null ? void 0 : _a.length) ?? 0) > 0;
36
+ const status = testStatus(node);
37
+ const fwIcon = frameworkIcon(node.framework);
38
+ const fwAdapter = runner.adapters.resolveFrameworkIcon(node, runner);
39
+ const fwNode = (_b = fwAdapter == null ? void 0 : fwAdapter.renderFrameworkIcon) == null ? void 0 : _b.call(fwAdapter, { node, runner });
40
+ const leadingAdapter = runner.adapters.resolveRowLeading(node, runner);
41
+ const dur = node.duration || (hasChildren ? totalDuration(node) : 0);
42
+ const canRerun = !!runner.onRerun && node.framework !== "task";
43
+ const canStop = !!runner.onStop && !!node.can_stop && !!node.task_id;
44
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
45
+ /* @__PURE__ */ jsx(Icon, { icon: statusIconFor(node), className: cn("shrink-0", node.running && "animate-spin") }),
46
+ fwNode != null ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center text-sm opacity-60", children: fwNode }) : fwIcon && /* @__PURE__ */ jsx(Icon, { icon: fwIcon, className: "shrink-0 text-sm opacity-60" }),
47
+ (_c = leadingAdapter == null ? void 0 : leadingAdapter.renderRowLeading) == null ? void 0 : _c.call(leadingAdapter, { node, runner }),
48
+ /* @__PURE__ */ jsx(
49
+ "span",
50
+ {
51
+ className: cn(
52
+ "truncate",
53
+ selected ? "font-semibold" : "font-medium",
54
+ status && NAME_TONE[status] || "text-foreground"
55
+ ),
56
+ children: humanizeName(node.name, node.framework)
57
+ }
58
+ ),
59
+ /* @__PURE__ */ jsx("span", { className: "flex-1" }),
60
+ node.progress && node.progress.total > 0 && !node.passed && !node.failed && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-blue-600 dark:text-blue-400", children: [
61
+ node.progress.phase && /* @__PURE__ */ jsx("span", { children: node.progress.phase }),
62
+ /* @__PURE__ */ jsxs("span", { className: "tabular-nums", children: [
63
+ node.progress.done,
64
+ "/",
65
+ node.progress.total
66
+ ] })
67
+ ] }),
68
+ dur > 0 && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: formatTestDuration(dur) }),
69
+ hasChildren && /* @__PURE__ */ jsx(CountBadges, { node }),
70
+ canStop && /* @__PURE__ */ jsx(
71
+ IconButton,
72
+ {
73
+ icon: UiStop,
74
+ label: "Stop",
75
+ disabled: runner.busy.stop,
76
+ onClick: (e) => {
77
+ e.stopPropagation();
78
+ runner.onStop(node);
79
+ }
80
+ }
81
+ ),
82
+ canRerun && /* @__PURE__ */ jsx(
83
+ IconButton,
84
+ {
85
+ icon: UiRefresh,
86
+ label: "Rerun",
87
+ disabled: runner.busy.rerun,
88
+ onClick: (e) => {
89
+ e.stopPropagation();
90
+ runner.onRerun(node);
91
+ }
92
+ }
93
+ )
94
+ ] });
95
+ }
96
+ export {
97
+ TestTreeNode
98
+ };
99
+ //# sourceMappingURL=TestTreeNode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TestTreeNode.js","sources":["../../../src/data/test-runner/TestTreeNode.tsx"],"sourcesContent":["import { Icon } from \"../Icon\";\nimport { Badge } from \"../Badge\";\nimport { IconButton } from \"../../components/IconButton\";\nimport { UiRefresh, UiStop } from \"../../icons\";\nimport { cn } from \"../../lib/utils\";\nimport type { Test } from \"./types\";\nimport { useTestRunner } from \"./context\";\nimport {\n formatTestDuration,\n humanizeName,\n statusIconFor,\n sum,\n testStatus,\n totalDuration,\n} from \"./status\";\nimport { frameworkIcon } from \"./frameworkIcon\";\n\nconst NAME_TONE: Record<string, string> = {\n failed: \"text-red-600 dark:text-red-400\",\n timedout: \"text-amber-600 dark:text-amber-400\",\n warned: \"text-amber-600 dark:text-amber-400\",\n running: \"text-blue-600 dark:text-blue-400\",\n skipped: \"text-yellow-700 dark:text-yellow-500\",\n pending: \"text-muted-foreground\",\n passed: \"text-foreground\",\n};\n\n/** Per-status roll-up count badges shown on a container row. */\nfunction CountBadges({ node }: { node: Test }) {\n const s = sum(node);\n if (s.total === 0) return null;\n return (\n <span className=\"flex shrink-0 items-center gap-1\">\n {s.passed > 0 && <Badge tone=\"success\" variant=\"solid\" size=\"xxs\" count={s.passed} />}\n {s.failed > 0 && <Badge tone=\"danger\" variant=\"solid\" size=\"xxs\" count={s.failed} />}\n {s.timedout > 0 && <Badge tone=\"warning\" variant=\"solid\" size=\"xxs\" count={s.timedout} />}\n {s.skipped > 0 && <Badge tone=\"warning\" variant=\"soft\" size=\"xxs\" count={s.skipped} />}\n {s.running > 0 && <Badge tone=\"info\" variant=\"solid\" size=\"xxs\" count={s.running} />}\n {s.pending > 0 && <Badge tone=\"neutral\" variant=\"soft\" size=\"xxs\" count={s.pending} />}\n </span>\n );\n}\n\n/**\n * Renders one test tree row: status icon, optional adapter decoration + framework\n * icon, status-colored name, duration, per-status count badges, and (when the\n * runner exposes the handlers) rerun/stop buttons. Designed as the `renderRow`\n * for the clicky `Tree`.\n */\nexport function TestTreeNode({ node, selected }: { node: Test; selected: boolean }) {\n const runner = useTestRunner();\n const hasChildren = (node.children?.length ?? 0) > 0;\n const status = testStatus(node);\n const fwIcon = frameworkIcon(node.framework);\n const fwAdapter = runner.adapters.resolveFrameworkIcon(node, runner);\n const fwNode = fwAdapter?.renderFrameworkIcon?.({ node, runner });\n const leadingAdapter = runner.adapters.resolveRowLeading(node, runner);\n const dur = node.duration || (hasChildren ? totalDuration(node) : 0);\n const canRerun = !!runner.onRerun && node.framework !== \"task\";\n const canStop = !!runner.onStop && !!node.can_stop && !!node.task_id;\n\n return (\n <>\n <Icon icon={statusIconFor(node)} className={cn(\"shrink-0\", node.running && \"animate-spin\")} />\n\n {fwNode != null ? (\n <span className=\"flex shrink-0 items-center text-sm opacity-60\">{fwNode}</span>\n ) : (\n fwIcon && <Icon icon={fwIcon} className=\"shrink-0 text-sm opacity-60\" />\n )}\n\n {leadingAdapter?.renderRowLeading?.({ node, runner })}\n\n <span\n className={cn(\n \"truncate\",\n selected ? \"font-semibold\" : \"font-medium\",\n (status && NAME_TONE[status]) || \"text-foreground\",\n )}\n >\n {humanizeName(node.name, node.framework)}\n </span>\n\n <span className=\"flex-1\" />\n\n {node.progress && node.progress.total > 0 && !node.passed && !node.failed && (\n <span className=\"flex shrink-0 items-center gap-1 text-xs text-blue-600 dark:text-blue-400\">\n {node.progress.phase && <span>{node.progress.phase}</span>}\n <span className=\"tabular-nums\">\n {node.progress.done}/{node.progress.total}\n </span>\n </span>\n )}\n\n {dur > 0 && (\n <span className=\"shrink-0 text-xs text-muted-foreground\">{formatTestDuration(dur)}</span>\n )}\n\n {hasChildren && <CountBadges node={node} />}\n\n {canStop && (\n <IconButton\n icon={UiStop}\n label=\"Stop\"\n disabled={runner.busy.stop}\n onClick={(e) => {\n e.stopPropagation();\n runner.onStop!(node);\n }}\n />\n )}\n\n {canRerun && (\n <IconButton\n icon={UiRefresh}\n label=\"Rerun\"\n disabled={runner.busy.rerun}\n onClick={(e) => {\n e.stopPropagation();\n runner.onRerun!(node);\n }}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
6
+ class TestNodeAdapterRegistry {
7
+ constructor(adapters = []) {
8
+ __publicField(this, "adapters");
9
+ this.adapters = adapters;
10
+ }
11
+ firstWith(node, runner, has) {
12
+ for (const a of this.adapters) {
13
+ if (has(a) && a.match(node, runner)) return a;
14
+ }
15
+ return null;
16
+ }
17
+ /** Adapter whose `renderDetail` should drive the body, or null for the default. */
18
+ resolveDetail(node, runner) {
19
+ return this.firstWith(node, runner, (a) => !!a.renderDetail);
20
+ }
21
+ resolveRowLeading(node, runner) {
22
+ return this.firstWith(node, runner, (a) => !!a.renderRowLeading);
23
+ }
24
+ resolveFrameworkIcon(node, runner) {
25
+ return this.firstWith(node, runner, (a) => !!a.renderFrameworkIcon);
26
+ }
27
+ resolveActions(node, runner) {
28
+ return this.firstWith(node, runner, (a) => !!a.nodeActions);
29
+ }
30
+ /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */
31
+ resolveTabs(node, runner) {
32
+ const ctx = { node, runner };
33
+ const seen = /* @__PURE__ */ new Set();
34
+ const tabs = [];
35
+ for (const a of this.adapters) {
36
+ if (!a.detailTabs || !a.match(node, runner)) continue;
37
+ for (const tab of a.detailTabs(ctx)) {
38
+ if (seen.has(tab.id)) continue;
39
+ seen.add(tab.id);
40
+ tabs.push(tab);
41
+ }
42
+ }
43
+ return tabs;
44
+ }
45
+ /** True when the resolved detail adapter owns its own scroll region. */
46
+ ownsScroll(node, runner) {
47
+ const a = this.resolveDetail(node, runner);
48
+ if (!(a == null ? void 0 : a.ownsScroll)) return false;
49
+ return a.ownsScroll({ node, runner });
50
+ }
51
+ }
52
+ function createTestRunnerRegistry(adapters = []) {
53
+ return new TestNodeAdapterRegistry(adapters);
54
+ }
55
+ exports.TestNodeAdapterRegistry = TestNodeAdapterRegistry;
56
+ exports.createTestRunnerRegistry = createTestRunnerRegistry;
57
+ //# sourceMappingURL=adapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.cjs","sources":["../../../src/data/test-runner/adapter.ts"],"sourcesContent":["// Node adapters let a host plug domain-specific rendering into the test runner\n// without forking it. An adapter claims nodes via `match` and contributes how\n// they render in each surface (tree row, detail body, detail tabs, header\n// actions). The registry resolves the first matching adapter per surface;\n// surfaces with no match fall back to the runner's built-in default rendering.\n//\n// This formalizes the wrapper-with-an-if-chain pattern hosts use today (e.g.\n// a ResultDetailPane fall-through over domain panels + tab providers + per-step\n// actions) into a registered, composable contract.\n\nimport type { ReactNode } from \"react\";\nimport type { Test } from \"./types\";\nimport type { TestRunnerContext } from \"./context\";\n\nexport type AdapterContext = {\n node: Test;\n runner: TestRunnerContext;\n};\n\nexport type DetailTab = {\n id: string;\n label: string;\n render: () => ReactNode;\n};\n\nexport type TestNodeAdapter = {\n /** Stable identifier, used as a React key and for debugging. */\n id: string;\n /** Claim the node. The first registered adapter to return true wins per surface. */\n match: (node: Test, runner: TestRunnerContext) => boolean;\n /**\n * Replace the framework icon for the node — return any node (e.g. a brand\n * logo via the host's own icon provider) or null to fall back to the built-in\n * `frameworkIcon`. Lets hosts restore devicon/logos glyphs without clicky\n * depending on a runtime icon provider.\n */\n renderFrameworkIcon?: (c: AdapterContext) => ReactNode;\n /** Extra decoration rendered between the framework icon and the node name. */\n renderRowLeading?: (c: AdapterContext) => ReactNode;\n /** Primary detail body for the node, replacing the default panel body. */\n renderDetail?: (c: AdapterContext) => ReactNode;\n /** Extra detail tabs, merged (in registration order) across all matching adapters. */\n detailTabs?: (c: AdapterContext) => DetailTab[];\n /** Node-scoped action buttons rendered in the detail header. */\n nodeActions?: (c: AdapterContext) => ReactNode;\n /**\n * Whether `renderDetail` owns its own full-height scroll container. When\n * false (the default for custom bodies), the panel wraps it in a scroll\n * region. The built-in default body owns its scroll.\n */\n ownsScroll?: (c: AdapterContext) => boolean;\n};\n\n/**\n * Ordered set of node adapters. First match wins per surface; `detailTabs` are\n * the exception — they merge across every matching adapter so independent\n * adapters can each contribute a tab.\n */\nexport class TestNodeAdapterRegistry {\n readonly adapters: TestNodeAdapter[];\n\n constructor(adapters: TestNodeAdapter[] = []) {\n this.adapters = adapters;\n }\n\n private firstWith(\n node: Test,\n runner: TestRunnerContext,\n has: (a: TestNodeAdapter) => boolean,\n ): TestNodeAdapter | null {\n for (const a of this.adapters) {\n if (has(a) && a.match(node, runner)) return a;\n }\n return null;\n }\n\n /** Adapter whose `renderDetail` should drive the body, or null for the default. */\n resolveDetail(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderDetail);\n }\n\n resolveRowLeading(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderRowLeading);\n }\n\n resolveFrameworkIcon(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.renderFrameworkIcon);\n }\n\n resolveActions(node: Test, runner: TestRunnerContext): TestNodeAdapter | null {\n return this.firstWith(node, runner, (a) => !!a.nodeActions);\n }\n\n /** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */\n resolveTabs(node: Test, runner: TestRunnerContext): DetailTab[] {\n const ctx: AdapterContext = { node, runner };\n const seen = new Set<string>();\n const tabs: DetailTab[] = [];\n for (const a of this.adapters) {\n if (!a.detailTabs || !a.match(node, runner)) continue;\n for (const tab of a.detailTabs(ctx)) {\n if (seen.has(tab.id)) continue;\n seen.add(tab.id);\n tabs.push(tab);\n }\n }\n return tabs;\n }\n\n /** True when the resolved detail adapter owns its own scroll region. */\n ownsScroll(node: Test, runner: TestRunnerContext): boolean {\n const a = this.resolveDetail(node, runner);\n if (!a?.ownsScroll) return false;\n return a.ownsScroll({ node, runner });\n }\n}\n\nexport function createTestRunnerRegistry(\n adapters: TestNodeAdapter[] = [],\n): TestNodeAdapterRegistry {\n return new TestNodeAdapterRegistry(adapters);\n}\n"],"names":[],"mappings":";;;;;AA0DO,MAAM,wBAAwB;AAAA,EAGnC,YAAY,WAA8B,IAAI;AAFrC;AAGP,SAAK,WAAW;AAAA,EAClB;AAAA,EAEQ,UACN,MACA,QACA,KACwB;AACxB,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,IAAI,CAAC,KAAK,EAAE,MAAM,MAAM,MAAM,EAAG,QAAO;AAAA,IAC9C;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,cAAc,MAAY,QAAmD;AAC3E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,YAAY;AAAA,EAC7D;AAAA,EAEA,kBAAkB,MAAY,QAAmD;AAC/E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,gBAAgB;AAAA,EACjE;AAAA,EAEA,qBAAqB,MAAY,QAAmD;AAClF,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,mBAAmB;AAAA,EACpE;AAAA,EAEA,eAAe,MAAY,QAAmD;AAC5E,WAAO,KAAK,UAAU,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW;AAAA,EAC5D;AAAA;AAAA,EAGA,YAAY,MAAY,QAAwC;AAC9D,UAAM,MAAsB,EAAE,MAAM,OAAA;AACpC,UAAM,2BAAW,IAAA;AACjB,UAAM,OAAoB,CAAA;AAC1B,eAAW,KAAK,KAAK,UAAU;AAC7B,UAAI,CAAC,EAAE,cAAc,CAAC,EAAE,MAAM,MAAM,MAAM,EAAG;AAC7C,iBAAW,OAAO,EAAE,WAAW,GAAG,GAAG;AACnC,YAAI,KAAK,IAAI,IAAI,EAAE,EAAG;AACtB,aAAK,IAAI,IAAI,EAAE;AACf,aAAK,KAAK,GAAG;AAAA,MACf;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,WAAW,MAAY,QAAoC;AACzD,UAAM,IAAI,KAAK,cAAc,MAAM,MAAM;AACzC,QAAI,EAAC,uBAAG,YAAY,QAAO;AAC3B,WAAO,EAAE,WAAW,EAAE,MAAM,QAAQ;AAAA,EACtC;AACF;AAEO,SAAS,yBACd,WAA8B,IACL;AACzB,SAAO,IAAI,wBAAwB,QAAQ;AAC7C;;;"}