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