@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.
- 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 +51 -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/TimeseriesCoreBars.cjs +8 -1
- package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
- package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
- package/dist/data/TimeseriesCoreBars.js +8 -1
- package/dist/data/TimeseriesCoreBars.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,60 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { Test } from './types';
|
|
3
|
+
import { TestRunnerContext } from './context';
|
|
4
|
+
export type AdapterContext = {
|
|
5
|
+
node: Test;
|
|
6
|
+
runner: TestRunnerContext;
|
|
7
|
+
};
|
|
8
|
+
export type DetailTab = {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
render: () => ReactNode;
|
|
12
|
+
};
|
|
13
|
+
export type TestNodeAdapter = {
|
|
14
|
+
/** Stable identifier, used as a React key and for debugging. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Claim the node. The first registered adapter to return true wins per surface. */
|
|
17
|
+
match: (node: Test, runner: TestRunnerContext) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Replace the framework icon for the node — return any node (e.g. a brand
|
|
20
|
+
* logo via the host's own icon provider) or null to fall back to the built-in
|
|
21
|
+
* `frameworkIcon`. Lets hosts restore devicon/logos glyphs without clicky
|
|
22
|
+
* depending on a runtime icon provider.
|
|
23
|
+
*/
|
|
24
|
+
renderFrameworkIcon?: (c: AdapterContext) => ReactNode;
|
|
25
|
+
/** Extra decoration rendered between the framework icon and the node name. */
|
|
26
|
+
renderRowLeading?: (c: AdapterContext) => ReactNode;
|
|
27
|
+
/** Primary detail body for the node, replacing the default panel body. */
|
|
28
|
+
renderDetail?: (c: AdapterContext) => ReactNode;
|
|
29
|
+
/** Extra detail tabs, merged (in registration order) across all matching adapters. */
|
|
30
|
+
detailTabs?: (c: AdapterContext) => DetailTab[];
|
|
31
|
+
/** Node-scoped action buttons rendered in the detail header. */
|
|
32
|
+
nodeActions?: (c: AdapterContext) => ReactNode;
|
|
33
|
+
/**
|
|
34
|
+
* Whether `renderDetail` owns its own full-height scroll container. When
|
|
35
|
+
* false (the default for custom bodies), the panel wraps it in a scroll
|
|
36
|
+
* region. The built-in default body owns its scroll.
|
|
37
|
+
*/
|
|
38
|
+
ownsScroll?: (c: AdapterContext) => boolean;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Ordered set of node adapters. First match wins per surface; `detailTabs` are
|
|
42
|
+
* the exception — they merge across every matching adapter so independent
|
|
43
|
+
* adapters can each contribute a tab.
|
|
44
|
+
*/
|
|
45
|
+
export declare class TestNodeAdapterRegistry {
|
|
46
|
+
readonly adapters: TestNodeAdapter[];
|
|
47
|
+
constructor(adapters?: TestNodeAdapter[]);
|
|
48
|
+
private firstWith;
|
|
49
|
+
/** Adapter whose `renderDetail` should drive the body, or null for the default. */
|
|
50
|
+
resolveDetail(node: Test, runner: TestRunnerContext): TestNodeAdapter | null;
|
|
51
|
+
resolveRowLeading(node: Test, runner: TestRunnerContext): TestNodeAdapter | null;
|
|
52
|
+
resolveFrameworkIcon(node: Test, runner: TestRunnerContext): TestNodeAdapter | null;
|
|
53
|
+
resolveActions(node: Test, runner: TestRunnerContext): TestNodeAdapter | null;
|
|
54
|
+
/** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */
|
|
55
|
+
resolveTabs(node: Test, runner: TestRunnerContext): DetailTab[];
|
|
56
|
+
/** True when the resolved detail adapter owns its own scroll region. */
|
|
57
|
+
ownsScroll(node: Test, runner: TestRunnerContext): boolean;
|
|
58
|
+
}
|
|
59
|
+
export declare function createTestRunnerRegistry(adapters?: TestNodeAdapter[]): TestNodeAdapterRegistry;
|
|
60
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/adapter.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,SAAS,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,oFAAoF;IACpF,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC;IAC1D;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IACvD,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IACpD,0EAA0E;IAC1E,YAAY,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IAChD,sFAAsF;IACtF,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,EAAE,CAAC;IAChD,gEAAgE;IAChE,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,SAAS,CAAC;IAC/C;;;;OAIG;IACH,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,cAAc,KAAK,OAAO,CAAC;CAC7C,CAAC;AAEF;;;;GAIG;AACH,qBAAa,uBAAuB;IAClC,QAAQ,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;gBAEzB,QAAQ,GAAE,eAAe,EAAO;IAI5C,OAAO,CAAC,SAAS;IAWjB,mFAAmF;IACnF,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAI5E,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAIhF,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAInF,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,eAAe,GAAG,IAAI;IAI7E,+EAA+E;IAC/E,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,SAAS,EAAE;IAe/D,wEAAwE;IACxE,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO;CAK3D;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,GAAE,eAAe,EAAO,GAC/B,uBAAuB,CAEzB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
class TestNodeAdapterRegistry {
|
|
5
|
+
constructor(adapters = []) {
|
|
6
|
+
__publicField(this, "adapters");
|
|
7
|
+
this.adapters = adapters;
|
|
8
|
+
}
|
|
9
|
+
firstWith(node, runner, has) {
|
|
10
|
+
for (const a of this.adapters) {
|
|
11
|
+
if (has(a) && a.match(node, runner)) return a;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
/** Adapter whose `renderDetail` should drive the body, or null for the default. */
|
|
16
|
+
resolveDetail(node, runner) {
|
|
17
|
+
return this.firstWith(node, runner, (a) => !!a.renderDetail);
|
|
18
|
+
}
|
|
19
|
+
resolveRowLeading(node, runner) {
|
|
20
|
+
return this.firstWith(node, runner, (a) => !!a.renderRowLeading);
|
|
21
|
+
}
|
|
22
|
+
resolveFrameworkIcon(node, runner) {
|
|
23
|
+
return this.firstWith(node, runner, (a) => !!a.renderFrameworkIcon);
|
|
24
|
+
}
|
|
25
|
+
resolveActions(node, runner) {
|
|
26
|
+
return this.firstWith(node, runner, (a) => !!a.nodeActions);
|
|
27
|
+
}
|
|
28
|
+
/** Merge `detailTabs` from every matching adapter, de-duplicated by tab id. */
|
|
29
|
+
resolveTabs(node, runner) {
|
|
30
|
+
const ctx = { node, runner };
|
|
31
|
+
const seen = /* @__PURE__ */ new Set();
|
|
32
|
+
const tabs = [];
|
|
33
|
+
for (const a of this.adapters) {
|
|
34
|
+
if (!a.detailTabs || !a.match(node, runner)) continue;
|
|
35
|
+
for (const tab of a.detailTabs(ctx)) {
|
|
36
|
+
if (seen.has(tab.id)) continue;
|
|
37
|
+
seen.add(tab.id);
|
|
38
|
+
tabs.push(tab);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return tabs;
|
|
42
|
+
}
|
|
43
|
+
/** True when the resolved detail adapter owns its own scroll region. */
|
|
44
|
+
ownsScroll(node, runner) {
|
|
45
|
+
const a = this.resolveDetail(node, runner);
|
|
46
|
+
if (!(a == null ? void 0 : a.ownsScroll)) return false;
|
|
47
|
+
return a.ownsScroll({ node, runner });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function createTestRunnerRegistry(adapters = []) {
|
|
51
|
+
return new TestNodeAdapterRegistry(adapters);
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
TestNodeAdapterRegistry,
|
|
55
|
+
createTestRunnerRegistry
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","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;"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const react = require("react");
|
|
4
|
+
const Context = react.createContext(null);
|
|
5
|
+
const TestRunnerProvider = Context.Provider;
|
|
6
|
+
function useTestRunner() {
|
|
7
|
+
const ctx = react.useContext(Context);
|
|
8
|
+
if (!ctx) {
|
|
9
|
+
throw new Error("useTestRunner must be used within a <TestRunner> / TestRunnerProvider");
|
|
10
|
+
}
|
|
11
|
+
return ctx;
|
|
12
|
+
}
|
|
13
|
+
exports.TestRunnerProvider = TestRunnerProvider;
|
|
14
|
+
exports.useTestRunner = useTestRunner;
|
|
15
|
+
//# sourceMappingURL=context.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.cjs","sources":["../../../src/data/test-runner/context.ts"],"sourcesContent":["// A single context object carries all runner state + handlers, provided once\n// and read by every sub-component via useTestRunner() — instead of threading\n// onRerun/onStop/selected/runMeta/... through each component's props. Matches\n// clicky's existing Provider idiom (ClickyRuntimeProvider / TagActionsProvider).\n\nimport { createContext, useContext } from \"react\";\nimport type { RunMeta, SnapshotStatus, Test } from \"./types\";\nimport type { TestFilters } from \"./filterState\";\nimport type { TestNodeAdapterRegistry } from \"./adapter\";\n\nexport type TestRunnerContext = {\n /** The (already filtered, host-owned) test forest to render. */\n tests: Test[];\n /** Currently selected node, or null. */\n selected: Test | null;\n runMeta?: RunMeta | undefined;\n /** Run status flags (data only — no transport lives here). */\n status?: SnapshotStatus | undefined;\n /** Whether the run has finished. */\n done: boolean;\n /**\n * Injected wall-clock epoch ms for elapsed-time display. Kept as data so the\n * summary stays pure and testable; hosts pass `Date.now()`.\n */\n now?: number | undefined;\n /** Active status/framework filters. */\n filters: TestFilters;\n /** Tri-state expand-all: true (all open), false (all closed), null (default). */\n expandAll: boolean | null;\n /** In-flight action flags driving button disabled/spinner states. */\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 /** Optional — when absent, the rerun affordance is hidden. */\n onRerun?: ((node: Test) => void) | undefined;\n /** Optional — when absent, the stop affordance is hidden. */\n onStop?: ((node: Test) => void) | undefined;\n\n /** Node adapters driving domain-specific rendering. */\n adapters: TestNodeAdapterRegistry;\n};\n\nconst Context = createContext<TestRunnerContext | null>(null);\n\nexport const TestRunnerProvider = Context.Provider;\n\nexport function useTestRunner(): TestRunnerContext {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"useTestRunner must be used within a <TestRunner> / TestRunnerProvider\");\n }\n return ctx;\n}\n"],"names":["createContext","useContext"],"mappings":";;;AA4CA,MAAM,UAAUA,MAAAA,cAAwC,IAAI;AAErD,MAAM,qBAAqB,QAAQ;AAEnC,SAAS,gBAAmC;AACjD,QAAM,MAAMC,MAAAA,WAAW,OAAO;AAC9B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AACA,SAAO;AACT;;;"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { RunMeta, SnapshotStatus, Test } from './types';
|
|
2
|
+
import { TestFilters } from './filterState';
|
|
3
|
+
import { TestNodeAdapterRegistry } from './adapter';
|
|
4
|
+
export type TestRunnerContext = {
|
|
5
|
+
/** The (already filtered, host-owned) test forest to render. */
|
|
6
|
+
tests: Test[];
|
|
7
|
+
/** Currently selected node, or null. */
|
|
8
|
+
selected: Test | null;
|
|
9
|
+
runMeta?: RunMeta | undefined;
|
|
10
|
+
/** Run status flags (data only — no transport lives here). */
|
|
11
|
+
status?: SnapshotStatus | undefined;
|
|
12
|
+
/** Whether the run has finished. */
|
|
13
|
+
done: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Injected wall-clock epoch ms for elapsed-time display. Kept as data so the
|
|
16
|
+
* summary stays pure and testable; hosts pass `Date.now()`.
|
|
17
|
+
*/
|
|
18
|
+
now?: number | undefined;
|
|
19
|
+
/** Active status/framework filters. */
|
|
20
|
+
filters: TestFilters;
|
|
21
|
+
/** Tri-state expand-all: true (all open), false (all closed), null (default). */
|
|
22
|
+
expandAll: boolean | null;
|
|
23
|
+
/** In-flight action flags driving button disabled/spinner states. */
|
|
24
|
+
busy: {
|
|
25
|
+
rerun?: boolean;
|
|
26
|
+
stop?: boolean;
|
|
27
|
+
};
|
|
28
|
+
onSelect: (node: Test | null) => void;
|
|
29
|
+
onFiltersChange: (next: TestFilters) => void;
|
|
30
|
+
onExpandAllChange: (next: boolean | null) => void;
|
|
31
|
+
/** Optional — when absent, the rerun affordance is hidden. */
|
|
32
|
+
onRerun?: ((node: Test) => void) | undefined;
|
|
33
|
+
/** Optional — when absent, the stop affordance is hidden. */
|
|
34
|
+
onStop?: ((node: Test) => void) | undefined;
|
|
35
|
+
/** Node adapters driving domain-specific rendering. */
|
|
36
|
+
adapters: TestNodeAdapterRegistry;
|
|
37
|
+
};
|
|
38
|
+
export declare const TestRunnerProvider: import('react').Provider<TestRunnerContext | null>;
|
|
39
|
+
export declare function useTestRunner(): TestRunnerContext;
|
|
40
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/context.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAC;AAEzD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gEAAgE;IAChE,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,wCAAwC;IACxC,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,8DAA8D;IAC9D,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,oCAAoC;IACpC,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,uCAAuC;IACvC,OAAO,EAAE,WAAW,CAAC;IACrB,iFAAiF;IACjF,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAE1C,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IACtC,eAAe,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IAC7C,iBAAiB,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,8DAA8D;IAC9D,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC7C,6DAA6D;IAC7D,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAE5C,uDAAuD;IACvD,QAAQ,EAAE,uBAAuB,CAAC;CACnC,CAAC;AAIF,eAAO,MAAM,kBAAkB,oDAAmB,CAAC;AAEnD,wBAAgB,aAAa,IAAI,iBAAiB,CAMjD"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
const Context = createContext(null);
|
|
3
|
+
const TestRunnerProvider = Context.Provider;
|
|
4
|
+
function useTestRunner() {
|
|
5
|
+
const ctx = useContext(Context);
|
|
6
|
+
if (!ctx) {
|
|
7
|
+
throw new Error("useTestRunner must be used within a <TestRunner> / TestRunnerProvider");
|
|
8
|
+
}
|
|
9
|
+
return ctx;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
TestRunnerProvider,
|
|
13
|
+
useTestRunner
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sources":["../../../src/data/test-runner/context.ts"],"sourcesContent":["// A single context object carries all runner state + handlers, provided once\n// and read by every sub-component via useTestRunner() — instead of threading\n// onRerun/onStop/selected/runMeta/... through each component's props. Matches\n// clicky's existing Provider idiom (ClickyRuntimeProvider / TagActionsProvider).\n\nimport { createContext, useContext } from \"react\";\nimport type { RunMeta, SnapshotStatus, Test } from \"./types\";\nimport type { TestFilters } from \"./filterState\";\nimport type { TestNodeAdapterRegistry } from \"./adapter\";\n\nexport type TestRunnerContext = {\n /** The (already filtered, host-owned) test forest to render. */\n tests: Test[];\n /** Currently selected node, or null. */\n selected: Test | null;\n runMeta?: RunMeta | undefined;\n /** Run status flags (data only — no transport lives here). */\n status?: SnapshotStatus | undefined;\n /** Whether the run has finished. */\n done: boolean;\n /**\n * Injected wall-clock epoch ms for elapsed-time display. Kept as data so the\n * summary stays pure and testable; hosts pass `Date.now()`.\n */\n now?: number | undefined;\n /** Active status/framework filters. */\n filters: TestFilters;\n /** Tri-state expand-all: true (all open), false (all closed), null (default). */\n expandAll: boolean | null;\n /** In-flight action flags driving button disabled/spinner states. */\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 /** Optional — when absent, the rerun affordance is hidden. */\n onRerun?: ((node: Test) => void) | undefined;\n /** Optional — when absent, the stop affordance is hidden. */\n onStop?: ((node: Test) => void) | undefined;\n\n /** Node adapters driving domain-specific rendering. */\n adapters: TestNodeAdapterRegistry;\n};\n\nconst Context = createContext<TestRunnerContext | null>(null);\n\nexport const TestRunnerProvider = Context.Provider;\n\nexport function useTestRunner(): TestRunnerContext {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\"useTestRunner must be used within a <TestRunner> / TestRunnerProvider\");\n }\n return ctx;\n}\n"],"names":[],"mappings":";AA4CA,MAAM,UAAU,cAAwC,IAAI;AAErD,MAAM,qBAAqB,QAAQ;AAEnC,SAAS,gBAAmC;AACjD,QAAM,MAAM,WAAW,OAAO;AAC9B,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AACA,SAAO;AACT;"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
function emptyTestFilters() {
|
|
4
|
+
return { status: /* @__PURE__ */ new Map(), framework: /* @__PURE__ */ new Map() };
|
|
5
|
+
}
|
|
6
|
+
function decodeFilterState(tokens) {
|
|
7
|
+
const state = /* @__PURE__ */ new Map();
|
|
8
|
+
for (const token of tokens) {
|
|
9
|
+
const value = token.startsWith("!") ? token.slice(1) : token;
|
|
10
|
+
if (!value) continue;
|
|
11
|
+
state.set(value, token.startsWith("!") ? "exclude" : "include");
|
|
12
|
+
}
|
|
13
|
+
return state;
|
|
14
|
+
}
|
|
15
|
+
function encodeFilterState(state) {
|
|
16
|
+
return Array.from(state.entries()).sort(([a], [b]) => String(a).localeCompare(String(b))).map(([value, mode]) => mode === "exclude" ? `!${value}` : String(value));
|
|
17
|
+
}
|
|
18
|
+
function cycleFilterState(state, key) {
|
|
19
|
+
const next = new Map(state);
|
|
20
|
+
const current = next.get(key);
|
|
21
|
+
if (current === "include") {
|
|
22
|
+
next.set(key, "exclude");
|
|
23
|
+
} else if (current === "exclude") {
|
|
24
|
+
next.delete(key);
|
|
25
|
+
} else {
|
|
26
|
+
next.set(key, "include");
|
|
27
|
+
}
|
|
28
|
+
return next;
|
|
29
|
+
}
|
|
30
|
+
function matchesFilterState(value, state) {
|
|
31
|
+
if (state.size === 0) return true;
|
|
32
|
+
const include = /* @__PURE__ */ new Set();
|
|
33
|
+
const exclude = /* @__PURE__ */ new Set();
|
|
34
|
+
for (const [key, mode] of state.entries()) {
|
|
35
|
+
if (mode === "include") include.add(key);
|
|
36
|
+
if (mode === "exclude") exclude.add(key);
|
|
37
|
+
}
|
|
38
|
+
if (value && exclude.has(value)) return false;
|
|
39
|
+
if (include.size === 0) return true;
|
|
40
|
+
return !!value && include.has(value);
|
|
41
|
+
}
|
|
42
|
+
exports.cycleFilterState = cycleFilterState;
|
|
43
|
+
exports.decodeFilterState = decodeFilterState;
|
|
44
|
+
exports.emptyTestFilters = emptyTestFilters;
|
|
45
|
+
exports.encodeFilterState = encodeFilterState;
|
|
46
|
+
exports.matchesFilterState = matchesFilterState;
|
|
47
|
+
//# sourceMappingURL=filterState.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filterState.cjs","sources":["../../../src/data/test-runner/filterState.ts"],"sourcesContent":["// Tri-state filter primitives — ported from the Gavel test runner. A key is\n// either absent (neutral), 'include' (only matching values pass), or 'exclude'\n// (matching values are removed). Ported verbatim so consumers rebase cleanly.\n\nexport type FilterMode = \"include\" | \"exclude\";\nexport type FilterState<T extends string = string> = Map<T, FilterMode>;\n\n/** Status + framework tri-state filters driving the test tree. */\nexport type TestFilters = {\n status: FilterState<string>;\n framework: FilterState<string>;\n};\n\nexport function emptyTestFilters(): TestFilters {\n return { status: new Map(), framework: new Map() };\n}\n\nexport function decodeFilterState<T extends string = string>(tokens: string[]): FilterState<T> {\n const state = new Map<T, FilterMode>();\n for (const token of tokens) {\n const value = token.startsWith(\"!\") ? token.slice(1) : token;\n if (!value) continue;\n state.set(value as T, token.startsWith(\"!\") ? \"exclude\" : \"include\");\n }\n return state;\n}\n\nexport function encodeFilterState<T extends string = string>(state: FilterState<T>): string[] {\n return Array.from(state.entries())\n .sort(([a], [b]) => String(a).localeCompare(String(b)))\n .map(([value, mode]) => (mode === \"exclude\" ? `!${value}` : String(value)));\n}\n\n/** Advance a key through neutral → include → exclude → neutral. */\nexport function cycleFilterState<T extends string = string>(\n state: FilterState<T>,\n key: T,\n): FilterState<T> {\n const next = new Map(state);\n const current = next.get(key);\n if (current === \"include\") {\n next.set(key, \"exclude\");\n } else if (current === \"exclude\") {\n next.delete(key);\n } else {\n next.set(key, \"include\");\n }\n return next;\n}\n\nexport function matchesFilterState<T extends string = string>(\n value: T | null | undefined,\n state: FilterState<T>,\n): boolean {\n if (state.size === 0) return true;\n\n const include = new Set<T>();\n const exclude = new Set<T>();\n for (const [key, mode] of state.entries()) {\n if (mode === \"include\") include.add(key);\n if (mode === \"exclude\") exclude.add(key);\n }\n\n if (value && exclude.has(value)) return false;\n if (include.size === 0) return true;\n return !!value && include.has(value);\n}\n"],"names":[],"mappings":";;AAaO,SAAS,mBAAgC;AAC9C,SAAO,EAAE,QAAQ,oBAAI,IAAA,GAAO,WAAW,oBAAI,MAAI;AACjD;AAEO,SAAS,kBAA6C,QAAkC;AAC7F,QAAM,4BAAY,IAAA;AAClB,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,MAAM,WAAW,GAAG,IAAI,MAAM,MAAM,CAAC,IAAI;AACvD,QAAI,CAAC,MAAO;AACZ,UAAM,IAAI,OAAY,MAAM,WAAW,GAAG,IAAI,YAAY,SAAS;AAAA,EACrE;AACA,SAAO;AACT;AAEO,SAAS,kBAA6C,OAAiC;AAC5F,SAAO,MAAM,KAAK,MAAM,QAAA,CAAS,EAC9B,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,OAAO,CAAC,EAAE,cAAc,OAAO,CAAC,CAAC,CAAC,EACrD,IAAI,CAAC,CAAC,OAAO,IAAI,MAAO,SAAS,YAAY,IAAI,KAAK,KAAK,OAAO,KAAK,CAAE;AAC9E;AAGO,SAAS,iBACd,OACA,KACgB;AAChB,QAAM,OAAO,IAAI,IAAI,KAAK;AAC1B,QAAM,UAAU,KAAK,IAAI,GAAG;AAC5B,MAAI,YAAY,WAAW;AACzB,SAAK,IAAI,KAAK,SAAS;AAAA,EACzB,WAAW,YAAY,WAAW;AAChC,SAAK,OAAO,GAAG;AAAA,EACjB,OAAO;AACL,SAAK,IAAI,KAAK,SAAS;AAAA,EACzB;AACA,SAAO;AACT;AAEO,SAAS,mBACd,OACA,OACS;AACT,MAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,QAAM,8BAAc,IAAA;AACpB,QAAM,8BAAc,IAAA;AACpB,aAAW,CAAC,KAAK,IAAI,KAAK,MAAM,WAAW;AACzC,QAAI,SAAS,UAAW,SAAQ,IAAI,GAAG;AACvC,QAAI,SAAS,UAAW,SAAQ,IAAI,GAAG;AAAA,EACzC;AAEA,MAAI,SAAS,QAAQ,IAAI,KAAK,EAAG,QAAO;AACxC,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,SAAO,CAAC,CAAC,SAAS,QAAQ,IAAI,KAAK;AACrC;;;;;;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type FilterMode = "include" | "exclude";
|
|
2
|
+
export type FilterState<T extends string = string> = Map<T, FilterMode>;
|
|
3
|
+
/** Status + framework tri-state filters driving the test tree. */
|
|
4
|
+
export type TestFilters = {
|
|
5
|
+
status: FilterState<string>;
|
|
6
|
+
framework: FilterState<string>;
|
|
7
|
+
};
|
|
8
|
+
export declare function emptyTestFilters(): TestFilters;
|
|
9
|
+
export declare function decodeFilterState<T extends string = string>(tokens: string[]): FilterState<T>;
|
|
10
|
+
export declare function encodeFilterState<T extends string = string>(state: FilterState<T>): string[];
|
|
11
|
+
/** Advance a key through neutral → include → exclude → neutral. */
|
|
12
|
+
export declare function cycleFilterState<T extends string = string>(state: FilterState<T>, key: T): FilterState<T>;
|
|
13
|
+
export declare function matchesFilterState<T extends string = string>(value: T | null | undefined, state: FilterState<T>): boolean;
|
|
14
|
+
//# sourceMappingURL=filterState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filterState.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/filterState.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;AAC/C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IAAI,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;AAExE,kEAAkE;AAClE,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5B,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,wBAAgB,gBAAgB,IAAI,WAAW,CAE9C;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,CAQ7F;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAI5F;AAED,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EACxD,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EACrB,GAAG,EAAE,CAAC,GACL,WAAW,CAAC,CAAC,CAAC,CAWhB;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC1D,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EAC3B,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GACpB,OAAO,CAaT"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
function emptyTestFilters() {
|
|
2
|
+
return { status: /* @__PURE__ */ new Map(), framework: /* @__PURE__ */ new Map() };
|
|
3
|
+
}
|
|
4
|
+
function decodeFilterState(tokens) {
|
|
5
|
+
const state = /* @__PURE__ */ new Map();
|
|
6
|
+
for (const token of tokens) {
|
|
7
|
+
const value = token.startsWith("!") ? token.slice(1) : token;
|
|
8
|
+
if (!value) continue;
|
|
9
|
+
state.set(value, token.startsWith("!") ? "exclude" : "include");
|
|
10
|
+
}
|
|
11
|
+
return state;
|
|
12
|
+
}
|
|
13
|
+
function encodeFilterState(state) {
|
|
14
|
+
return Array.from(state.entries()).sort(([a], [b]) => String(a).localeCompare(String(b))).map(([value, mode]) => mode === "exclude" ? `!${value}` : String(value));
|
|
15
|
+
}
|
|
16
|
+
function cycleFilterState(state, key) {
|
|
17
|
+
const next = new Map(state);
|
|
18
|
+
const current = next.get(key);
|
|
19
|
+
if (current === "include") {
|
|
20
|
+
next.set(key, "exclude");
|
|
21
|
+
} else if (current === "exclude") {
|
|
22
|
+
next.delete(key);
|
|
23
|
+
} else {
|
|
24
|
+
next.set(key, "include");
|
|
25
|
+
}
|
|
26
|
+
return next;
|
|
27
|
+
}
|
|
28
|
+
function matchesFilterState(value, state) {
|
|
29
|
+
if (state.size === 0) return true;
|
|
30
|
+
const include = /* @__PURE__ */ new Set();
|
|
31
|
+
const exclude = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const [key, mode] of state.entries()) {
|
|
33
|
+
if (mode === "include") include.add(key);
|
|
34
|
+
if (mode === "exclude") exclude.add(key);
|
|
35
|
+
}
|
|
36
|
+
if (value && exclude.has(value)) return false;
|
|
37
|
+
if (include.size === 0) return true;
|
|
38
|
+
return !!value && include.has(value);
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
cycleFilterState,
|
|
42
|
+
decodeFilterState,
|
|
43
|
+
emptyTestFilters,
|
|
44
|
+
encodeFilterState,
|
|
45
|
+
matchesFilterState
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=filterState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filterState.js","sources":["../../../src/data/test-runner/filterState.ts"],"sourcesContent":["// Tri-state filter primitives — ported from the Gavel test runner. A key is\n// either absent (neutral), 'include' (only matching values pass), or 'exclude'\n// (matching values are removed). Ported verbatim so consumers rebase cleanly.\n\nexport type FilterMode = \"include\" | \"exclude\";\nexport type FilterState<T extends string = string> = Map<T, FilterMode>;\n\n/** Status + framework tri-state filters driving the test tree. */\nexport type TestFilters = {\n status: FilterState<string>;\n framework: FilterState<string>;\n};\n\nexport function emptyTestFilters(): TestFilters {\n return { status: new Map(), framework: new Map() };\n}\n\nexport function decodeFilterState<T extends string = string>(tokens: string[]): FilterState<T> {\n const state = new Map<T, FilterMode>();\n for (const token of tokens) {\n const value = token.startsWith(\"!\") ? token.slice(1) : token;\n if (!value) continue;\n state.set(value as T, token.startsWith(\"!\") ? \"exclude\" : \"include\");\n }\n return state;\n}\n\nexport function encodeFilterState<T extends string = string>(state: FilterState<T>): string[] {\n return Array.from(state.entries())\n .sort(([a], [b]) => String(a).localeCompare(String(b)))\n .map(([value, mode]) => (mode === \"exclude\" ? `!${value}` : String(value)));\n}\n\n/** Advance a key through neutral → include → exclude → neutral. */\nexport function cycleFilterState<T extends string = string>(\n state: FilterState<T>,\n key: T,\n): FilterState<T> {\n const next = new Map(state);\n const current = next.get(key);\n if (current === \"include\") {\n next.set(key, \"exclude\");\n } else if (current === \"exclude\") {\n next.delete(key);\n } else {\n next.set(key, \"include\");\n }\n return next;\n}\n\nexport function matchesFilterState<T extends string = string>(\n value: T | null | undefined,\n state: FilterState<T>,\n): boolean {\n if (state.size === 0) return true;\n\n const include = new Set<T>();\n const exclude = new Set<T>();\n for (const [key, mode] of state.entries()) {\n if (mode === \"include\") include.add(key);\n if (mode === \"exclude\") exclude.add(key);\n }\n\n if (value && exclude.has(value)) return false;\n if (include.size === 0) return true;\n return !!value && include.has(value);\n}\n"],"names":[],"mappings":"AAaO,SAAS,mBAAgC;AAC9C,SAAO,EAAE,QAAQ,oBAAI,IAAA,GAAO,WAAW,oBAAI,MAAI;AACjD;AAEO,SAAS,kBAA6C,QAAkC;AAC7F,QAAM,4BAAY,IAAA;AAClB,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,MAAM,WAAW,GAAG,IAAI,MAAM,MAAM,CAAC,IAAI;AACvD,QAAI,CAAC,MAAO;AACZ,UAAM,IAAI,OAAY,MAAM,WAAW,GAAG,IAAI,YAAY,SAAS;AAAA,EACrE;AACA,SAAO;AACT;AAEO,SAAS,kBAA6C,OAAiC;AAC5F,SAAO,MAAM,KAAK,MAAM,QAAA,CAAS,EAC9B,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,OAAO,CAAC,EAAE,cAAc,OAAO,CAAC,CAAC,CAAC,EACrD,IAAI,CAAC,CAAC,OAAO,IAAI,MAAO,SAAS,YAAY,IAAI,KAAK,KAAK,OAAO,KAAK,CAAE;AAC9E;AAGO,SAAS,iBACd,OACA,KACgB;AAChB,QAAM,OAAO,IAAI,IAAI,KAAK;AAC1B,QAAM,UAAU,KAAK,IAAI,GAAG;AAC5B,MAAI,YAAY,WAAW;AACzB,SAAK,IAAI,KAAK,SAAS;AAAA,EACzB,WAAW,YAAY,WAAW;AAChC,SAAK,OAAO,GAAG;AAAA,EACjB,OAAO;AACL,SAAK,IAAI,KAAK,SAAS;AAAA,EACzB;AACA,SAAO;AACT;AAEO,SAAS,mBACd,OACA,OACS;AACT,MAAI,MAAM,SAAS,EAAG,QAAO;AAE7B,QAAM,8BAAc,IAAA;AACpB,QAAM,8BAAc,IAAA;AACpB,aAAW,CAAC,KAAK,IAAI,KAAK,MAAM,WAAW;AACzC,QAAI,SAAS,UAAW,SAAQ,IAAI,GAAG;AACvC,QAAI,SAAS,UAAW,SAAQ,IAAI,GAAG;AAAA,EACzC;AAEA,MAAI,SAAS,QAAQ,IAAI,KAAK,EAAG,QAAO;AACxC,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,SAAO,CAAC,CAAC,SAAS,QAAQ,IAAI,KAAK;AACrC;"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const UiTerminal = require("../../icons/components/UiTerminal.cjs");
|
|
4
|
+
const UiMarkdown = require("../../icons/components/UiMarkdown.cjs");
|
|
5
|
+
const UiGlobe = require("../../icons/components/UiGlobe.cjs");
|
|
6
|
+
const UiBeaker = require("../../icons/components/UiBeaker.cjs");
|
|
7
|
+
const UiTest = require("../../icons/components/UiTest.cjs");
|
|
8
|
+
function frameworkIcon(framework) {
|
|
9
|
+
switch (framework) {
|
|
10
|
+
case "go test":
|
|
11
|
+
case "ginkgo":
|
|
12
|
+
return UiTest.UiTest;
|
|
13
|
+
case "jest":
|
|
14
|
+
case "vitest":
|
|
15
|
+
return UiBeaker.UiBeaker;
|
|
16
|
+
case "playwright":
|
|
17
|
+
return UiGlobe.UiGlobe;
|
|
18
|
+
case "fixture":
|
|
19
|
+
return UiMarkdown.UiMarkdown;
|
|
20
|
+
case "task":
|
|
21
|
+
return UiTerminal.UiTerminal;
|
|
22
|
+
default:
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.frameworkIcon = frameworkIcon;
|
|
27
|
+
//# sourceMappingURL=frameworkIcon.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameworkIcon.cjs","sources":["../../../src/data/test-runner/frameworkIcon.ts"],"sourcesContent":["// Maps a test framework to a native clicky icon component. We deliberately use\n// only imported Ui* components (not iconify runtime name strings) so framework\n// glyphs always resolve without a fallback icon provider — runtime names render\n// as a dashed \"?\" placeholder when no provider is registered.\n//\n// Hosts wanting brand logos (devicon/logos) can override per node via a\n// TestNodeAdapter's `renderRowLeading`, which renders before the name.\n\nimport type { StaticIconComponent } from \"../Icon\";\nimport { UiBeaker, UiGlobe, UiMarkdown, UiTerminal, UiTest } from \"../../icons\";\n\nexport function frameworkIcon(framework?: string): StaticIconComponent | null {\n switch (framework) {\n case \"go test\":\n case \"ginkgo\":\n return UiTest;\n case \"jest\":\n case \"vitest\":\n return UiBeaker;\n case \"playwright\":\n return UiGlobe;\n case \"fixture\":\n return UiMarkdown;\n case \"task\":\n return UiTerminal;\n default:\n return null;\n }\n}\n"],"names":["UiTest","UiBeaker","UiGlobe","UiMarkdown","UiTerminal"],"mappings":";;;;;;;AAWO,SAAS,cAAc,WAAgD;AAC5E,UAAQ,WAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAOA,OAAAA;AAAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAOC,SAAAA;AAAAA,IACT,KAAK;AACH,aAAOC,QAAAA;AAAAA,IACT,KAAK;AACH,aAAOC,WAAAA;AAAAA,IACT,KAAK;AACH,aAAOC,WAAAA;AAAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameworkIcon.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/frameworkIcon.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAGnD,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAiB5E"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { UiTerminal } from "../../icons/components/UiTerminal.js";
|
|
2
|
+
import { UiMarkdown } from "../../icons/components/UiMarkdown.js";
|
|
3
|
+
import { UiGlobe } from "../../icons/components/UiGlobe.js";
|
|
4
|
+
import { UiBeaker } from "../../icons/components/UiBeaker.js";
|
|
5
|
+
import { UiTest } from "../../icons/components/UiTest.js";
|
|
6
|
+
function frameworkIcon(framework) {
|
|
7
|
+
switch (framework) {
|
|
8
|
+
case "go test":
|
|
9
|
+
case "ginkgo":
|
|
10
|
+
return UiTest;
|
|
11
|
+
case "jest":
|
|
12
|
+
case "vitest":
|
|
13
|
+
return UiBeaker;
|
|
14
|
+
case "playwright":
|
|
15
|
+
return UiGlobe;
|
|
16
|
+
case "fixture":
|
|
17
|
+
return UiMarkdown;
|
|
18
|
+
case "task":
|
|
19
|
+
return UiTerminal;
|
|
20
|
+
default:
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
frameworkIcon
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=frameworkIcon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frameworkIcon.js","sources":["../../../src/data/test-runner/frameworkIcon.ts"],"sourcesContent":["// Maps a test framework to a native clicky icon component. We deliberately use\n// only imported Ui* components (not iconify runtime name strings) so framework\n// glyphs always resolve without a fallback icon provider — runtime names render\n// as a dashed \"?\" placeholder when no provider is registered.\n//\n// Hosts wanting brand logos (devicon/logos) can override per node via a\n// TestNodeAdapter's `renderRowLeading`, which renders before the name.\n\nimport type { StaticIconComponent } from \"../Icon\";\nimport { UiBeaker, UiGlobe, UiMarkdown, UiTerminal, UiTest } from \"../../icons\";\n\nexport function frameworkIcon(framework?: string): StaticIconComponent | null {\n switch (framework) {\n case \"go test\":\n case \"ginkgo\":\n return UiTest;\n case \"jest\":\n case \"vitest\":\n return UiBeaker;\n case \"playwright\":\n return UiGlobe;\n case \"fixture\":\n return UiMarkdown;\n case \"task\":\n return UiTerminal;\n default:\n return null;\n }\n}\n"],"names":[],"mappings":";;;;;AAWO,SAAS,cAAc,WAAgD;AAC5E,UAAQ,WAAA;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EAAA;AAEb;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { TestRunner, type TestRunnerProps } from './TestRunner';
|
|
2
|
+
export { TestTree } from './TestTree';
|
|
3
|
+
export { TestTreeNode } from './TestTreeNode';
|
|
4
|
+
export { TestDetailPanel } from './TestDetailPanel';
|
|
5
|
+
export { TestFailureDetail, TestOutput, TestAttempts, } from './TestFailureDetail';
|
|
6
|
+
export { TestRunSummary, type TestRunSummaryProps } from './TestRunSummary';
|
|
7
|
+
export { TestFilterBar, type TestFilterBarProps } from './TestFilterBar';
|
|
8
|
+
export { TestRunnerProvider, useTestRunner, type TestRunnerContext, } from './context';
|
|
9
|
+
export { TestNodeAdapterRegistry, createTestRunnerRegistry, type TestNodeAdapter, type AdapterContext, type DetailTab, } from './adapter';
|
|
10
|
+
export { type StatusCounts, type TestStatus, sum, sumNonTaskTests, hasFailed, hasPending, testStatus, totalDuration, statusIconFor, statusToneFor, formatTestDuration, formatCount, humanizeName, collectFrameworks, collapseSingleChildChains, filterTests, } from './status';
|
|
11
|
+
export { frameworkIcon } from './frameworkIcon';
|
|
12
|
+
export { type FilterMode, type FilterState, type TestFilters, emptyTestFilters, decodeFilterState, encodeFilterState, cycleFilterState, matchesFilterState, } from './filterState';
|
|
13
|
+
export type { Test, TestSummary, TestAttempt, FailureDetail, FailureKind, GoTestContext, GinkgoContext, FixtureContext, TaskContext, RunMeta, Snapshot, SnapshotGit, SnapshotStatus, } from './types';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/data/test-runner/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,YAAY,GACb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,KAAK,iBAAiB,GACvB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,SAAS,GACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,GAAG,EACH,eAAe,EACf,SAAS,EACT,UAAU,EACV,UAAU,EACV,aAAa,EACb,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,yBAAyB,EACzB,WAAW,GACZ,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EACL,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,IAAI,EACJ,WAAW,EACX,WAAW,EACX,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,cAAc,EACd,WAAW,EACX,OAAO,EACP,QAAQ,EACR,WAAW,EACX,cAAc,GACf,MAAM,SAAS,CAAC"}
|