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