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