executable-stories-react 0.1.16 → 0.2.1
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/index.cjs +1767 -371
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +148 -14
- package/dist/index.d.ts +148 -14
- package/dist/index.js +1748 -370
- package/dist/index.js.map +1 -1
- package/dist/interactive.cjs +1485 -265
- package/dist/interactive.cjs.map +1 -1
- package/dist/interactive.d.cts +55 -4
- package/dist/interactive.d.ts +55 -4
- package/dist/interactive.js +1474 -255
- package/dist/interactive.js.map +1 -1
- package/dist/parse.cjs +631 -154
- package/dist/parse.cjs.map +1 -1
- package/dist/parse.d.cts +12 -4
- package/dist/parse.d.ts +12 -4
- package/dist/parse.js +631 -154
- package/dist/parse.js.map +1 -1
- package/dist/report-island.global.js +132 -0
- package/dist/ssr.cjs +1235 -0
- package/dist/ssr.cjs.map +1 -0
- package/dist/ssr.d.cts +72 -0
- package/dist/ssr.d.ts +72 -0
- package/dist/ssr.js +1210 -0
- package/dist/ssr.js.map +1 -0
- package/dist/styles.css +21 -6
- package/dist/tailwind.css +2 -0
- package/package.json +43 -8
package/dist/interactive.cjs
CHANGED
|
@@ -23,8 +23,10 @@ var interactive_exports = {};
|
|
|
23
23
|
__export(interactive_exports, {
|
|
24
24
|
ReportFailureBanner: () => ReportFailureBanner,
|
|
25
25
|
ReportInteractive: () => ReportInteractive,
|
|
26
|
+
ReportInteractiveIsland: () => ReportInteractiveIsland,
|
|
26
27
|
ReportSearch: () => ReportSearch,
|
|
27
28
|
ReportShortcutsHelp: () => ReportShortcutsHelp,
|
|
29
|
+
buildIslandRenderers: () => buildIslandRenderers,
|
|
28
30
|
filterReport: () => filterReport,
|
|
29
31
|
listFailures: () => listFailures,
|
|
30
32
|
normalizeQuery: () => normalizeQuery,
|
|
@@ -34,7 +36,16 @@ __export(interactive_exports, {
|
|
|
34
36
|
module.exports = __toCommonJS(interactive_exports);
|
|
35
37
|
|
|
36
38
|
// src/interactive/ReportInteractive.tsx
|
|
37
|
-
var
|
|
39
|
+
var import_react14 = require("react");
|
|
40
|
+
|
|
41
|
+
// src/result.ts
|
|
42
|
+
var ok = (data) => ({ ok: true, data });
|
|
43
|
+
function unwrapReport(value) {
|
|
44
|
+
if (typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean") {
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
return ok(value);
|
|
48
|
+
}
|
|
38
49
|
|
|
39
50
|
// src/context/ReportRoot.tsx
|
|
40
51
|
var import_react2 = require("react");
|
|
@@ -76,74 +87,80 @@ function useReport() {
|
|
|
76
87
|
return ctx.report;
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
// src/components/
|
|
90
|
+
// src/components/ReportScenario.tsx
|
|
91
|
+
var import_duration3 = require("executable-stories-core/utils/duration");
|
|
92
|
+
|
|
93
|
+
// src/components/ReportSteps.tsx
|
|
94
|
+
var import_duration = require("executable-stories-core/utils/duration");
|
|
95
|
+
|
|
96
|
+
// src/components/doc/DocNote.tsx
|
|
80
97
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
84
|
-
ReportSummaryView,
|
|
85
|
-
{
|
|
86
|
-
summary: report.summary,
|
|
87
|
-
...className !== void 0 && { className },
|
|
88
|
-
ariaLabel: "Run summary"
|
|
89
|
-
}
|
|
90
|
-
);
|
|
98
|
+
function DocNote({ entry }) {
|
|
99
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "my-2 text-sm text-muted-foreground", children: entry.text });
|
|
91
100
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
] }),
|
|
104
|
-
" \xB7 ",
|
|
105
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "passed", children: [
|
|
106
|
-
summary.passed,
|
|
107
|
-
" passed"
|
|
108
|
-
] }),
|
|
109
|
-
" \xB7 ",
|
|
110
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "failed", children: [
|
|
111
|
-
summary.failed,
|
|
112
|
-
" failed"
|
|
113
|
-
] }),
|
|
114
|
-
summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
115
|
-
" \xB7 ",
|
|
116
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "skipped", children: [
|
|
117
|
-
summary.skipped,
|
|
118
|
-
" skipped"
|
|
119
|
-
] })
|
|
120
|
-
] }) : null,
|
|
121
|
-
summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
122
|
-
" \xB7 ",
|
|
123
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "pending", children: [
|
|
124
|
-
summary.pending,
|
|
125
|
-
" pending"
|
|
126
|
-
] })
|
|
127
|
-
] }) : null
|
|
128
|
-
]
|
|
129
|
-
}
|
|
130
|
-
);
|
|
101
|
+
|
|
102
|
+
// src/components/ui/badge.tsx
|
|
103
|
+
var React = require("react");
|
|
104
|
+
var import_class_variance_authority = require("class-variance-authority");
|
|
105
|
+
var import_use_render = require("@base-ui/react/use-render");
|
|
106
|
+
|
|
107
|
+
// src/lib/utils.ts
|
|
108
|
+
var import_clsx = require("clsx");
|
|
109
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
110
|
+
function cn(...inputs) {
|
|
111
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
131
112
|
}
|
|
132
113
|
|
|
133
|
-
// src/components/
|
|
134
|
-
var
|
|
135
|
-
|
|
136
|
-
|
|
114
|
+
// src/components/ui/badge.tsx
|
|
115
|
+
var badgeVariants = (0, import_class_variance_authority.cva)(
|
|
116
|
+
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
|
|
117
|
+
{
|
|
118
|
+
variants: {
|
|
119
|
+
variant: {
|
|
120
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
121
|
+
secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
122
|
+
destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
|
|
123
|
+
outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
124
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
125
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline",
|
|
126
|
+
passed: "border-pass-border bg-pass-bg text-pass font-mono uppercase",
|
|
127
|
+
failed: "border-fail-border bg-fail-bg text-fail font-mono uppercase",
|
|
128
|
+
skipped: "border-skip-border bg-skip-bg text-skip font-mono uppercase",
|
|
129
|
+
pending: "border-pend-border bg-pend-bg text-pend font-mono uppercase",
|
|
130
|
+
tag: "border-pass-border bg-pass-bg text-pass font-mono font-medium"
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
defaultVariants: {
|
|
134
|
+
variant: "default"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
function Badge({
|
|
139
|
+
className,
|
|
140
|
+
variant = "default",
|
|
141
|
+
render,
|
|
142
|
+
...props
|
|
143
|
+
}) {
|
|
144
|
+
return (0, import_use_render.useRender)({
|
|
145
|
+
defaultTagName: "span",
|
|
146
|
+
render,
|
|
147
|
+
props: {
|
|
148
|
+
"data-slot": "badge",
|
|
149
|
+
"data-variant": variant,
|
|
150
|
+
className: cn(badgeVariants({ variant }), className),
|
|
151
|
+
...props
|
|
152
|
+
}
|
|
153
|
+
});
|
|
137
154
|
}
|
|
138
155
|
|
|
139
156
|
// src/components/doc/DocTag.tsx
|
|
140
|
-
var
|
|
157
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
141
158
|
function DocTag({ entry }) {
|
|
142
|
-
return /* @__PURE__ */ (0,
|
|
159
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "my-2 flex flex-wrap gap-1.5", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Badge, { variant: "tag", children: n }) }, n)) });
|
|
143
160
|
}
|
|
144
161
|
|
|
145
162
|
// src/components/doc/DocKv.tsx
|
|
146
|
-
var
|
|
163
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
147
164
|
function formatValue(value) {
|
|
148
165
|
if (value === null) return "null";
|
|
149
166
|
if (typeof value === "string") return value;
|
|
@@ -155,9 +172,9 @@ function formatValue(value) {
|
|
|
155
172
|
}
|
|
156
173
|
}
|
|
157
174
|
function DocKv({ entry }) {
|
|
158
|
-
return /* @__PURE__ */ (0,
|
|
159
|
-
/* @__PURE__ */ (0,
|
|
160
|
-
/* @__PURE__ */ (0,
|
|
175
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("dl", { className: "my-2 flex flex-wrap items-baseline gap-x-2 text-sm", children: [
|
|
176
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dt", { className: "font-semibold text-muted-foreground", children: entry.label }),
|
|
177
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("dd", { className: "font-mono text-foreground", children: formatValue(entry.value) })
|
|
161
178
|
] });
|
|
162
179
|
}
|
|
163
180
|
|
|
@@ -175,37 +192,40 @@ function useBuiltinRenderers() {
|
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
// src/components/doc/DocCode.tsx
|
|
178
|
-
var
|
|
195
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
196
|
+
function CodeFigure({ label, children }) {
|
|
197
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("figure", { className: "my-3", children: [
|
|
198
|
+
label ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: label }) : null,
|
|
199
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3", children })
|
|
200
|
+
] });
|
|
201
|
+
}
|
|
179
202
|
function DocCode({ entry }) {
|
|
180
203
|
const renderers = useBuiltinRenderers();
|
|
181
204
|
if (renderers.code) {
|
|
182
|
-
return /* @__PURE__ */ (0,
|
|
205
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: renderers.code(entry) });
|
|
183
206
|
}
|
|
184
|
-
return /* @__PURE__ */ (0,
|
|
185
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { children: entry.label }),
|
|
186
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: entry.lang ? `language-${entry.lang}` : void 0, children: entry.content }) })
|
|
187
|
-
] });
|
|
207
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CodeFigure, { label: entry.label, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
|
|
188
208
|
}
|
|
189
209
|
|
|
190
210
|
// src/components/doc/DocTable.tsx
|
|
191
|
-
var
|
|
211
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
192
212
|
function DocTable({ entry }) {
|
|
193
|
-
return /* @__PURE__ */ (0,
|
|
194
|
-
/* @__PURE__ */ (0,
|
|
195
|
-
/* @__PURE__ */ (0,
|
|
196
|
-
/* @__PURE__ */ (0,
|
|
197
|
-
/* @__PURE__ */ (0,
|
|
198
|
-
] })
|
|
213
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "my-3", children: [
|
|
214
|
+
entry.label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { tabIndex: 0, className: "overflow-x-auto rounded-md border border-border", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { className: "w-full border-collapse text-sm", children: [
|
|
216
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { className: "bg-muted/50", children: entry.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { scope: "col", className: "border-b border-border px-3 py-2 text-left font-semibold", children: c }, c)) }) }),
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tr", { children: row.map((cell, j) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell }, j)) }, i)) })
|
|
218
|
+
] }) })
|
|
199
219
|
] });
|
|
200
220
|
}
|
|
201
221
|
|
|
202
222
|
// src/components/doc/DocLink.tsx
|
|
203
|
-
var
|
|
223
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
204
224
|
function DocLink({ entry }) {
|
|
205
|
-
return /* @__PURE__ */ (0,
|
|
225
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
206
226
|
"a",
|
|
207
227
|
{
|
|
208
|
-
className: "
|
|
228
|
+
className: "text-sm font-medium text-primary underline underline-offset-2 hover:no-underline",
|
|
209
229
|
href: entry.url,
|
|
210
230
|
rel: "noreferrer noopener",
|
|
211
231
|
target: "_blank",
|
|
@@ -216,23 +236,46 @@ function DocLink({ entry }) {
|
|
|
216
236
|
|
|
217
237
|
// src/components/doc/DocSection.tsx
|
|
218
238
|
var import_marked = require("marked");
|
|
219
|
-
|
|
239
|
+
|
|
240
|
+
// src/lib/url.ts
|
|
241
|
+
function safeUrl(value) {
|
|
242
|
+
if (!value) return void 0;
|
|
243
|
+
const trimmed = value.trim();
|
|
244
|
+
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(trimmed);
|
|
245
|
+
if (scheme && !/^https?$/i.test(scheme[1] ?? "")) return void 0;
|
|
246
|
+
return trimmed;
|
|
247
|
+
}
|
|
248
|
+
function safeImageUrl(value) {
|
|
249
|
+
if (!value) return void 0;
|
|
250
|
+
const trimmed = value.trim();
|
|
251
|
+
if (/^data:image\//i.test(trimmed)) return trimmed;
|
|
252
|
+
return safeUrl(trimmed);
|
|
253
|
+
}
|
|
254
|
+
function isLocalFsPath(value) {
|
|
255
|
+
return /^(?:[/\\]|[A-Za-z]:[/\\])/.test(value);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/components/doc/DocSection.tsx
|
|
259
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
260
|
+
function neutralizeUrl(value) {
|
|
261
|
+
return safeUrl(value) ?? "#";
|
|
262
|
+
}
|
|
220
263
|
function safeMarkdownHtml(markdown) {
|
|
221
264
|
const raw = import_marked.marked.parse(markdown, { async: false });
|
|
222
|
-
return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"
|
|
265
|
+
return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<\/?(?:iframe|object|embed|form|base)\b[^>]*>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"([^"]*)"/gi, (_m, attr, val) => `${attr}="${neutralizeUrl(val)}"`).replace(/(href|src)\s*=\s*'([^']*)'/gi, (_m, attr, val) => `${attr}='${neutralizeUrl(val)}'`);
|
|
223
266
|
}
|
|
224
267
|
function DocSection({ entry }) {
|
|
225
268
|
const renderers = useBuiltinRenderers();
|
|
226
269
|
if (renderers.section) {
|
|
227
|
-
return /* @__PURE__ */ (0,
|
|
270
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: renderers.section(entry) });
|
|
228
271
|
}
|
|
229
272
|
const html = safeMarkdownHtml(entry.markdown);
|
|
230
|
-
return /* @__PURE__ */ (0,
|
|
231
|
-
entry.title ? /* @__PURE__ */ (0,
|
|
232
|
-
/* @__PURE__ */ (0,
|
|
273
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { className: "my-3 text-sm", "aria-label": entry.title, children: [
|
|
274
|
+
entry.title ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { className: "mb-1 font-semibold text-foreground", children: entry.title }) : null,
|
|
275
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
233
276
|
"div",
|
|
234
277
|
{
|
|
235
|
-
className: "es-doc-
|
|
278
|
+
className: "es-doc-prose text-muted-foreground",
|
|
236
279
|
dangerouslySetInnerHTML: { __html: html }
|
|
237
280
|
}
|
|
238
281
|
)
|
|
@@ -240,45 +283,129 @@ function DocSection({ entry }) {
|
|
|
240
283
|
}
|
|
241
284
|
|
|
242
285
|
// src/components/doc/DocMermaid.tsx
|
|
243
|
-
var
|
|
286
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
287
|
+
function MermaidSource({ entry, ref }) {
|
|
288
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("figure", { ref, className: "my-3", "aria-label": entry.title ?? "Diagram", children: [
|
|
289
|
+
entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
|
|
290
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
291
|
+
"pre",
|
|
292
|
+
{
|
|
293
|
+
"data-mermaid": true,
|
|
294
|
+
tabIndex: 0,
|
|
295
|
+
className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground",
|
|
296
|
+
children: entry.code
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
] });
|
|
300
|
+
}
|
|
244
301
|
function DocMermaid({ entry }) {
|
|
245
302
|
const renderers = useBuiltinRenderers();
|
|
246
303
|
if (renderers.mermaid) {
|
|
247
|
-
return /* @__PURE__ */ (0,
|
|
304
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.mermaid(entry) });
|
|
248
305
|
}
|
|
249
|
-
return /* @__PURE__ */ (0,
|
|
250
|
-
"figure",
|
|
251
|
-
{
|
|
252
|
-
className: "es-doc es-doc-mermaid",
|
|
253
|
-
"aria-label": entry.title ?? "Diagram",
|
|
254
|
-
children: [
|
|
255
|
-
entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { children: entry.title }) : null,
|
|
256
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { "data-mermaid": true, children: entry.code })
|
|
257
|
-
]
|
|
258
|
-
}
|
|
259
|
-
);
|
|
306
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MermaidSource, { entry });
|
|
260
307
|
}
|
|
261
308
|
|
|
262
309
|
// src/components/doc/DocScreenshot.tsx
|
|
263
|
-
var
|
|
310
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
264
311
|
function DocScreenshot({ entry }) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
312
|
+
const src = isLocalFsPath(entry.path) ? void 0 : safeImageUrl(entry.path);
|
|
313
|
+
if (!src) {
|
|
314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
|
|
315
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "font-medium", children: "Screenshot unavailable" }),
|
|
316
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
|
|
317
|
+
entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.alt }) : null
|
|
318
|
+
] });
|
|
319
|
+
}
|
|
320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("figure", { className: "my-3", children: [
|
|
321
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
322
|
+
"img",
|
|
323
|
+
{
|
|
324
|
+
src,
|
|
325
|
+
alt: entry.alt ?? "",
|
|
326
|
+
loading: "lazy",
|
|
327
|
+
className: "max-w-full rounded-md border border-border"
|
|
328
|
+
}
|
|
329
|
+
),
|
|
330
|
+
entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { className: "mt-1.5 text-xs text-muted-foreground", children: entry.alt }) : null
|
|
268
331
|
] });
|
|
269
332
|
}
|
|
270
333
|
|
|
271
|
-
// src/components/doc/
|
|
334
|
+
// src/components/doc/DocVideo.tsx
|
|
335
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
336
|
+
function DocVideo({ entry }) {
|
|
337
|
+
if (isLocalFsPath(entry.path)) {
|
|
338
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3 rounded-md border border-dashed border-border p-3 text-sm text-muted-foreground", children: [
|
|
339
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "font-medium", children: "Video unavailable" }),
|
|
340
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "mt-1 break-all font-mono text-xs", children: entry.path }),
|
|
341
|
+
entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1.5 text-xs", children: entry.caption }) : null
|
|
342
|
+
] });
|
|
343
|
+
}
|
|
344
|
+
const src = safeUrl(entry.path);
|
|
345
|
+
const poster = safeUrl(entry.poster);
|
|
346
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "my-3", children: [
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
348
|
+
"video",
|
|
349
|
+
{
|
|
350
|
+
controls: true,
|
|
351
|
+
preload: "metadata",
|
|
352
|
+
...src ? { src } : {},
|
|
353
|
+
...poster ? { poster } : {},
|
|
354
|
+
className: "max-w-full rounded-md border border-border"
|
|
355
|
+
}
|
|
356
|
+
),
|
|
357
|
+
entry.caption ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: entry.caption }) : null
|
|
358
|
+
] });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/components/doc/DocHtml.tsx
|
|
272
362
|
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
363
|
+
function DocHtml({ entry }) {
|
|
364
|
+
const height = typeof entry.height === "number" ? `${entry.height}px` : entry.height ?? "400px";
|
|
365
|
+
const title = entry.title ?? "Embedded HTML";
|
|
366
|
+
const safeHref = safeUrl(entry.url ?? entry.path);
|
|
367
|
+
const frameProps = entry.content !== void 0 ? { srcDoc: entry.content } : { src: safeHref ?? "" };
|
|
368
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figure", { className: "my-3 overflow-hidden rounded-md border border-border", children: [
|
|
369
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("figcaption", { className: "flex items-center justify-between border-b border-border bg-muted/40 px-3 py-1.5 text-xs font-medium text-muted-foreground", children: [
|
|
370
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { children: entry.title ?? "HTML" }),
|
|
371
|
+
safeHref ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
372
|
+
"a",
|
|
373
|
+
{
|
|
374
|
+
href: safeHref,
|
|
375
|
+
target: "_blank",
|
|
376
|
+
rel: "noreferrer noopener",
|
|
377
|
+
"aria-label": "Open in new tab",
|
|
378
|
+
title: "Open in new tab",
|
|
379
|
+
className: "text-primary",
|
|
380
|
+
children: "\u2197"
|
|
381
|
+
}
|
|
382
|
+
) : null
|
|
383
|
+
] }),
|
|
384
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
385
|
+
"iframe",
|
|
386
|
+
{
|
|
387
|
+
...frameProps,
|
|
388
|
+
sandbox: "allow-scripts",
|
|
389
|
+
loading: "lazy",
|
|
390
|
+
title,
|
|
391
|
+
className: "block w-full border-0",
|
|
392
|
+
style: { height }
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
] });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/components/doc/DocCustom.tsx
|
|
399
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
273
400
|
function DocCustom({ entry }) {
|
|
274
401
|
const renderers = useCustomRenderers();
|
|
275
402
|
const renderer = renderers[entry.type];
|
|
276
403
|
if (renderer) {
|
|
277
|
-
return /* @__PURE__ */ (0,
|
|
404
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_jsx_runtime13.Fragment, { children: renderer(entry) });
|
|
278
405
|
}
|
|
279
|
-
return /* @__PURE__ */ (0,
|
|
280
|
-
/* @__PURE__ */ (0,
|
|
281
|
-
/* @__PURE__ */ (0,
|
|
406
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "my-2", "data-type": entry.type, children: [
|
|
407
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: entry.type }),
|
|
408
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("pre", { tabIndex: 0, className: "overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs text-foreground", children: safeStringify(entry.data) })
|
|
282
409
|
] });
|
|
283
410
|
}
|
|
284
411
|
function safeStringify(value) {
|
|
@@ -290,158 +417,810 @@ function safeStringify(value) {
|
|
|
290
417
|
}
|
|
291
418
|
|
|
292
419
|
// src/components/doc/DocEntry.tsx
|
|
293
|
-
var
|
|
420
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
294
421
|
function DocEntry({ entry }) {
|
|
295
422
|
switch (entry.kind) {
|
|
296
423
|
case "note":
|
|
297
|
-
return /* @__PURE__ */ (0,
|
|
424
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocNote, { entry });
|
|
298
425
|
case "tag":
|
|
299
|
-
return /* @__PURE__ */ (0,
|
|
426
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocTag, { entry });
|
|
300
427
|
case "kv":
|
|
301
|
-
return /* @__PURE__ */ (0,
|
|
428
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocKv, { entry });
|
|
302
429
|
case "code":
|
|
303
|
-
return /* @__PURE__ */ (0,
|
|
430
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocCode, { entry });
|
|
304
431
|
case "table":
|
|
305
|
-
return /* @__PURE__ */ (0,
|
|
432
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocTable, { entry });
|
|
306
433
|
case "link":
|
|
307
|
-
return /* @__PURE__ */ (0,
|
|
434
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocLink, { entry });
|
|
308
435
|
case "section":
|
|
309
|
-
return /* @__PURE__ */ (0,
|
|
436
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocSection, { entry });
|
|
310
437
|
case "mermaid":
|
|
311
|
-
return /* @__PURE__ */ (0,
|
|
438
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocMermaid, { entry });
|
|
312
439
|
case "screenshot":
|
|
313
|
-
return /* @__PURE__ */ (0,
|
|
440
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocScreenshot, { entry });
|
|
441
|
+
case "video":
|
|
442
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocVideo, { entry });
|
|
443
|
+
case "html":
|
|
444
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocHtml, { entry });
|
|
314
445
|
case "custom":
|
|
315
|
-
return /* @__PURE__ */ (0,
|
|
446
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocCustom, { entry });
|
|
316
447
|
}
|
|
317
448
|
}
|
|
318
449
|
|
|
319
450
|
// src/components/ReportDocEntries.tsx
|
|
320
|
-
var
|
|
451
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
321
452
|
function ReportDocEntries({ entries }) {
|
|
322
453
|
if (entries.length === 0) return null;
|
|
323
|
-
return /* @__PURE__ */ (0,
|
|
454
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_jsx_runtime15.Fragment, { children: entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DocEntry, { entry }, i)) });
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/lib/step-params.tsx
|
|
458
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
459
|
+
var STEP_PARAM_PATTERN = /"[^"]*"|(?<![\w.-])\d+(?:\.\d+)?(?![\w.-])/g;
|
|
460
|
+
function highlightStepParams(text) {
|
|
461
|
+
const matches = Array.from(text.matchAll(STEP_PARAM_PATTERN));
|
|
462
|
+
if (matches.length === 0) return text;
|
|
463
|
+
const nodes = [];
|
|
464
|
+
let lastIndex = 0;
|
|
465
|
+
matches.forEach((match, i) => {
|
|
466
|
+
const start = match.index;
|
|
467
|
+
const end = start + match[0].length;
|
|
468
|
+
if (start > lastIndex) nodes.push(text.slice(lastIndex, start));
|
|
469
|
+
nodes.push(
|
|
470
|
+
/* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "font-medium text-step-param italic", children: match[0] }, i)
|
|
471
|
+
);
|
|
472
|
+
lastIndex = end;
|
|
473
|
+
});
|
|
474
|
+
if (lastIndex < text.length) nodes.push(text.slice(lastIndex));
|
|
475
|
+
return nodes;
|
|
324
476
|
}
|
|
325
477
|
|
|
326
478
|
// src/components/ReportSteps.tsx
|
|
327
|
-
var
|
|
479
|
+
var import_jsx_runtime17 = require("react/jsx-runtime");
|
|
480
|
+
var STEP_GLYPH = {
|
|
481
|
+
passed: "\u2713",
|
|
482
|
+
failed: "\u2717",
|
|
483
|
+
skipped: "\u25CB",
|
|
484
|
+
pending: "\u25CB"
|
|
485
|
+
};
|
|
486
|
+
var STEP_GLYPH_COLOR = {
|
|
487
|
+
passed: "text-pass",
|
|
488
|
+
failed: "text-fail",
|
|
489
|
+
skipped: "text-skip",
|
|
490
|
+
pending: "text-pend"
|
|
491
|
+
};
|
|
328
492
|
function ReportSteps({ scenario }) {
|
|
329
493
|
if (scenario.steps.length === 0) return null;
|
|
330
|
-
return /* @__PURE__ */ (0,
|
|
494
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("ol", { "data-slot": "steps", className: "flex flex-col", children: scenario.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ReportStepItem, { step }, step.id)) });
|
|
331
495
|
}
|
|
332
496
|
function ReportStepItem({ step }) {
|
|
333
|
-
|
|
497
|
+
const kw = step.keyword.toLowerCase();
|
|
498
|
+
const continuation = kw === "and" || kw === "but";
|
|
499
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
334
500
|
"li",
|
|
335
501
|
{
|
|
336
502
|
id: step.id,
|
|
337
|
-
className: `es-step es-step-${step.status}`,
|
|
338
503
|
"data-status": step.status,
|
|
504
|
+
className: cn("py-1.5 text-[0.8125rem] leading-normal", continuation && "pl-5"),
|
|
339
505
|
children: [
|
|
340
|
-
/* @__PURE__ */ (0,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
506
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-baseline gap-2", children: [
|
|
507
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { "aria-hidden": true, className: cn("w-4 shrink-0 text-center text-xs", STEP_GLYPH_COLOR[step.status]), children: STEP_GLYPH[step.status] ?? "\u25CB" }),
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
509
|
+
"span",
|
|
510
|
+
{
|
|
511
|
+
className: cn(
|
|
512
|
+
"min-w-[52px] shrink-0 font-mono text-xs font-semibold capitalize",
|
|
513
|
+
continuation ? "font-medium text-muted-foreground" : "text-keyword"
|
|
514
|
+
),
|
|
515
|
+
children: step.keyword
|
|
516
|
+
}
|
|
517
|
+
),
|
|
518
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "flex-1 text-foreground", children: highlightStepParams(step.text) }),
|
|
519
|
+
step.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "shrink-0 whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration.formatDuration)(step.durationMs) }) : null
|
|
520
|
+
] }),
|
|
521
|
+
step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
522
|
+
"pre",
|
|
523
|
+
{
|
|
524
|
+
role: "alert",
|
|
525
|
+
className: "mt-2.5 ml-6 overflow-x-auto rounded-md border border-fail-border bg-fail-bg px-4 py-3.5 font-mono text-xs leading-relaxed whitespace-pre-wrap text-fail",
|
|
526
|
+
children: step.errorMessage
|
|
527
|
+
}
|
|
528
|
+
) : null,
|
|
529
|
+
step.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { "data-es-docs": true, className: "mt-2 ml-6", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ReportDocEntries, { entries: step.docEntries }) }) : null
|
|
344
530
|
]
|
|
345
531
|
}
|
|
346
532
|
);
|
|
347
533
|
}
|
|
348
534
|
|
|
535
|
+
// src/components/ReportAttachments.tsx
|
|
536
|
+
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
537
|
+
function ReportAttachments({ attachments }) {
|
|
538
|
+
if (attachments.length === 0) return null;
|
|
539
|
+
return (
|
|
540
|
+
// role="group" (not a labelled <section>) so multiple scenarios' attachment
|
|
541
|
+
// blocks don't create duplicate "Attachments" region landmarks.
|
|
542
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { role: "group", "aria-label": "Attachments", className: "mt-3 flex flex-col gap-2", children: attachments.map((att, i) => {
|
|
543
|
+
const dataUri = att.contentEncoding === "BASE64" ? `data:${att.mediaType};base64,${att.body}` : `data:${att.mediaType},${encodeURIComponent(att.body)}`;
|
|
544
|
+
const isImage = att.mediaType.startsWith("image/");
|
|
545
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("figure", { className: "m-0", children: isImage ? /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
|
|
546
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
547
|
+
"img",
|
|
548
|
+
{
|
|
549
|
+
src: dataUri,
|
|
550
|
+
alt: att.name,
|
|
551
|
+
loading: "lazy",
|
|
552
|
+
className: "max-w-full rounded-md border border-border"
|
|
553
|
+
}
|
|
554
|
+
),
|
|
555
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)("figcaption", { className: "mt-1 text-xs text-muted-foreground", children: att.name })
|
|
556
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
557
|
+
"a",
|
|
558
|
+
{
|
|
559
|
+
href: dataUri,
|
|
560
|
+
download: att.name,
|
|
561
|
+
className: "text-xs text-link underline underline-offset-2",
|
|
562
|
+
children: [
|
|
563
|
+
att.name,
|
|
564
|
+
" ",
|
|
565
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-muted-foreground", children: [
|
|
566
|
+
"(",
|
|
567
|
+
att.mediaType,
|
|
568
|
+
")"
|
|
569
|
+
] })
|
|
570
|
+
]
|
|
571
|
+
}
|
|
572
|
+
) }, `${att.name}-${i}`);
|
|
573
|
+
}) })
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/components/ReportTrace.tsx
|
|
578
|
+
var import_duration2 = require("executable-stories-core/utils/duration");
|
|
579
|
+
|
|
580
|
+
// src/lib/trace.ts
|
|
581
|
+
var VALID = /* @__PURE__ */ new Set(["ok", "error", "unset"]);
|
|
582
|
+
var safeStatus = (s) => VALID.has(s) ? s : "unset";
|
|
583
|
+
function normalizeSpans(spans) {
|
|
584
|
+
const out = [];
|
|
585
|
+
for (const span of spans) {
|
|
586
|
+
if (!span || typeof span.spanId !== "string" || typeof span.name !== "string") continue;
|
|
587
|
+
let startTimeMs;
|
|
588
|
+
let durationMs;
|
|
589
|
+
if (span.startTimeMs != null && span.durationMs != null) {
|
|
590
|
+
startTimeMs = span.startTimeMs;
|
|
591
|
+
durationMs = span.durationMs;
|
|
592
|
+
} else if (span.startTimeUnixNano != null && span.endTimeUnixNano != null) {
|
|
593
|
+
startTimeMs = span.startTimeUnixNano / 1e6;
|
|
594
|
+
durationMs = (span.endTimeUnixNano - span.startTimeUnixNano) / 1e6;
|
|
595
|
+
} else {
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
durationMs = Math.max(0, durationMs);
|
|
599
|
+
if (!isFinite(startTimeMs) || !isFinite(durationMs)) continue;
|
|
600
|
+
out.push({
|
|
601
|
+
spanId: span.spanId,
|
|
602
|
+
parentSpanId: span.parentSpanId,
|
|
603
|
+
name: span.name,
|
|
604
|
+
startTimeMs,
|
|
605
|
+
durationMs,
|
|
606
|
+
status: safeStatus(span.status),
|
|
607
|
+
statusMessage: span.statusMessage
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
return out;
|
|
611
|
+
}
|
|
612
|
+
function toTraceRows(spans) {
|
|
613
|
+
const normalized = normalizeSpans(spans);
|
|
614
|
+
if (normalized.length === 0) return [];
|
|
615
|
+
const byId = /* @__PURE__ */ new Map();
|
|
616
|
+
for (const span of normalized) {
|
|
617
|
+
let key = span.spanId;
|
|
618
|
+
let suffix = 2;
|
|
619
|
+
while (byId.has(key)) key = `${span.spanId}__dup${suffix++}`;
|
|
620
|
+
byId.set(key, { span: { ...span, spanId: key }, children: [], depth: 0 });
|
|
621
|
+
}
|
|
622
|
+
const roots = [];
|
|
623
|
+
for (const node of byId.values()) {
|
|
624
|
+
const parent = node.span.parentSpanId ? byId.get(node.span.parentSpanId) : void 0;
|
|
625
|
+
if (parent && parent !== node) parent.children.push(node);
|
|
626
|
+
else roots.push(node);
|
|
627
|
+
}
|
|
628
|
+
for (const node of byId.values()) node.children.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
629
|
+
roots.sort((a, b) => a.span.startTimeMs - b.span.startTimeMs);
|
|
630
|
+
const rows = [];
|
|
631
|
+
const seen = /* @__PURE__ */ new Set();
|
|
632
|
+
const walk = (node, depth) => {
|
|
633
|
+
if (seen.has(node.span.spanId)) return;
|
|
634
|
+
seen.add(node.span.spanId);
|
|
635
|
+
rows.push({ ...node.span, depth });
|
|
636
|
+
for (const child of node.children) walk(child, depth + 1);
|
|
637
|
+
};
|
|
638
|
+
for (const root of roots) walk(root, 0);
|
|
639
|
+
for (const node of byId.values()) {
|
|
640
|
+
if (!seen.has(node.span.spanId)) walk(node, 0);
|
|
641
|
+
}
|
|
642
|
+
return rows;
|
|
643
|
+
}
|
|
644
|
+
function traceWindow(rows) {
|
|
645
|
+
let min = Infinity;
|
|
646
|
+
let max = -Infinity;
|
|
647
|
+
for (const r of rows) {
|
|
648
|
+
min = Math.min(min, r.startTimeMs);
|
|
649
|
+
max = Math.max(max, r.startTimeMs + r.durationMs);
|
|
650
|
+
}
|
|
651
|
+
const total = max - min;
|
|
652
|
+
return { minStartMs: isFinite(min) ? min : 0, totalMs: total > 0 ? total : 1 };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// src/components/ReportTrace.tsx
|
|
656
|
+
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
657
|
+
var BAR_COLOR = {
|
|
658
|
+
ok: "bg-pass",
|
|
659
|
+
error: "bg-fail",
|
|
660
|
+
unset: "bg-skip"
|
|
661
|
+
};
|
|
662
|
+
function ReportTrace({ spans }) {
|
|
663
|
+
if (!spans || spans.length === 0) return null;
|
|
664
|
+
const rows = toTraceRows(spans);
|
|
665
|
+
if (rows.length === 0) return null;
|
|
666
|
+
const { minStartMs, totalMs } = traceWindow(rows);
|
|
667
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("details", { className: "mt-3 rounded-md border border-border bg-muted/30", children: [
|
|
668
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("summary", { className: "cursor-pointer px-3 py-2 text-xs font-medium text-muted-foreground", children: [
|
|
669
|
+
"Trace (",
|
|
670
|
+
rows.length,
|
|
671
|
+
" span",
|
|
672
|
+
rows.length === 1 ? "" : "s",
|
|
673
|
+
", ",
|
|
674
|
+
(0, import_duration2.formatDuration)(totalMs),
|
|
675
|
+
")"
|
|
676
|
+
] }),
|
|
677
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "flex flex-col gap-0.5 px-3 pb-3 font-mono text-[0.6875rem]", children: rows.map((row, i) => {
|
|
678
|
+
const leftPct = (row.startTimeMs - minStartMs) / totalMs * 100;
|
|
679
|
+
const widthPct = Math.max(row.durationMs / totalMs * 100, 0.5);
|
|
680
|
+
return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("li", { className: "flex items-center gap-2", children: [
|
|
681
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
682
|
+
"span",
|
|
683
|
+
{
|
|
684
|
+
className: "min-w-0 flex-1 truncate text-foreground",
|
|
685
|
+
style: { paddingLeft: `${row.depth * 12}px` },
|
|
686
|
+
title: row.statusMessage ? `${row.name} \u2014 ${row.statusMessage}` : row.name,
|
|
687
|
+
children: row.name
|
|
688
|
+
}
|
|
689
|
+
),
|
|
690
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "relative h-3 w-1/2 shrink-0 overflow-hidden rounded-sm bg-border/40", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
691
|
+
"span",
|
|
692
|
+
{
|
|
693
|
+
className: cn("absolute top-0 h-full rounded-sm", BAR_COLOR[row.status]),
|
|
694
|
+
style: { left: `${leftPct}%`, width: `${widthPct}%` }
|
|
695
|
+
}
|
|
696
|
+
) }),
|
|
697
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 shrink-0 text-right text-muted-foreground", children: (0, import_duration2.formatDuration)(row.durationMs) })
|
|
698
|
+
] }, `${row.spanId}-${i}`);
|
|
699
|
+
}) })
|
|
700
|
+
] });
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/interactive/collapse-context.ts
|
|
704
|
+
var import_react5 = require("react");
|
|
705
|
+
var CollapseContext = (0, import_react5.createContext)(null);
|
|
706
|
+
var CollapseProvider = CollapseContext.Provider;
|
|
707
|
+
function useCollapse() {
|
|
708
|
+
return (0, import_react5.useContext)(CollapseContext);
|
|
709
|
+
}
|
|
710
|
+
var STORAGE_KEY = "es-collapsed-ids";
|
|
711
|
+
function readPersisted() {
|
|
712
|
+
if (typeof localStorage === "undefined") return /* @__PURE__ */ new Set();
|
|
713
|
+
try {
|
|
714
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
715
|
+
return raw ? new Set(JSON.parse(raw)) : /* @__PURE__ */ new Set();
|
|
716
|
+
} catch {
|
|
717
|
+
return /* @__PURE__ */ new Set();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function persist(ids) {
|
|
721
|
+
if (typeof localStorage === "undefined") return;
|
|
722
|
+
try {
|
|
723
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify([...ids]));
|
|
724
|
+
} catch {
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
function useCollapseState() {
|
|
728
|
+
const [collapsed, setCollapsed] = (0, import_react5.useState)(readPersisted);
|
|
729
|
+
const isCollapsed = (0, import_react5.useCallback)((id) => collapsed.has(id), [collapsed]);
|
|
730
|
+
const toggle = (0, import_react5.useCallback)((id) => {
|
|
731
|
+
setCollapsed((prev) => {
|
|
732
|
+
const next = new Set(prev);
|
|
733
|
+
if (next.has(id)) next.delete(id);
|
|
734
|
+
else next.add(id);
|
|
735
|
+
persist(next);
|
|
736
|
+
return next;
|
|
737
|
+
});
|
|
738
|
+
}, []);
|
|
739
|
+
const collapseAll = (0, import_react5.useCallback)((ids) => {
|
|
740
|
+
setCollapsed(() => {
|
|
741
|
+
const next = new Set(ids);
|
|
742
|
+
persist(next);
|
|
743
|
+
return next;
|
|
744
|
+
});
|
|
745
|
+
}, []);
|
|
746
|
+
const expandAll = (0, import_react5.useCallback)(() => {
|
|
747
|
+
setCollapsed(() => {
|
|
748
|
+
const next = /* @__PURE__ */ new Set();
|
|
749
|
+
persist(next);
|
|
750
|
+
return next;
|
|
751
|
+
});
|
|
752
|
+
}, []);
|
|
753
|
+
return { api: { isCollapsed, toggle }, collapseAll, expandAll };
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/interactive/scenario-actions.tsx
|
|
757
|
+
var import_react6 = require("react");
|
|
758
|
+
var ScenarioActionsContext = (0, import_react6.createContext)(null);
|
|
759
|
+
var ScenarioActionsProvider = ScenarioActionsContext.Provider;
|
|
760
|
+
function useScenarioActions() {
|
|
761
|
+
return (0, import_react6.useContext)(ScenarioActionsContext);
|
|
762
|
+
}
|
|
763
|
+
function scenarioToMarkdown(scenario) {
|
|
764
|
+
const lines = [`## ${scenario.title} _(${scenario.status})_`, ""];
|
|
765
|
+
for (const step of scenario.steps) {
|
|
766
|
+
lines.push(`- **${step.keyword}** ${step.text}`);
|
|
767
|
+
}
|
|
768
|
+
if (scenario.errorMessage) {
|
|
769
|
+
lines.push("", "```", scenario.errorMessage.trim(), "```");
|
|
770
|
+
}
|
|
771
|
+
return lines.join("\n");
|
|
772
|
+
}
|
|
773
|
+
function scenarioToPrompt(scenario) {
|
|
774
|
+
const lines = [
|
|
775
|
+
`A test scenario is ${scenario.status}. Investigate the likely cause and propose a fix.`,
|
|
776
|
+
"",
|
|
777
|
+
`Scenario: ${scenario.title}`,
|
|
778
|
+
"Steps:"
|
|
779
|
+
];
|
|
780
|
+
for (const step of scenario.steps) {
|
|
781
|
+
const mark = step.status === "failed" ? " \u27F5 failed here" : "";
|
|
782
|
+
lines.push(` ${step.keyword} ${step.text}${mark}`);
|
|
783
|
+
}
|
|
784
|
+
if (scenario.errorMessage) {
|
|
785
|
+
lines.push("", "Error:", scenario.errorMessage.trim());
|
|
786
|
+
}
|
|
787
|
+
if (scenario.errorStack) {
|
|
788
|
+
lines.push("", "Stack:", scenario.errorStack.trim());
|
|
789
|
+
}
|
|
790
|
+
return lines.join("\n");
|
|
791
|
+
}
|
|
792
|
+
function scenarioPermalink(scenario) {
|
|
793
|
+
if (typeof location === "undefined") return `#${scenario.id}`;
|
|
794
|
+
return `${location.href.split("#")[0]}#${scenario.id}`;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/components/ui/card.tsx
|
|
798
|
+
var React2 = require("react");
|
|
799
|
+
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
800
|
+
function Card({ className, ...props }) {
|
|
801
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
802
|
+
"div",
|
|
803
|
+
{
|
|
804
|
+
"data-slot": "card",
|
|
805
|
+
className: cn(
|
|
806
|
+
"flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
|
|
807
|
+
className
|
|
808
|
+
),
|
|
809
|
+
...props
|
|
810
|
+
}
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
function CardHeader({ className, ...props }) {
|
|
814
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
815
|
+
"div",
|
|
816
|
+
{
|
|
817
|
+
"data-slot": "card-header",
|
|
818
|
+
className: cn(
|
|
819
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
820
|
+
className
|
|
821
|
+
),
|
|
822
|
+
...props
|
|
823
|
+
}
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
function CardContent({ className, ...props }) {
|
|
827
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
828
|
+
"div",
|
|
829
|
+
{
|
|
830
|
+
"data-slot": "card-content",
|
|
831
|
+
className: cn("px-6", className),
|
|
832
|
+
...props
|
|
833
|
+
}
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
|
|
349
837
|
// src/components/ReportScenario.tsx
|
|
350
|
-
var
|
|
838
|
+
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
351
839
|
var STATUS_LABEL = {
|
|
352
840
|
passed: "Passed",
|
|
353
841
|
failed: "Failed",
|
|
354
842
|
skipped: "Skipped",
|
|
355
843
|
pending: "Pending"
|
|
356
844
|
};
|
|
357
|
-
|
|
845
|
+
var STATUS_GLYPH = {
|
|
846
|
+
passed: "\u2713",
|
|
847
|
+
failed: "\u2717",
|
|
848
|
+
skipped: "\u25CB",
|
|
849
|
+
pending: "\u25CB"
|
|
850
|
+
};
|
|
851
|
+
var STATUS_GLYPH_COLOR = {
|
|
852
|
+
passed: "text-pass",
|
|
853
|
+
failed: "text-fail",
|
|
854
|
+
skipped: "text-skip",
|
|
855
|
+
pending: "text-pend"
|
|
856
|
+
};
|
|
857
|
+
var STATUS_BADGE = {
|
|
858
|
+
passed: "passed",
|
|
859
|
+
failed: "failed",
|
|
860
|
+
skipped: "skipped",
|
|
861
|
+
pending: "pending"
|
|
862
|
+
};
|
|
863
|
+
function ReportScenario({ scenario, hideTitle = false }) {
|
|
358
864
|
const titleId = `${scenario.id}-title`;
|
|
359
|
-
|
|
360
|
-
|
|
865
|
+
const bodyId = `${scenario.id}-body`;
|
|
866
|
+
const label = STATUS_LABEL[scenario.status];
|
|
867
|
+
const collapse = useCollapse();
|
|
868
|
+
const collapsible = collapse !== null && !hideTitle;
|
|
869
|
+
const collapsed = collapsible ? collapse.isCollapsed(scenario.id) : false;
|
|
870
|
+
const actions = useScenarioActions();
|
|
871
|
+
return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
|
|
872
|
+
Card,
|
|
361
873
|
{
|
|
362
874
|
id: scenario.id,
|
|
363
|
-
className: `es-scenario es-status-${scenario.status}`,
|
|
364
|
-
"aria-labelledby": titleId,
|
|
365
875
|
"data-status": scenario.status,
|
|
876
|
+
...hideTitle ? { "aria-label": `${scenario.title} \u2014 ${label}` } : { "aria-labelledby": titleId },
|
|
877
|
+
className: "gap-0 py-0",
|
|
366
878
|
children: [
|
|
367
|
-
/* @__PURE__ */ (0,
|
|
368
|
-
/* @__PURE__ */ (0,
|
|
369
|
-
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardHeader, { className: "px-4 py-3", children: [
|
|
880
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: cn("flex items-start gap-4", hideTitle ? "justify-end" : "justify-between"), children: [
|
|
881
|
+
hideTitle ? null : /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("h3", { id: titleId, className: "flex items-center gap-2 text-sm font-medium text-foreground", children: [
|
|
882
|
+
collapsible ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
883
|
+
"button",
|
|
884
|
+
{
|
|
885
|
+
type: "button",
|
|
886
|
+
onClick: () => collapse.toggle(scenario.id),
|
|
887
|
+
"aria-expanded": !collapsed,
|
|
888
|
+
"aria-controls": bodyId,
|
|
889
|
+
"aria-label": `Toggle ${scenario.title}`,
|
|
890
|
+
className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
|
|
891
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
|
|
892
|
+
}
|
|
893
|
+
) : null,
|
|
894
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { "aria-hidden": true, className: cn("text-sm leading-none", STATUS_GLYPH_COLOR[scenario.status]), children: STATUS_GLYPH[scenario.status] }),
|
|
895
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: scenario.title })
|
|
896
|
+
] }),
|
|
897
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
898
|
+
scenario.durationMs > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "whitespace-nowrap font-mono text-[0.6875rem] text-muted-foreground", children: (0, import_duration3.formatDuration)(scenario.durationMs) }) : null,
|
|
899
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: STATUS_BADGE[scenario.status], "aria-label": `Status: ${label}`, children: label }),
|
|
900
|
+
actions ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-0.5", children: [
|
|
901
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
902
|
+
"button",
|
|
903
|
+
{
|
|
904
|
+
type: "button",
|
|
905
|
+
onClick: () => actions.copyLink(scenario),
|
|
906
|
+
"aria-label": `Copy link to ${scenario.title}`,
|
|
907
|
+
title: "Copy permalink",
|
|
908
|
+
className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
909
|
+
children: "link"
|
|
910
|
+
}
|
|
911
|
+
),
|
|
912
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
913
|
+
"button",
|
|
914
|
+
{
|
|
915
|
+
type: "button",
|
|
916
|
+
onClick: () => actions.copyMarkdown(scenario),
|
|
917
|
+
"aria-label": `Copy ${scenario.title} as Markdown`,
|
|
918
|
+
title: "Copy as Markdown",
|
|
919
|
+
className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
920
|
+
children: "md"
|
|
921
|
+
}
|
|
922
|
+
),
|
|
923
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
924
|
+
"button",
|
|
925
|
+
{
|
|
926
|
+
type: "button",
|
|
927
|
+
onClick: () => actions.copyPrompt(scenario),
|
|
928
|
+
"aria-label": `Copy ${scenario.title} as an AI investigation prompt`,
|
|
929
|
+
title: "Copy as AI prompt",
|
|
930
|
+
className: "cursor-pointer rounded px-1 py-0.5 font-mono text-[0.625rem] text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
931
|
+
children: "ai"
|
|
932
|
+
}
|
|
933
|
+
)
|
|
934
|
+
] }) : null
|
|
935
|
+
] })
|
|
936
|
+
] }),
|
|
937
|
+
scenario.tags.length > 0 || (scenario.tickets?.length ?? 0) > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("ul", { "aria-label": "Tags and tickets", className: "flex flex-wrap gap-1.5", children: [
|
|
938
|
+
scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "tag", children: t }) }, `tag-${t}`)),
|
|
939
|
+
(scenario.tickets ?? []).map((ticket) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: ticket.url ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("a", { href: ticket.url, target: "_blank", rel: "noreferrer noopener", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: ticket.id }) }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Badge, { variant: "outline", children: ticket.id }) }, `ticket-${ticket.id}`))
|
|
940
|
+
] }) : null
|
|
370
941
|
] }),
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
942
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(CardContent, { id: bodyId, hidden: collapsed, className: "px-4 pt-0 pb-4", children: [
|
|
943
|
+
scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
944
|
+
"pre",
|
|
945
|
+
{
|
|
946
|
+
role: "alert",
|
|
947
|
+
className: "mb-1 overflow-x-auto rounded-md border border-fail-border bg-fail-bg px-4 py-3.5 font-mono text-xs leading-relaxed whitespace-pre-wrap text-fail",
|
|
948
|
+
children: scenario.errorMessage
|
|
949
|
+
}
|
|
950
|
+
) : null,
|
|
951
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportSteps, { scenario }),
|
|
952
|
+
scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { "data-es-docs": true, className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
|
|
953
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportAttachments, { attachments: scenario.attachments }),
|
|
954
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ReportTrace, { spans: scenario.otelSpans })
|
|
955
|
+
] })
|
|
375
956
|
]
|
|
376
957
|
}
|
|
377
958
|
);
|
|
378
959
|
}
|
|
379
960
|
|
|
380
961
|
// src/components/ReportScenarioList.tsx
|
|
381
|
-
var
|
|
962
|
+
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
382
963
|
function ReportScenarioList({ feature }) {
|
|
383
|
-
return /* @__PURE__ */ (0,
|
|
964
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_jsx_runtime22.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ReportScenario, { scenario }, scenario.id)) });
|
|
384
965
|
}
|
|
385
966
|
|
|
386
967
|
// src/components/ReportFeature.tsx
|
|
387
|
-
var
|
|
968
|
+
var import_jsx_runtime23 = require("react/jsx-runtime");
|
|
388
969
|
function ReportFeature({ feature }) {
|
|
389
970
|
const titleId = `${feature.id}-title`;
|
|
390
|
-
|
|
971
|
+
const bodyId = `${feature.id}-body`;
|
|
972
|
+
const s = feature.summary;
|
|
973
|
+
const skipped = s.skipped + s.pending;
|
|
974
|
+
const collapse = useCollapse();
|
|
975
|
+
const collapsed = collapse?.isCollapsed(feature.id) ?? false;
|
|
976
|
+
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
|
|
391
977
|
"section",
|
|
392
978
|
{
|
|
393
979
|
id: feature.id,
|
|
394
|
-
|
|
980
|
+
"data-slot": "feature",
|
|
395
981
|
"aria-labelledby": titleId,
|
|
982
|
+
className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
|
|
396
983
|
children: [
|
|
397
|
-
/* @__PURE__ */ (0,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
984
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center justify-between gap-4 border-b border-border bg-muted/40 px-4 py-3", children: [
|
|
985
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
986
|
+
collapse ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
|
|
987
|
+
"button",
|
|
988
|
+
{
|
|
989
|
+
type: "button",
|
|
990
|
+
onClick: () => collapse.toggle(feature.id),
|
|
991
|
+
"aria-expanded": !collapsed,
|
|
992
|
+
"aria-controls": bodyId,
|
|
993
|
+
"aria-label": `Toggle ${feature.title}`,
|
|
994
|
+
className: "shrink-0 cursor-pointer text-muted-foreground hover:text-foreground",
|
|
995
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { "aria-hidden": true, className: cn("inline-block text-xs transition-transform", !collapsed && "rotate-90"), children: "\u25B8" })
|
|
996
|
+
}
|
|
997
|
+
) : null,
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "min-w-0", children: [
|
|
999
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h2", { id: titleId, className: "text-sm font-semibold tracking-tight text-foreground", children: feature.title }),
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "mt-0.5 truncate font-mono text-xs text-muted-foreground", children: feature.sourceFile })
|
|
1001
|
+
] })
|
|
1002
|
+
] }),
|
|
1003
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
|
|
1004
|
+
"div",
|
|
1005
|
+
{
|
|
1006
|
+
role: "img",
|
|
1007
|
+
className: "flex shrink-0 items-center gap-2.5 font-mono text-xs font-medium",
|
|
1008
|
+
"aria-label": `${s.passed} passed, ${s.failed} failed, ${skipped} skipped`,
|
|
1009
|
+
children: [
|
|
1010
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-pass", children: [
|
|
1011
|
+
"\u2713",
|
|
1012
|
+
s.passed
|
|
1013
|
+
] }),
|
|
1014
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-fail", children: [
|
|
1015
|
+
"\u2717",
|
|
1016
|
+
s.failed
|
|
1017
|
+
] }),
|
|
1018
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { "aria-hidden": true, className: "text-skip", children: [
|
|
1019
|
+
"\u25CB",
|
|
1020
|
+
skipped
|
|
1021
|
+
] })
|
|
1022
|
+
]
|
|
1023
|
+
}
|
|
1024
|
+
)
|
|
1025
|
+
] }),
|
|
1026
|
+
/* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { id: bodyId, hidden: collapsed, className: "flex flex-col gap-2 p-3", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ReportScenarioList, { feature }) })
|
|
401
1027
|
]
|
|
402
1028
|
}
|
|
403
1029
|
);
|
|
404
1030
|
}
|
|
405
1031
|
|
|
406
1032
|
// src/components/ReportFeatureList.tsx
|
|
407
|
-
var
|
|
1033
|
+
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
408
1034
|
function ReportFeatureList() {
|
|
409
1035
|
const report = useReport();
|
|
410
|
-
return /* @__PURE__ */ (0,
|
|
1036
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_jsx_runtime24.Fragment, { children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(ReportFeature, { feature }, feature.id)) });
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// src/components/ui/empty.tsx
|
|
1040
|
+
var import_class_variance_authority2 = require("class-variance-authority");
|
|
1041
|
+
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
1042
|
+
function Empty({ className, ...props }) {
|
|
1043
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1044
|
+
"div",
|
|
1045
|
+
{
|
|
1046
|
+
"data-slot": "empty",
|
|
1047
|
+
className: cn(
|
|
1048
|
+
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
|
|
1049
|
+
className
|
|
1050
|
+
),
|
|
1051
|
+
...props
|
|
1052
|
+
}
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
function EmptyHeader({ className, ...props }) {
|
|
1056
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1057
|
+
"div",
|
|
1058
|
+
{
|
|
1059
|
+
"data-slot": "empty-header",
|
|
1060
|
+
className: cn(
|
|
1061
|
+
"flex max-w-sm flex-col items-center gap-2 text-center",
|
|
1062
|
+
className
|
|
1063
|
+
),
|
|
1064
|
+
...props
|
|
1065
|
+
}
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
var emptyMediaVariants = (0, import_class_variance_authority2.cva)(
|
|
1069
|
+
"mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1070
|
+
{
|
|
1071
|
+
variants: {
|
|
1072
|
+
variant: {
|
|
1073
|
+
default: "bg-transparent",
|
|
1074
|
+
icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6"
|
|
1075
|
+
}
|
|
1076
|
+
},
|
|
1077
|
+
defaultVariants: {
|
|
1078
|
+
variant: "default"
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
function EmptyTitle({ className, ...props }) {
|
|
1083
|
+
return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
|
|
1084
|
+
"div",
|
|
1085
|
+
{
|
|
1086
|
+
"data-slot": "empty-title",
|
|
1087
|
+
className: cn("text-lg font-medium tracking-tight", className),
|
|
1088
|
+
...props
|
|
1089
|
+
}
|
|
1090
|
+
);
|
|
411
1091
|
}
|
|
412
1092
|
|
|
413
1093
|
// src/components/ReportEmpty.tsx
|
|
414
|
-
var
|
|
1094
|
+
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
415
1095
|
function ReportEmpty({ message }) {
|
|
416
|
-
return /* @__PURE__ */ (0,
|
|
1096
|
+
return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(Empty, { "aria-live": "polite", className: "border", children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EmptyHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(EmptyTitle, { children: message ?? "No scenarios in this report." }) }) });
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// src/components/ReportSummary.tsx
|
|
1100
|
+
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
1101
|
+
function ReportSummary({ className }) {
|
|
1102
|
+
const report = useReport();
|
|
1103
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
|
|
1104
|
+
ReportSummaryView,
|
|
1105
|
+
{
|
|
1106
|
+
summary: report.summary,
|
|
1107
|
+
...className !== void 0 && { className },
|
|
1108
|
+
ariaLabel: "Run summary"
|
|
1109
|
+
}
|
|
1110
|
+
);
|
|
1111
|
+
}
|
|
1112
|
+
function ReportSummaryView({ summary, className, ariaLabel }) {
|
|
1113
|
+
return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("p", { className: cn("text-sm text-muted-foreground", className), "aria-label": ariaLabel, children: [
|
|
1114
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { children: [
|
|
1115
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsx)("strong", { className: "font-semibold text-foreground", children: summary.total }),
|
|
1116
|
+
" scenario",
|
|
1117
|
+
summary.total === 1 ? "" : "s"
|
|
1118
|
+
] }),
|
|
1119
|
+
" \xB7 ",
|
|
1120
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "passed", className: "text-pass", children: [
|
|
1121
|
+
summary.passed,
|
|
1122
|
+
" passed"
|
|
1123
|
+
] }),
|
|
1124
|
+
" \xB7 ",
|
|
1125
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "failed", className: "text-fail", children: [
|
|
1126
|
+
summary.failed,
|
|
1127
|
+
" failed"
|
|
1128
|
+
] }),
|
|
1129
|
+
summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|
|
1130
|
+
" \xB7 ",
|
|
1131
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "skipped", className: "text-skip", children: [
|
|
1132
|
+
summary.skipped,
|
|
1133
|
+
" skipped"
|
|
1134
|
+
] })
|
|
1135
|
+
] }) : null,
|
|
1136
|
+
summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
|
|
1137
|
+
" \xB7 ",
|
|
1138
|
+
/* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("span", { "data-status": "pending", className: "text-pend", children: [
|
|
1139
|
+
summary.pending,
|
|
1140
|
+
" pending"
|
|
1141
|
+
] })
|
|
1142
|
+
] }) : null
|
|
1143
|
+
] });
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// src/components/ReportMeta.tsx
|
|
1147
|
+
var import_duration4 = require("executable-stories-core/utils/duration");
|
|
1148
|
+
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
1149
|
+
function ReportMeta() {
|
|
1150
|
+
const report = useReport();
|
|
1151
|
+
const items = [];
|
|
1152
|
+
if (report.startedAtMs) {
|
|
1153
|
+
items.push({ label: "Started", value: new Date(report.startedAtMs).toLocaleString() });
|
|
1154
|
+
}
|
|
1155
|
+
if (report.durationMs) {
|
|
1156
|
+
items.push({ label: "Duration", value: (0, import_duration4.formatDuration)(report.durationMs) });
|
|
1157
|
+
}
|
|
1158
|
+
if (report.packageVersion) {
|
|
1159
|
+
items.push({ label: "Version", value: report.packageVersion });
|
|
1160
|
+
}
|
|
1161
|
+
if (report.gitSha) {
|
|
1162
|
+
items.push({ label: "Commit", value: report.gitSha.slice(0, 8) });
|
|
1163
|
+
}
|
|
1164
|
+
if (report.ci) {
|
|
1165
|
+
items.push({
|
|
1166
|
+
label: "CI",
|
|
1167
|
+
value: report.ci.url ? /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("a", { href: report.ci.url, target: "_blank", rel: "noreferrer noopener", className: "text-primary underline underline-offset-2", children: [
|
|
1168
|
+
report.ci.name,
|
|
1169
|
+
report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""
|
|
1170
|
+
] }) : `${report.ci.name}${report.ci.buildNumber ? ` #${report.ci.buildNumber}` : ""}`
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
if (items.length === 0) return null;
|
|
1174
|
+
return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dl", { "aria-label": "Run metadata", className: "flex flex-wrap gap-x-6 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "flex gap-1.5", children: [
|
|
1175
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dt", { className: "font-medium", children: item.label }),
|
|
1176
|
+
/* @__PURE__ */ (0, import_jsx_runtime28.jsx)("dd", { className: "font-mono text-foreground", children: item.value })
|
|
1177
|
+
] }, item.label)) });
|
|
417
1178
|
}
|
|
418
1179
|
|
|
419
1180
|
// src/components/ReportSchemaError.tsx
|
|
420
|
-
var
|
|
1181
|
+
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
421
1182
|
function ReportSchemaError({ error }) {
|
|
422
|
-
return /* @__PURE__ */ (0,
|
|
423
|
-
/* @__PURE__ */ (0,
|
|
424
|
-
/* @__PURE__ */ (0,
|
|
425
|
-
error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0,
|
|
1183
|
+
return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
|
|
1184
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("strong", { children: "Report could not be displayed." }) }),
|
|
1185
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("p", { children: error.message }),
|
|
1186
|
+
error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("p", { children: [
|
|
426
1187
|
"The report bundle is newer than this version of ",
|
|
427
|
-
/* @__PURE__ */ (0,
|
|
1188
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("code", { children: "executable-stories-react" }),
|
|
428
1189
|
". Upgrade the package, or regenerate the report with an older formatters CLI."
|
|
429
1190
|
] }) : null,
|
|
430
|
-
error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0,
|
|
431
|
-
/* @__PURE__ */ (0,
|
|
1191
|
+
error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("details", { children: [
|
|
1192
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("summary", { children: [
|
|
432
1193
|
error.issues.length,
|
|
433
1194
|
" validation issue",
|
|
434
1195
|
error.issues.length === 1 ? "" : "s"
|
|
435
1196
|
] }),
|
|
436
|
-
/* @__PURE__ */ (0,
|
|
1197
|
+
/* @__PURE__ */ (0, import_jsx_runtime29.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
|
|
437
1198
|
] }) : null
|
|
438
1199
|
] });
|
|
439
1200
|
}
|
|
440
1201
|
|
|
1202
|
+
// src/components/ReportShell.tsx
|
|
1203
|
+
var import_jsx_runtime30 = require("react/jsx-runtime");
|
|
1204
|
+
function ReportTitleBlock({ title }) {
|
|
1205
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
|
|
1206
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)("h1", { children: title ?? "Story Report" }),
|
|
1207
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportSummary, {}),
|
|
1208
|
+
/* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportMeta, {})
|
|
1209
|
+
] });
|
|
1210
|
+
}
|
|
1211
|
+
function ReportErrorShell({
|
|
1212
|
+
error,
|
|
1213
|
+
className,
|
|
1214
|
+
title,
|
|
1215
|
+
dataTheme
|
|
1216
|
+
}) {
|
|
1217
|
+
return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("main", { className: cn("es-report", className), "aria-label": title ?? "Test report", "data-theme": dataTheme, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ReportSchemaError, { error }) });
|
|
1218
|
+
}
|
|
1219
|
+
|
|
441
1220
|
// src/interactive/ReportSearch.tsx
|
|
442
|
-
var
|
|
443
|
-
var
|
|
444
|
-
var ReportSearch = (0,
|
|
1221
|
+
var import_react7 = require("react");
|
|
1222
|
+
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
1223
|
+
var ReportSearch = (0, import_react7.forwardRef)(
|
|
445
1224
|
function ReportSearch2(props, ref) {
|
|
446
1225
|
const {
|
|
447
1226
|
value,
|
|
@@ -451,7 +1230,7 @@ var ReportSearch = (0, import_react5.forwardRef)(
|
|
|
451
1230
|
placeholder = "Search scenarios, tags, or step text\u2026",
|
|
452
1231
|
className
|
|
453
1232
|
} = props;
|
|
454
|
-
const inputId = (0,
|
|
1233
|
+
const inputId = (0, import_react7.useId)();
|
|
455
1234
|
const showCounts = typeof matchedCount === "number" && typeof totalCount === "number";
|
|
456
1235
|
function handleChange(e) {
|
|
457
1236
|
onChange(e.target.value);
|
|
@@ -462,9 +1241,9 @@ var ReportSearch = (0, import_react5.forwardRef)(
|
|
|
462
1241
|
e.preventDefault();
|
|
463
1242
|
}
|
|
464
1243
|
}
|
|
465
|
-
return /* @__PURE__ */ (0,
|
|
466
|
-
/* @__PURE__ */ (0,
|
|
467
|
-
/* @__PURE__ */ (0,
|
|
1244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
|
|
1245
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
|
|
1246
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
468
1247
|
"input",
|
|
469
1248
|
{
|
|
470
1249
|
ref,
|
|
@@ -479,41 +1258,58 @@ var ReportSearch = (0, import_react5.forwardRef)(
|
|
|
479
1258
|
"aria-keyshortcuts": "/"
|
|
480
1259
|
}
|
|
481
1260
|
),
|
|
482
|
-
showCounts ? /* @__PURE__ */ (0,
|
|
1261
|
+
showCounts ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
|
|
483
1262
|
] });
|
|
484
1263
|
}
|
|
485
1264
|
);
|
|
486
1265
|
|
|
487
1266
|
// src/interactive/ReportFailureBanner.tsx
|
|
488
|
-
var
|
|
489
|
-
|
|
1267
|
+
var import_react8 = require("react");
|
|
1268
|
+
|
|
1269
|
+
// src/lib/scroll.ts
|
|
1270
|
+
function prefersReducedMotion() {
|
|
1271
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
1272
|
+
return false;
|
|
1273
|
+
}
|
|
1274
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1275
|
+
}
|
|
1276
|
+
function scrollToScenarioId(scenarioId, options = {}) {
|
|
1277
|
+
if (typeof document === "undefined") return;
|
|
1278
|
+
const el = document.getElementById(scenarioId);
|
|
1279
|
+
if (!el) return;
|
|
1280
|
+
el.scrollIntoView({
|
|
1281
|
+
behavior: prefersReducedMotion() ? "auto" : "smooth",
|
|
1282
|
+
block: "start"
|
|
1283
|
+
});
|
|
1284
|
+
if (options.updateHash !== false && typeof history !== "undefined") {
|
|
1285
|
+
history.replaceState(null, "", `#${scenarioId}`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// src/interactive/ReportFailureBanner.tsx
|
|
1290
|
+
var import_jsx_runtime32 = require("react/jsx-runtime");
|
|
490
1291
|
function ReportFailureBanner({ failures }) {
|
|
491
1292
|
const first = failures[0];
|
|
492
|
-
const jumpToFirst = (0,
|
|
1293
|
+
const jumpToFirst = (0, import_react8.useCallback)(() => {
|
|
493
1294
|
if (!first) return;
|
|
494
|
-
|
|
495
|
-
const el = document.getElementById(first.scenarioId);
|
|
496
|
-
el?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
497
|
-
if (typeof history !== "undefined") {
|
|
498
|
-
history.replaceState(null, "", `#${first.scenarioId}`);
|
|
499
|
-
}
|
|
1295
|
+
scrollToScenarioId(first.scenarioId);
|
|
500
1296
|
}, [first]);
|
|
501
1297
|
if (failures.length === 0) return null;
|
|
502
|
-
return /* @__PURE__ */ (0,
|
|
503
|
-
"
|
|
1298
|
+
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
|
|
1299
|
+
"div",
|
|
504
1300
|
{
|
|
505
1301
|
className: "es-failure-banner",
|
|
506
1302
|
role: "status",
|
|
507
1303
|
"aria-live": "polite",
|
|
508
1304
|
"aria-label": "Failure summary",
|
|
509
1305
|
children: [
|
|
510
|
-
/* @__PURE__ */ (0,
|
|
511
|
-
/* @__PURE__ */ (0,
|
|
1306
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: "es-failure-banner-text", children: [
|
|
1307
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)("strong", { children: failures.length }),
|
|
512
1308
|
" ",
|
|
513
1309
|
"failure",
|
|
514
1310
|
failures.length === 1 ? "" : "s"
|
|
515
1311
|
] }),
|
|
516
|
-
/* @__PURE__ */ (0,
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
|
|
517
1313
|
"button",
|
|
518
1314
|
{
|
|
519
1315
|
type: "button",
|
|
@@ -529,8 +1325,8 @@ function ReportFailureBanner({ failures }) {
|
|
|
529
1325
|
}
|
|
530
1326
|
|
|
531
1327
|
// src/interactive/ReportShortcutsHelp.tsx
|
|
532
|
-
var
|
|
533
|
-
var
|
|
1328
|
+
var import_react9 = require("react");
|
|
1329
|
+
var import_jsx_runtime33 = require("react/jsx-runtime");
|
|
534
1330
|
var SHORTCUTS = [
|
|
535
1331
|
{ keys: "/", description: "Focus search" },
|
|
536
1332
|
{ keys: "f", description: "Jump to next failure" },
|
|
@@ -539,14 +1335,14 @@ var SHORTCUTS = [
|
|
|
539
1335
|
{ keys: "?", description: "Toggle this help" }
|
|
540
1336
|
];
|
|
541
1337
|
function ReportShortcutsHelp({ open, onClose }) {
|
|
542
|
-
const dialogRef = (0,
|
|
543
|
-
(0,
|
|
1338
|
+
const dialogRef = (0, import_react9.useRef)(null);
|
|
1339
|
+
(0, import_react9.useEffect)(() => {
|
|
544
1340
|
const dialog = dialogRef.current;
|
|
545
1341
|
if (!dialog) return;
|
|
546
1342
|
if (open && !dialog.open) dialog.showModal();
|
|
547
1343
|
else if (!open && dialog.open) dialog.close();
|
|
548
1344
|
}, [open]);
|
|
549
|
-
return /* @__PURE__ */ (0,
|
|
1345
|
+
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
|
|
550
1346
|
"dialog",
|
|
551
1347
|
{
|
|
552
1348
|
ref: dialogRef,
|
|
@@ -554,19 +1350,19 @@ function ReportShortcutsHelp({ open, onClose }) {
|
|
|
554
1350
|
"aria-label": "Keyboard shortcuts",
|
|
555
1351
|
onClose,
|
|
556
1352
|
children: [
|
|
557
|
-
/* @__PURE__ */ (0,
|
|
558
|
-
/* @__PURE__ */ (0,
|
|
559
|
-
/* @__PURE__ */ (0,
|
|
560
|
-
/* @__PURE__ */ (0,
|
|
1353
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("h2", { children: "Keyboard shortcuts" }),
|
|
1354
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { children: [
|
|
1355
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dt", { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("kbd", { children: s.keys }) }),
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("dd", { children: s.description })
|
|
561
1357
|
] }, s.keys)) }),
|
|
562
|
-
/* @__PURE__ */ (0,
|
|
1358
|
+
/* @__PURE__ */ (0, import_jsx_runtime33.jsx)("form", { method: "dialog", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
|
|
563
1359
|
]
|
|
564
1360
|
}
|
|
565
1361
|
);
|
|
566
1362
|
}
|
|
567
1363
|
|
|
568
1364
|
// src/interactive/use-keyboard-shortcuts.ts
|
|
569
|
-
var
|
|
1365
|
+
var import_react10 = require("react");
|
|
570
1366
|
function isEditableTarget(target) {
|
|
571
1367
|
if (!(target instanceof HTMLElement)) return false;
|
|
572
1368
|
const tag = target.tagName;
|
|
@@ -575,9 +1371,9 @@ function isEditableTarget(target) {
|
|
|
575
1371
|
return false;
|
|
576
1372
|
}
|
|
577
1373
|
function useKeyboardShortcuts(handlers) {
|
|
578
|
-
const ref = (0,
|
|
1374
|
+
const ref = (0, import_react10.useRef)(handlers);
|
|
579
1375
|
ref.current = handlers;
|
|
580
|
-
(0,
|
|
1376
|
+
(0, import_react10.useEffect)(() => {
|
|
581
1377
|
function onKeyDown(e) {
|
|
582
1378
|
const h = ref.current;
|
|
583
1379
|
if (e.key === "Escape") {
|
|
@@ -603,6 +1399,14 @@ function useKeyboardShortcuts(handlers) {
|
|
|
603
1399
|
h.onPrevFailure?.();
|
|
604
1400
|
e.preventDefault();
|
|
605
1401
|
return;
|
|
1402
|
+
case "e":
|
|
1403
|
+
h.onExpandAll?.();
|
|
1404
|
+
e.preventDefault();
|
|
1405
|
+
return;
|
|
1406
|
+
case "c":
|
|
1407
|
+
h.onCollapseAll?.();
|
|
1408
|
+
e.preventDefault();
|
|
1409
|
+
return;
|
|
606
1410
|
default:
|
|
607
1411
|
return;
|
|
608
1412
|
}
|
|
@@ -613,16 +1417,14 @@ function useKeyboardShortcuts(handlers) {
|
|
|
613
1417
|
}
|
|
614
1418
|
|
|
615
1419
|
// src/interactive/use-deep-link-scroll.ts
|
|
616
|
-
var
|
|
1420
|
+
var import_react11 = require("react");
|
|
617
1421
|
function useDeepLinkScroll() {
|
|
618
|
-
(0,
|
|
1422
|
+
(0, import_react11.useEffect)(() => {
|
|
619
1423
|
function scrollToHash() {
|
|
620
1424
|
const hash = window.location.hash.replace(/^#/, "");
|
|
621
1425
|
if (!hash) return;
|
|
622
|
-
const el = document.getElementById(hash);
|
|
623
|
-
if (!el) return;
|
|
624
1426
|
requestAnimationFrame(() => {
|
|
625
|
-
|
|
1427
|
+
scrollToScenarioId(hash, { updateHash: false });
|
|
626
1428
|
});
|
|
627
1429
|
}
|
|
628
1430
|
scrollToHash();
|
|
@@ -635,7 +1437,16 @@ function useDeepLinkScroll() {
|
|
|
635
1437
|
function normalizeQuery(query) {
|
|
636
1438
|
return query.trim().toLowerCase();
|
|
637
1439
|
}
|
|
638
|
-
function
|
|
1440
|
+
function allTags(report) {
|
|
1441
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1442
|
+
for (const feature of report.features) {
|
|
1443
|
+
for (const scenario of feature.scenarios) {
|
|
1444
|
+
for (const tag of scenario.tags) seen.add(tag);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
return [...seen];
|
|
1448
|
+
}
|
|
1449
|
+
function queryMatches(scenario, q) {
|
|
639
1450
|
if (q === "") return true;
|
|
640
1451
|
if (scenario.title.toLowerCase().includes(q)) return true;
|
|
641
1452
|
for (const tag of scenario.tags) {
|
|
@@ -646,6 +1457,11 @@ function scenarioMatches(scenario, q) {
|
|
|
646
1457
|
}
|
|
647
1458
|
return false;
|
|
648
1459
|
}
|
|
1460
|
+
function scenarioMatches(scenario, q, status, tags) {
|
|
1461
|
+
if (status !== "all" && scenario.status !== status) return false;
|
|
1462
|
+
if (tags.length > 0 && !tags.some((t) => scenario.tags.includes(t))) return false;
|
|
1463
|
+
return queryMatches(scenario, q);
|
|
1464
|
+
}
|
|
649
1465
|
function summarizeScenarios(scenarios) {
|
|
650
1466
|
let total = 0, passed = 0, failed = 0, skipped = 0, pending = 0, durationMs = 0;
|
|
651
1467
|
for (const s of scenarios) {
|
|
@@ -658,13 +1474,16 @@ function summarizeScenarios(scenarios) {
|
|
|
658
1474
|
}
|
|
659
1475
|
return { total, passed, failed, skipped, pending, durationMs };
|
|
660
1476
|
}
|
|
661
|
-
function filterReport(report,
|
|
662
|
-
const
|
|
663
|
-
|
|
1477
|
+
function filterReport(report, criteria) {
|
|
1478
|
+
const c = typeof criteria === "string" ? { query: criteria } : criteria;
|
|
1479
|
+
const q = normalizeQuery(c.query ?? "");
|
|
1480
|
+
const status = c.status ?? "all";
|
|
1481
|
+
const tags = c.tags ?? [];
|
|
1482
|
+
if (q === "" && status === "all" && tags.length === 0) return report;
|
|
664
1483
|
const features = [];
|
|
665
1484
|
let topTotal = 0, topPassed = 0, topFailed = 0, topSkipped = 0, topPending = 0, topDuration = 0;
|
|
666
1485
|
for (const feature of report.features) {
|
|
667
|
-
const matched = feature.scenarios.filter((s) => scenarioMatches(s, q));
|
|
1486
|
+
const matched = feature.scenarios.filter((s) => scenarioMatches(s, q, status, tags));
|
|
668
1487
|
if (matched.length === 0) continue;
|
|
669
1488
|
const summary = summarizeScenarios(matched);
|
|
670
1489
|
features.push({ ...feature, summary, scenarios: matched });
|
|
@@ -708,28 +1527,170 @@ function listFailures(report) {
|
|
|
708
1527
|
return out;
|
|
709
1528
|
}
|
|
710
1529
|
|
|
711
|
-
// src/interactive/
|
|
712
|
-
var
|
|
713
|
-
function
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1530
|
+
// src/interactive/ReportFilters.tsx
|
|
1531
|
+
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
1532
|
+
function ReportFilters({
|
|
1533
|
+
statuses,
|
|
1534
|
+
status,
|
|
1535
|
+
onStatus,
|
|
1536
|
+
tags,
|
|
1537
|
+
activeTags,
|
|
1538
|
+
onToggleTag
|
|
1539
|
+
}) {
|
|
1540
|
+
if (statuses.length === 0 && tags.length === 0) return null;
|
|
1541
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
1542
|
+
statuses.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { role: "group", "aria-label": "Filter by status", className: "flex flex-wrap gap-1", children: statuses.map((opt) => {
|
|
1543
|
+
const active = status === opt.key;
|
|
1544
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
1545
|
+
"button",
|
|
722
1546
|
{
|
|
723
|
-
|
|
724
|
-
"aria-
|
|
725
|
-
"
|
|
726
|
-
|
|
727
|
-
|
|
1547
|
+
type: "button",
|
|
1548
|
+
"aria-pressed": active,
|
|
1549
|
+
onClick: () => onStatus(active && opt.key !== "all" ? "all" : opt.key),
|
|
1550
|
+
className: cn(
|
|
1551
|
+
"cursor-pointer rounded-md border px-2 py-1 text-xs transition-colors",
|
|
1552
|
+
active ? "border-primary bg-primary text-primary-foreground" : "border-border text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
1553
|
+
),
|
|
1554
|
+
children: [
|
|
1555
|
+
opt.label,
|
|
1556
|
+
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "ml-1 font-mono opacity-70", children: opt.count })
|
|
1557
|
+
]
|
|
1558
|
+
},
|
|
1559
|
+
opt.key
|
|
728
1560
|
);
|
|
1561
|
+
}) }) : null,
|
|
1562
|
+
tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("ul", { "aria-label": "Filter by tag", className: "flex flex-wrap gap-1.5", children: tags.map((tag) => {
|
|
1563
|
+
const active = activeTags.includes(tag);
|
|
1564
|
+
return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
|
|
1565
|
+
"button",
|
|
1566
|
+
{
|
|
1567
|
+
type: "button",
|
|
1568
|
+
"aria-pressed": active,
|
|
1569
|
+
onClick: () => onToggleTag(tag),
|
|
1570
|
+
className: "cursor-pointer",
|
|
1571
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Badge, { variant: active ? "default" : "tag", children: tag })
|
|
1572
|
+
}
|
|
1573
|
+
) }, tag);
|
|
1574
|
+
}) }) : null
|
|
1575
|
+
] });
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// src/interactive/ReportToc.tsx
|
|
1579
|
+
var import_react12 = require("react");
|
|
1580
|
+
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
1581
|
+
var DOT = {
|
|
1582
|
+
passed: "text-pass",
|
|
1583
|
+
failed: "text-fail",
|
|
1584
|
+
skipped: "text-skip",
|
|
1585
|
+
pending: "text-pend"
|
|
1586
|
+
};
|
|
1587
|
+
function ReportToc() {
|
|
1588
|
+
const report = useReport();
|
|
1589
|
+
const [activeId, setActiveId] = (0, import_react12.useState)(null);
|
|
1590
|
+
const scenarioIds = (0, import_react12.useMemo)(
|
|
1591
|
+
() => report.features.flatMap((f) => f.scenarios.map((s) => s.id)),
|
|
1592
|
+
[report]
|
|
1593
|
+
);
|
|
1594
|
+
(0, import_react12.useEffect)(() => {
|
|
1595
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
1596
|
+
const els = scenarioIds.map((id) => document.getElementById(id)).filter((el) => el !== null);
|
|
1597
|
+
if (els.length === 0) return;
|
|
1598
|
+
const observer = new IntersectionObserver(
|
|
1599
|
+
(entries) => {
|
|
1600
|
+
const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
|
|
1601
|
+
if (visible[0]) setActiveId(visible[0].target.id);
|
|
1602
|
+
},
|
|
1603
|
+
{ rootMargin: "-8% 0px -80% 0px" }
|
|
1604
|
+
);
|
|
1605
|
+
els.forEach((el) => observer.observe(el));
|
|
1606
|
+
return () => observer.disconnect();
|
|
1607
|
+
}, [scenarioIds]);
|
|
1608
|
+
if (report.features.length === 0) return null;
|
|
1609
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
1610
|
+
"nav",
|
|
1611
|
+
{
|
|
1612
|
+
"aria-label": "Table of contents",
|
|
1613
|
+
className: "sticky top-4 hidden h-fit w-56 shrink-0 self-start text-xs lg:block",
|
|
1614
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "flex flex-col gap-3", children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("li", { children: [
|
|
1615
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("p", { className: "mb-1 truncate font-semibold text-foreground", children: feature.title }),
|
|
1616
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("ul", { className: "flex flex-col", children: feature.scenarios.map((scenario) => {
|
|
1617
|
+
const active = activeId === scenario.id;
|
|
1618
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
1619
|
+
"a",
|
|
1620
|
+
{
|
|
1621
|
+
href: `#${scenario.id}`,
|
|
1622
|
+
onClick: (e) => {
|
|
1623
|
+
e.preventDefault();
|
|
1624
|
+
scrollToScenarioId(scenario.id, { updateHash: true });
|
|
1625
|
+
},
|
|
1626
|
+
"aria-current": active ? "true" : void 0,
|
|
1627
|
+
className: cn(
|
|
1628
|
+
"flex items-center gap-1.5 truncate border-l-2 py-0.5 pl-2 hover:text-foreground",
|
|
1629
|
+
active ? "border-primary font-medium text-foreground" : "border-border text-muted-foreground"
|
|
1630
|
+
),
|
|
1631
|
+
children: [
|
|
1632
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { "aria-hidden": true, className: cn("shrink-0", DOT[scenario.status]), children: "\u2022" }),
|
|
1633
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "truncate", children: scenario.title })
|
|
1634
|
+
]
|
|
1635
|
+
}
|
|
1636
|
+
) }, scenario.id);
|
|
1637
|
+
}) })
|
|
1638
|
+
] }, feature.id)) })
|
|
729
1639
|
}
|
|
730
|
-
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
// src/interactive/use-theme.ts
|
|
1644
|
+
var import_react13 = require("react");
|
|
1645
|
+
var KEY = "es-theme";
|
|
1646
|
+
function applyTheme(theme) {
|
|
1647
|
+
if (typeof document !== "undefined") {
|
|
1648
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
731
1649
|
}
|
|
732
|
-
|
|
1650
|
+
}
|
|
1651
|
+
function initialTheme() {
|
|
1652
|
+
if (typeof localStorage !== "undefined") {
|
|
1653
|
+
const stored = localStorage.getItem(KEY);
|
|
1654
|
+
if (stored === "light" || stored === "dark") return stored;
|
|
1655
|
+
}
|
|
1656
|
+
if (typeof document !== "undefined" && document.documentElement.getAttribute("data-theme") === "dark") {
|
|
1657
|
+
return "dark";
|
|
1658
|
+
}
|
|
1659
|
+
if (typeof matchMedia !== "undefined" && matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
1660
|
+
return "dark";
|
|
1661
|
+
}
|
|
1662
|
+
return "light";
|
|
1663
|
+
}
|
|
1664
|
+
function useTheme() {
|
|
1665
|
+
const [theme, setTheme] = (0, import_react13.useState)("light");
|
|
1666
|
+
(0, import_react13.useEffect)(() => {
|
|
1667
|
+
const t = initialTheme();
|
|
1668
|
+
setTheme(t);
|
|
1669
|
+
applyTheme(t);
|
|
1670
|
+
}, []);
|
|
1671
|
+
const toggle = (0, import_react13.useCallback)(() => {
|
|
1672
|
+
setTheme((prev) => {
|
|
1673
|
+
const next = prev === "dark" ? "light" : "dark";
|
|
1674
|
+
applyTheme(next);
|
|
1675
|
+
try {
|
|
1676
|
+
localStorage.setItem(KEY, next);
|
|
1677
|
+
} catch {
|
|
1678
|
+
}
|
|
1679
|
+
return next;
|
|
1680
|
+
});
|
|
1681
|
+
}, []);
|
|
1682
|
+
return { theme, toggle };
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// src/interactive/ReportInteractive.tsx
|
|
1686
|
+
var import_jsx_runtime36 = require("react/jsx-runtime");
|
|
1687
|
+
function ReportInteractive(props) {
|
|
1688
|
+
const { report, className, title, dataTheme } = props;
|
|
1689
|
+
const result = unwrapReport(report);
|
|
1690
|
+
if (!result.ok) {
|
|
1691
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportErrorShell, { error: result.error, className, title, dataTheme });
|
|
1692
|
+
}
|
|
1693
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportInteractiveView, { ...props, report: result.data });
|
|
733
1694
|
}
|
|
734
1695
|
function ReportInteractiveView({
|
|
735
1696
|
report,
|
|
@@ -737,39 +1698,85 @@ function ReportInteractiveView({
|
|
|
737
1698
|
renderers,
|
|
738
1699
|
className,
|
|
739
1700
|
title,
|
|
740
|
-
dataTheme
|
|
1701
|
+
dataTheme,
|
|
1702
|
+
hideHeader = false
|
|
741
1703
|
}) {
|
|
742
|
-
const [query, setQuery] = (0,
|
|
743
|
-
const [
|
|
744
|
-
const
|
|
745
|
-
const
|
|
746
|
-
const
|
|
747
|
-
const
|
|
748
|
-
const
|
|
749
|
-
|
|
1704
|
+
const [query, setQuery] = (0, import_react14.useState)("");
|
|
1705
|
+
const [statusFilter, setStatusFilter] = (0, import_react14.useState)("all");
|
|
1706
|
+
const [activeTags, setActiveTags] = (0, import_react14.useState)([]);
|
|
1707
|
+
const [detail, setDetail] = (0, import_react14.useState)("full");
|
|
1708
|
+
const [helpOpen, setHelpOpen] = (0, import_react14.useState)(false);
|
|
1709
|
+
const { theme, toggle: toggleTheme } = useTheme();
|
|
1710
|
+
const searchRef = (0, import_react14.useRef)(null);
|
|
1711
|
+
const deferredQuery = (0, import_react14.useDeferredValue)(query);
|
|
1712
|
+
const failures = (0, import_react14.useMemo)(() => listFailures(report), [report]);
|
|
1713
|
+
const filtered = (0, import_react14.useMemo)(
|
|
1714
|
+
() => filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags }),
|
|
1715
|
+
[report, deferredQuery, statusFilter, activeTags]
|
|
1716
|
+
);
|
|
1717
|
+
const isFiltering = query !== deferredQuery;
|
|
1718
|
+
const statusOptions = (0, import_react14.useMemo)(() => {
|
|
1719
|
+
const s = report.summary;
|
|
1720
|
+
const opts = [
|
|
1721
|
+
{ key: "all", label: "All", count: s.total }
|
|
1722
|
+
];
|
|
1723
|
+
if (s.passed) opts.push({ key: "passed", label: "Passed", count: s.passed });
|
|
1724
|
+
if (s.failed) opts.push({ key: "failed", label: "Failed", count: s.failed });
|
|
1725
|
+
if (s.skipped) opts.push({ key: "skipped", label: "Skipped", count: s.skipped });
|
|
1726
|
+
if (s.pending) opts.push({ key: "pending", label: "Pending", count: s.pending });
|
|
1727
|
+
return opts;
|
|
1728
|
+
}, [report]);
|
|
1729
|
+
const tagOptions = (0, import_react14.useMemo)(() => allTags(report), [report]);
|
|
1730
|
+
const toggleTag = (0, import_react14.useCallback)(
|
|
1731
|
+
(tag) => setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]),
|
|
1732
|
+
[]
|
|
1733
|
+
);
|
|
1734
|
+
const [toast, setToast] = (0, import_react14.useState)(null);
|
|
1735
|
+
const toastTimer = (0, import_react14.useRef)(null);
|
|
1736
|
+
const showToast = (0, import_react14.useCallback)((msg) => {
|
|
1737
|
+
setToast(msg);
|
|
1738
|
+
if (toastTimer.current) clearTimeout(toastTimer.current);
|
|
1739
|
+
toastTimer.current = setTimeout(() => setToast(null), 1800);
|
|
750
1740
|
}, []);
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1741
|
+
const copy = (0, import_react14.useCallback)(
|
|
1742
|
+
(text, msg) => {
|
|
1743
|
+
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
|
1744
|
+
navigator.clipboard.writeText(text).then(() => showToast(msg), () => showToast("Copy failed"));
|
|
1745
|
+
}
|
|
1746
|
+
},
|
|
1747
|
+
[showToast]
|
|
1748
|
+
);
|
|
1749
|
+
const scenarioActions = (0, import_react14.useMemo)(
|
|
1750
|
+
() => ({
|
|
1751
|
+
copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
|
|
1752
|
+
copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
|
|
1753
|
+
copyPrompt: (s) => copy(scenarioToPrompt(s), "Prompt copied")
|
|
1754
|
+
}),
|
|
1755
|
+
[copy]
|
|
1756
|
+
);
|
|
1757
|
+
const failureIndexRef = (0, import_react14.useRef)(0);
|
|
1758
|
+
const collapse = useCollapseState();
|
|
1759
|
+
const allCollapsibleIds = (0, import_react14.useMemo)(
|
|
1760
|
+
() => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
|
|
1761
|
+
[report]
|
|
1762
|
+
);
|
|
1763
|
+
const collapseAll = (0, import_react14.useCallback)(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
|
|
1764
|
+
const focusSearch = (0, import_react14.useCallback)(() => {
|
|
1765
|
+
searchRef.current?.focus();
|
|
759
1766
|
}, []);
|
|
760
|
-
const stepFailure = (0,
|
|
1767
|
+
const stepFailure = (0, import_react14.useCallback)(
|
|
761
1768
|
(direction) => {
|
|
762
1769
|
if (failures.length === 0) return;
|
|
763
1770
|
failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
|
|
764
1771
|
const target = failures[failureIndexRef.current];
|
|
765
|
-
if (target)
|
|
1772
|
+
if (target) scrollToScenarioId(target.scenarioId);
|
|
766
1773
|
},
|
|
767
|
-
[failures
|
|
1774
|
+
[failures]
|
|
768
1775
|
);
|
|
769
|
-
const toggleHelp = (0,
|
|
1776
|
+
const toggleHelp = (0, import_react14.useCallback)(() => {
|
|
770
1777
|
setHelpOpen((v) => !v);
|
|
771
1778
|
}, []);
|
|
772
|
-
const escape = (0,
|
|
1779
|
+
const escape = (0, import_react14.useCallback)(() => {
|
|
773
1780
|
if (helpOpen) setHelpOpen(false);
|
|
774
1781
|
else if (query !== "") setQuery("");
|
|
775
1782
|
}, [helpOpen, query]);
|
|
@@ -778,42 +1785,103 @@ function ReportInteractiveView({
|
|
|
778
1785
|
onNextFailure: () => stepFailure(1),
|
|
779
1786
|
onPrevFailure: () => stepFailure(-1),
|
|
780
1787
|
onToggleHelp: toggleHelp,
|
|
781
|
-
onEscape: escape
|
|
1788
|
+
onEscape: escape,
|
|
1789
|
+
onExpandAll: collapse.expandAll,
|
|
1790
|
+
onCollapseAll: collapseAll
|
|
782
1791
|
});
|
|
783
1792
|
useDeepLinkScroll();
|
|
784
1793
|
const hasContent = filtered.features.length > 0;
|
|
785
1794
|
const totalScenarios = report.summary.total;
|
|
786
1795
|
const matchedScenarios = filtered.summary.total;
|
|
787
|
-
return /* @__PURE__ */ (0,
|
|
1796
|
+
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(CollapseProvider, { value: collapse.api, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ScenarioActionsProvider, { value: scenarioActions, children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
788
1797
|
ReportRoot,
|
|
789
1798
|
{
|
|
790
1799
|
report: filtered,
|
|
791
1800
|
customRenderers,
|
|
792
1801
|
renderers,
|
|
793
|
-
children: /* @__PURE__ */ (0,
|
|
1802
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
|
|
794
1803
|
"main",
|
|
795
1804
|
{
|
|
796
|
-
className:
|
|
1805
|
+
className: cn("es-report", "es-report-interactive", className),
|
|
797
1806
|
"aria-label": title ?? "Test report",
|
|
1807
|
+
"aria-busy": isFiltering,
|
|
798
1808
|
"data-theme": dataTheme,
|
|
1809
|
+
"data-detail-level": detail,
|
|
799
1810
|
children: [
|
|
800
|
-
/* @__PURE__ */ (0,
|
|
801
|
-
/* @__PURE__ */ (0,
|
|
802
|
-
/* @__PURE__ */ (0,
|
|
803
|
-
|
|
804
|
-
|
|
1811
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("header", { className: "es-report-header", children: [
|
|
1812
|
+
hideHeader ? null : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportTitleBlock, { title }),
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1814
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1815
|
+
ReportSearch,
|
|
1816
|
+
{
|
|
1817
|
+
ref: searchRef,
|
|
1818
|
+
value: query,
|
|
1819
|
+
onChange: setQuery,
|
|
1820
|
+
matchedCount: matchedScenarios,
|
|
1821
|
+
totalCount: totalScenarios
|
|
1822
|
+
}
|
|
1823
|
+
),
|
|
1824
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex shrink-0 gap-1", children: [
|
|
1825
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1826
|
+
"button",
|
|
1827
|
+
{
|
|
1828
|
+
type: "button",
|
|
1829
|
+
onClick: collapse.expandAll,
|
|
1830
|
+
"aria-keyshortcuts": "e",
|
|
1831
|
+
className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1832
|
+
children: "Expand all"
|
|
1833
|
+
}
|
|
1834
|
+
),
|
|
1835
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1836
|
+
"button",
|
|
1837
|
+
{
|
|
1838
|
+
type: "button",
|
|
1839
|
+
onClick: collapseAll,
|
|
1840
|
+
"aria-keyshortcuts": "c",
|
|
1841
|
+
className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1842
|
+
children: "Collapse all"
|
|
1843
|
+
}
|
|
1844
|
+
),
|
|
1845
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1846
|
+
"button",
|
|
1847
|
+
{
|
|
1848
|
+
type: "button",
|
|
1849
|
+
onClick: () => setDetail((d) => d === "full" ? "minimal" : "full"),
|
|
1850
|
+
"aria-pressed": detail === "minimal",
|
|
1851
|
+
className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1852
|
+
children: detail === "full" ? "Hide docs" : "Show docs"
|
|
1853
|
+
}
|
|
1854
|
+
),
|
|
1855
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1856
|
+
"button",
|
|
1857
|
+
{
|
|
1858
|
+
type: "button",
|
|
1859
|
+
onClick: toggleTheme,
|
|
1860
|
+
"aria-label": `Switch to ${theme === "dark" ? "light" : "dark"} theme`,
|
|
1861
|
+
className: "cursor-pointer rounded-md border border-border px-2 py-1 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1862
|
+
children: theme === "dark" ? "\u2600 Light" : "\u263E Dark"
|
|
1863
|
+
}
|
|
1864
|
+
)
|
|
1865
|
+
] })
|
|
1866
|
+
] }),
|
|
1867
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1868
|
+
ReportFilters,
|
|
805
1869
|
{
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1870
|
+
statuses: statusOptions,
|
|
1871
|
+
status: statusFilter,
|
|
1872
|
+
onStatus: setStatusFilter,
|
|
1873
|
+
tags: tagOptions,
|
|
1874
|
+
activeTags,
|
|
1875
|
+
onToggleTag: toggleTag
|
|
811
1876
|
}
|
|
812
1877
|
)
|
|
813
1878
|
] }),
|
|
814
|
-
/* @__PURE__ */ (0,
|
|
815
|
-
hasContent ? /* @__PURE__ */ (0,
|
|
816
|
-
|
|
1879
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFailureBanner, { failures }),
|
|
1880
|
+
hasContent ? /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex gap-6", children: [
|
|
1881
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportToc, {}),
|
|
1882
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportFeatureList, {}) })
|
|
1883
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
|
|
1884
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
817
1885
|
"button",
|
|
818
1886
|
{
|
|
819
1887
|
type: "button",
|
|
@@ -824,19 +1892,171 @@ function ReportInteractiveView({
|
|
|
824
1892
|
children: "?"
|
|
825
1893
|
}
|
|
826
1894
|
),
|
|
827
|
-
/* @__PURE__ */ (0,
|
|
1895
|
+
/* @__PURE__ */ (0, import_jsx_runtime36.jsx)(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) }),
|
|
1896
|
+
toast ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
1897
|
+
"div",
|
|
1898
|
+
{
|
|
1899
|
+
role: "status",
|
|
1900
|
+
"aria-live": "polite",
|
|
1901
|
+
className: "fixed bottom-4 left-1/2 -translate-x-1/2 rounded-md bg-foreground px-3 py-1.5 text-xs font-medium text-background shadow-lg",
|
|
1902
|
+
children: toast
|
|
1903
|
+
}
|
|
1904
|
+
) : null
|
|
828
1905
|
]
|
|
829
1906
|
}
|
|
830
1907
|
)
|
|
831
1908
|
}
|
|
1909
|
+
) }) });
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// src/interactive/ReportInteractiveIsland.tsx
|
|
1913
|
+
var import_react17 = require("react");
|
|
1914
|
+
|
|
1915
|
+
// src/interactive/island-renderers.tsx
|
|
1916
|
+
var import_react16 = require("react");
|
|
1917
|
+
|
|
1918
|
+
// src/components/doc/MermaidView.tsx
|
|
1919
|
+
var import_react15 = require("react");
|
|
1920
|
+
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
1921
|
+
function prefersDark(el) {
|
|
1922
|
+
const themed = el?.closest("[data-theme]");
|
|
1923
|
+
if (themed?.dataset.theme === "dark") return true;
|
|
1924
|
+
if (themed?.dataset.theme === "light") return false;
|
|
1925
|
+
return typeof window !== "undefined" && !!window.matchMedia?.("(prefers-color-scheme: dark)").matches;
|
|
1926
|
+
}
|
|
1927
|
+
function MermaidView({ entry, load }) {
|
|
1928
|
+
const ref = (0, import_react15.useRef)(null);
|
|
1929
|
+
const [svg, setSvg] = (0, import_react15.useState)(null);
|
|
1930
|
+
(0, import_react15.useEffect)(() => {
|
|
1931
|
+
let cancelled = false;
|
|
1932
|
+
const draw = async () => {
|
|
1933
|
+
try {
|
|
1934
|
+
const mermaid = await load();
|
|
1935
|
+
mermaid.initialize({
|
|
1936
|
+
startOnLoad: false,
|
|
1937
|
+
securityLevel: "strict",
|
|
1938
|
+
theme: prefersDark(ref.current) ? "dark" : "default"
|
|
1939
|
+
});
|
|
1940
|
+
const id = `es-mermaid-${Math.random().toString(36).slice(2)}`;
|
|
1941
|
+
const { svg: rendered } = await mermaid.render(id, entry.code);
|
|
1942
|
+
if (!cancelled) setSvg(rendered);
|
|
1943
|
+
} catch {
|
|
1944
|
+
if (!cancelled) setSvg(null);
|
|
1945
|
+
}
|
|
1946
|
+
};
|
|
1947
|
+
void draw();
|
|
1948
|
+
const themed = ref.current?.closest("[data-theme]");
|
|
1949
|
+
const observer = themed ? new MutationObserver(() => void draw()) : void 0;
|
|
1950
|
+
observer?.observe(themed, { attributes: true, attributeFilter: ["data-theme"] });
|
|
1951
|
+
return () => {
|
|
1952
|
+
cancelled = true;
|
|
1953
|
+
observer?.disconnect();
|
|
1954
|
+
};
|
|
1955
|
+
}, [entry.code, load]);
|
|
1956
|
+
if (svg) {
|
|
1957
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsxs)("figure", { ref, className: "my-3", children: [
|
|
1958
|
+
entry.title ? /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.title }) : null,
|
|
1959
|
+
/* @__PURE__ */ (0, import_jsx_runtime37.jsx)(
|
|
1960
|
+
"div",
|
|
1961
|
+
{
|
|
1962
|
+
role: "img",
|
|
1963
|
+
"aria-label": entry.title ?? "Mermaid diagram",
|
|
1964
|
+
className: "overflow-x-auto rounded-md border border-border bg-card p-3 [&_svg]:mx-auto [&_svg]:h-auto [&_svg]:max-w-full",
|
|
1965
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
1966
|
+
}
|
|
1967
|
+
)
|
|
1968
|
+
] });
|
|
1969
|
+
}
|
|
1970
|
+
return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)(MermaidSource, { entry, ref });
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
// src/interactive/island-renderers.tsx
|
|
1974
|
+
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
1975
|
+
var HLJS_ESM = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/es/highlight.min.js";
|
|
1976
|
+
var MERMAID_ESM = "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
|
|
1977
|
+
function cachedCdnModule(url) {
|
|
1978
|
+
let loader;
|
|
1979
|
+
return () => {
|
|
1980
|
+
loader ??= import(
|
|
1981
|
+
/* @vite-ignore */
|
|
1982
|
+
/* webpackIgnore: true */
|
|
1983
|
+
url
|
|
1984
|
+
).then((m) => m.default ?? m);
|
|
1985
|
+
return loader;
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
var loadMermaidFromCdn = cachedCdnModule(MERMAID_ESM);
|
|
1989
|
+
var loadHljs = cachedCdnModule(HLJS_ESM);
|
|
1990
|
+
function CdnHighlightedCode({ entry }) {
|
|
1991
|
+
const [html, setHtml] = (0, import_react16.useState)(null);
|
|
1992
|
+
(0, import_react16.useEffect)(() => {
|
|
1993
|
+
let cancelled = false;
|
|
1994
|
+
void (async () => {
|
|
1995
|
+
try {
|
|
1996
|
+
const hljs = await loadHljs();
|
|
1997
|
+
const lang = entry.lang && hljs.getLanguage(entry.lang) ? entry.lang : void 0;
|
|
1998
|
+
const result = lang ? hljs.highlight(entry.content, { language: lang, ignoreIllegals: true }) : hljs.highlightAuto(entry.content);
|
|
1999
|
+
if (!cancelled) setHtml(result.value);
|
|
2000
|
+
} catch {
|
|
2001
|
+
if (!cancelled) setHtml(null);
|
|
2002
|
+
}
|
|
2003
|
+
})();
|
|
2004
|
+
return () => {
|
|
2005
|
+
cancelled = true;
|
|
2006
|
+
};
|
|
2007
|
+
}, [entry.content, entry.lang]);
|
|
2008
|
+
return /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(CodeFigure, { label: entry.label, children: html !== null ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)(
|
|
2009
|
+
"code",
|
|
2010
|
+
{
|
|
2011
|
+
className: cn("hljs font-mono text-xs", entry.lang && `language-${entry.lang}`),
|
|
2012
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
2013
|
+
}
|
|
2014
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("code", { className: cn("font-mono text-xs", entry.lang && `language-${entry.lang}`), children: entry.content }) });
|
|
2015
|
+
}
|
|
2016
|
+
function buildIslandRenderers(opts) {
|
|
2017
|
+
const renderers = {};
|
|
2018
|
+
if (opts.mermaid) {
|
|
2019
|
+
renderers.mermaid = (entry) => (0, import_react16.createElement)(MermaidView, { entry, load: loadMermaidFromCdn });
|
|
2020
|
+
}
|
|
2021
|
+
if (opts.syntaxHighlighting) {
|
|
2022
|
+
renderers.code = (entry) => (0, import_react16.createElement)(CdnHighlightedCode, { entry });
|
|
2023
|
+
}
|
|
2024
|
+
return renderers;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// src/interactive/ReportInteractiveIsland.tsx
|
|
2028
|
+
var import_jsx_runtime39 = require("react/jsx-runtime");
|
|
2029
|
+
function ReportInteractiveIsland({
|
|
2030
|
+
report,
|
|
2031
|
+
title,
|
|
2032
|
+
className,
|
|
2033
|
+
hideHeader,
|
|
2034
|
+
syntaxHighlighting = true,
|
|
2035
|
+
mermaid = true
|
|
2036
|
+
}) {
|
|
2037
|
+
const renderers = (0, import_react17.useMemo)(
|
|
2038
|
+
() => buildIslandRenderers({ syntaxHighlighting, mermaid }),
|
|
2039
|
+
[syntaxHighlighting, mermaid]
|
|
2040
|
+
);
|
|
2041
|
+
return /* @__PURE__ */ (0, import_jsx_runtime39.jsx)(
|
|
2042
|
+
ReportInteractive,
|
|
2043
|
+
{
|
|
2044
|
+
report,
|
|
2045
|
+
title,
|
|
2046
|
+
className,
|
|
2047
|
+
hideHeader,
|
|
2048
|
+
renderers
|
|
2049
|
+
}
|
|
832
2050
|
);
|
|
833
2051
|
}
|
|
834
2052
|
// Annotate the CommonJS export names for ESM import in node:
|
|
835
2053
|
0 && (module.exports = {
|
|
836
2054
|
ReportFailureBanner,
|
|
837
2055
|
ReportInteractive,
|
|
2056
|
+
ReportInteractiveIsland,
|
|
838
2057
|
ReportSearch,
|
|
839
2058
|
ReportShortcutsHelp,
|
|
2059
|
+
buildIslandRenderers,
|
|
840
2060
|
filterReport,
|
|
841
2061
|
listFailures,
|
|
842
2062
|
normalizeQuery,
|