executable-stories-react 0.1.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.
@@ -0,0 +1,846 @@
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/interactive/index.ts
22
+ var interactive_exports = {};
23
+ __export(interactive_exports, {
24
+ ReportFailureBanner: () => ReportFailureBanner,
25
+ ReportInteractive: () => ReportInteractive,
26
+ ReportSearch: () => ReportSearch,
27
+ ReportShortcutsHelp: () => ReportShortcutsHelp,
28
+ filterReport: () => filterReport,
29
+ listFailures: () => listFailures,
30
+ normalizeQuery: () => normalizeQuery,
31
+ useDeepLinkScroll: () => useDeepLinkScroll,
32
+ useKeyboardShortcuts: () => useKeyboardShortcuts
33
+ });
34
+ module.exports = __toCommonJS(interactive_exports);
35
+
36
+ // src/interactive/ReportInteractive.tsx
37
+ var import_react10 = require("react");
38
+
39
+ // src/context/ReportRoot.tsx
40
+ var import_react2 = require("react");
41
+
42
+ // src/context/ReportContext.ts
43
+ var import_react = require("react");
44
+ var ReportContext = (0, import_react.createContext)(null);
45
+
46
+ // src/context/ReportRoot.tsx
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ var EMPTY_CUSTOM = {};
49
+ var EMPTY_RENDERERS = {};
50
+ function ReportRoot({
51
+ report,
52
+ customRenderers,
53
+ renderers,
54
+ children
55
+ }) {
56
+ const value = (0, import_react2.useMemo)(
57
+ () => ({
58
+ report,
59
+ customRenderers: customRenderers ?? EMPTY_CUSTOM,
60
+ renderers: renderers ?? EMPTY_RENDERERS
61
+ }),
62
+ [report, customRenderers, renderers]
63
+ );
64
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReportContext.Provider, { value, children });
65
+ }
66
+
67
+ // src/hooks/useReport.ts
68
+ var import_react3 = require("react");
69
+ function useReport() {
70
+ const ctx = (0, import_react3.useContext)(ReportContext);
71
+ if (!ctx) {
72
+ throw new Error(
73
+ "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
74
+ );
75
+ }
76
+ return ctx.report;
77
+ }
78
+
79
+ // src/components/ReportSummary.tsx
80
+ var import_jsx_runtime2 = require("react/jsx-runtime");
81
+ function ReportSummary({ className }) {
82
+ const report = useReport();
83
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
84
+ ReportSummaryView,
85
+ {
86
+ summary: report.summary,
87
+ ...className !== void 0 && { className },
88
+ ariaLabel: "Run summary"
89
+ }
90
+ );
91
+ }
92
+ function ReportSummaryView({ summary, className, ariaLabel }) {
93
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
94
+ "p",
95
+ {
96
+ className: ["es-report-summary", className].filter(Boolean).join(" "),
97
+ "aria-label": ariaLabel,
98
+ children: [
99
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
100
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { children: summary.total }),
101
+ " scenario",
102
+ summary.total === 1 ? "" : "s"
103
+ ] }),
104
+ " \xB7 ",
105
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "passed", children: [
106
+ summary.passed,
107
+ " passed"
108
+ ] }),
109
+ " \xB7 ",
110
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "failed", children: [
111
+ summary.failed,
112
+ " failed"
113
+ ] }),
114
+ summary.skipped > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
115
+ " \xB7 ",
116
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "skipped", children: [
117
+ summary.skipped,
118
+ " skipped"
119
+ ] })
120
+ ] }) : null,
121
+ summary.pending > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
122
+ " \xB7 ",
123
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-status": "pending", children: [
124
+ summary.pending,
125
+ " pending"
126
+ ] })
127
+ ] }) : null
128
+ ]
129
+ }
130
+ );
131
+ }
132
+
133
+ // src/components/doc/DocNote.tsx
134
+ var import_jsx_runtime3 = require("react/jsx-runtime");
135
+ function DocNote({ entry }) {
136
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "es-doc es-doc-note", children: entry.text });
137
+ }
138
+
139
+ // src/components/doc/DocTag.tsx
140
+ var import_jsx_runtime4 = require("react/jsx-runtime");
141
+ function DocTag({ entry }) {
142
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "es-doc es-tags", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: n }, n)) });
143
+ }
144
+
145
+ // src/components/doc/DocKv.tsx
146
+ var import_jsx_runtime5 = require("react/jsx-runtime");
147
+ function formatValue(value) {
148
+ if (value === null) return "null";
149
+ if (typeof value === "string") return value;
150
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
151
+ try {
152
+ return JSON.stringify(value);
153
+ } catch {
154
+ return String(value);
155
+ }
156
+ }
157
+ function DocKv({ entry }) {
158
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("dl", { className: "es-doc es-doc-kv", children: [
159
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dt", { children: entry.label }),
160
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("dd", { children: formatValue(entry.value) })
161
+ ] });
162
+ }
163
+
164
+ // src/hooks/useRenderers.ts
165
+ var import_react4 = require("react");
166
+ var EMPTY_CUSTOM2 = {};
167
+ var EMPTY_RENDERERS2 = {};
168
+ function useCustomRenderers() {
169
+ const ctx = (0, import_react4.useContext)(ReportContext);
170
+ return ctx?.customRenderers ?? EMPTY_CUSTOM2;
171
+ }
172
+ function useBuiltinRenderers() {
173
+ const ctx = (0, import_react4.useContext)(ReportContext);
174
+ return ctx?.renderers ?? EMPTY_RENDERERS2;
175
+ }
176
+
177
+ // src/components/doc/DocCode.tsx
178
+ var import_jsx_runtime6 = require("react/jsx-runtime");
179
+ function DocCode({ entry }) {
180
+ const renderers = useBuiltinRenderers();
181
+ if (renderers.code) {
182
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: renderers.code(entry) });
183
+ }
184
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: "es-doc es-doc-code", children: [
185
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("figcaption", { children: entry.label }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { className: entry.lang ? `language-${entry.lang}` : void 0, children: entry.content }) })
187
+ ] });
188
+ }
189
+
190
+ // src/components/doc/DocTable.tsx
191
+ var import_jsx_runtime7 = require("react/jsx-runtime");
192
+ function DocTable({ entry }) {
193
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("figure", { className: "es-doc es-doc-table", children: [
194
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("figcaption", { children: entry.label }),
195
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { children: [
196
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: entry.columns.map((c) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { scope: "col", children: c }, c)) }) }),
197
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: row.map((cell, j) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { children: cell }, j)) }, i)) })
198
+ ] })
199
+ ] });
200
+ }
201
+
202
+ // src/components/doc/DocLink.tsx
203
+ var import_jsx_runtime8 = require("react/jsx-runtime");
204
+ function DocLink({ entry }) {
205
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
206
+ "a",
207
+ {
208
+ className: "es-doc es-doc-link",
209
+ href: entry.url,
210
+ rel: "noreferrer noopener",
211
+ target: "_blank",
212
+ children: entry.label
213
+ }
214
+ );
215
+ }
216
+
217
+ // src/components/doc/DocSection.tsx
218
+ var import_marked = require("marked");
219
+ var import_jsx_runtime9 = require("react/jsx-runtime");
220
+ function safeMarkdownHtml(markdown) {
221
+ const raw = import_marked.marked.parse(markdown, { async: false });
222
+ return raw.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "").replace(/\son[a-z]+\s*=\s*"[^"]*"/gi, "").replace(/\son[a-z]+\s*=\s*'[^']*'/gi, "").replace(/\son[a-z]+\s*=\s*[^\s>]+/gi, "").replace(/(href|src)\s*=\s*"\s*javascript:[^"]*"/gi, '$1="#"').replace(/(href|src)\s*=\s*'\s*javascript:[^']*'/gi, "$1='#'");
223
+ }
224
+ function DocSection({ entry }) {
225
+ const renderers = useBuiltinRenderers();
226
+ if (renderers.section) {
227
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_jsx_runtime9.Fragment, { children: renderers.section(entry) });
228
+ }
229
+ const html = safeMarkdownHtml(entry.markdown);
230
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "es-doc es-doc-section", "aria-label": entry.title, children: [
231
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { className: "es-doc-section-title", children: entry.title }) : null,
232
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
233
+ "div",
234
+ {
235
+ className: "es-doc-section-content",
236
+ dangerouslySetInnerHTML: { __html: html }
237
+ }
238
+ )
239
+ ] });
240
+ }
241
+
242
+ // src/components/doc/DocMermaid.tsx
243
+ var import_jsx_runtime10 = require("react/jsx-runtime");
244
+ function DocMermaid({ entry }) {
245
+ const renderers = useBuiltinRenderers();
246
+ if (renderers.mermaid) {
247
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, { children: renderers.mermaid(entry) });
248
+ }
249
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
250
+ "figure",
251
+ {
252
+ className: "es-doc es-doc-mermaid",
253
+ "aria-label": entry.title ?? "Diagram",
254
+ children: [
255
+ entry.title ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("figcaption", { children: entry.title }) : null,
256
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("pre", { "data-mermaid": true, children: entry.code })
257
+ ]
258
+ }
259
+ );
260
+ }
261
+
262
+ // src/components/doc/DocScreenshot.tsx
263
+ var import_jsx_runtime11 = require("react/jsx-runtime");
264
+ function DocScreenshot({ entry }) {
265
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("figure", { className: "es-doc es-doc-screenshot", children: [
266
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: entry.path, alt: entry.alt ?? "", loading: "lazy" }),
267
+ entry.alt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("figcaption", { children: entry.alt }) : null
268
+ ] });
269
+ }
270
+
271
+ // src/components/doc/DocCustom.tsx
272
+ var import_jsx_runtime12 = require("react/jsx-runtime");
273
+ function DocCustom({ entry }) {
274
+ const renderers = useCustomRenderers();
275
+ const renderer = renderers[entry.type];
276
+ if (renderer) {
277
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_jsx_runtime12.Fragment, { children: renderer(entry) });
278
+ }
279
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "es-doc es-doc-custom", "data-type": entry.type, children: [
280
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "es-doc-custom-type", children: entry.type }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("pre", { children: safeStringify(entry.data) })
282
+ ] });
283
+ }
284
+ function safeStringify(value) {
285
+ try {
286
+ return JSON.stringify(value, null, 2);
287
+ } catch {
288
+ return String(value);
289
+ }
290
+ }
291
+
292
+ // src/components/doc/DocEntry.tsx
293
+ var import_jsx_runtime13 = require("react/jsx-runtime");
294
+ function DocEntry({ entry }) {
295
+ switch (entry.kind) {
296
+ case "note":
297
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocNote, { entry });
298
+ case "tag":
299
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocTag, { entry });
300
+ case "kv":
301
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocKv, { entry });
302
+ case "code":
303
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocCode, { entry });
304
+ case "table":
305
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocTable, { entry });
306
+ case "link":
307
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocLink, { entry });
308
+ case "section":
309
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocSection, { entry });
310
+ case "mermaid":
311
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocMermaid, { entry });
312
+ case "screenshot":
313
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocScreenshot, { entry });
314
+ case "custom":
315
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(DocCustom, { entry });
316
+ }
317
+ }
318
+
319
+ // src/components/ReportDocEntries.tsx
320
+ var import_jsx_runtime14 = require("react/jsx-runtime");
321
+ function ReportDocEntries({ entries }) {
322
+ if (entries.length === 0) return null;
323
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_jsx_runtime14.Fragment, { children: entries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(DocEntry, { entry }, i)) });
324
+ }
325
+
326
+ // src/components/ReportSteps.tsx
327
+ var import_jsx_runtime15 = require("react/jsx-runtime");
328
+ function ReportSteps({ scenario }) {
329
+ if (scenario.steps.length === 0) return null;
330
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("ol", { className: "es-steps", children: scenario.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ReportStepItem, { step }, step.id)) });
331
+ }
332
+ function ReportStepItem({ step }) {
333
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
334
+ "li",
335
+ {
336
+ id: step.id,
337
+ className: `es-step es-step-${step.status}`,
338
+ "data-status": step.status,
339
+ children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "es-step-keyword", children: step.keyword }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "es-step-text", children: step.text }),
342
+ step.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("pre", { className: "es-scenario-error", role: "alert", children: step.errorMessage }) : null,
343
+ step.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "es-step-docs", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ReportDocEntries, { entries: step.docEntries }) }) : null
344
+ ]
345
+ }
346
+ );
347
+ }
348
+
349
+ // src/components/ReportScenario.tsx
350
+ var import_jsx_runtime16 = require("react/jsx-runtime");
351
+ var STATUS_LABEL = {
352
+ passed: "Passed",
353
+ failed: "Failed",
354
+ skipped: "Skipped",
355
+ pending: "Pending"
356
+ };
357
+ function ReportScenario({ scenario }) {
358
+ const titleId = `${scenario.id}-title`;
359
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
360
+ "article",
361
+ {
362
+ id: scenario.id,
363
+ className: `es-scenario es-status-${scenario.status}`,
364
+ "aria-labelledby": titleId,
365
+ "data-status": scenario.status,
366
+ children: [
367
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("h3", { id: titleId, className: "es-scenario-title", children: [
368
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { children: scenario.title }),
369
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "es-scenario-status", "aria-label": `Status: ${STATUS_LABEL[scenario.status]}`, children: STATUS_LABEL[scenario.status] })
370
+ ] }),
371
+ scenario.tags.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("ul", { className: "es-tags", "aria-label": "Tags", children: scenario.tags.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("li", { children: t }, t)) }) : null,
372
+ scenario.errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("pre", { className: "es-scenario-error", role: "alert", children: scenario.errorMessage }) : null,
373
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "es-scenario-docs", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
374
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ReportSteps, { scenario })
375
+ ]
376
+ }
377
+ );
378
+ }
379
+
380
+ // src/components/ReportScenarioList.tsx
381
+ var import_jsx_runtime17 = require("react/jsx-runtime");
382
+ function ReportScenarioList({ feature }) {
383
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_jsx_runtime17.Fragment, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(ReportScenario, { scenario }, scenario.id)) });
384
+ }
385
+
386
+ // src/components/ReportFeature.tsx
387
+ var import_jsx_runtime18 = require("react/jsx-runtime");
388
+ function ReportFeature({ feature }) {
389
+ const titleId = `${feature.id}-title`;
390
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
391
+ "section",
392
+ {
393
+ id: feature.id,
394
+ className: "es-feature",
395
+ "aria-labelledby": titleId,
396
+ children: [
397
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("h2", { id: titleId, className: "es-feature-title", children: feature.title }),
398
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "es-feature-source", children: feature.sourceFile }),
399
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ReportSummaryView, { summary: feature.summary, className: "es-feature-summary" }),
400
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ReportScenarioList, { feature })
401
+ ]
402
+ }
403
+ );
404
+ }
405
+
406
+ // src/components/ReportFeatureList.tsx
407
+ var import_jsx_runtime19 = require("react/jsx-runtime");
408
+ function ReportFeatureList() {
409
+ const report = useReport();
410
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_jsx_runtime19.Fragment, { children: report.features.map((feature) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(ReportFeature, { feature }, feature.id)) });
411
+ }
412
+
413
+ // src/components/ReportEmpty.tsx
414
+ var import_jsx_runtime20 = require("react/jsx-runtime");
415
+ function ReportEmpty({ message }) {
416
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("section", { className: "es-empty", "aria-live": "polite", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { children: message ?? "No scenarios in this report." }) });
417
+ }
418
+
419
+ // src/components/ReportSchemaError.tsx
420
+ var import_jsx_runtime21 = require("react/jsx-runtime");
421
+ function ReportSchemaError({ error }) {
422
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
423
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("strong", { children: "Report could not be displayed." }) }),
424
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { children: error.message }),
425
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("p", { children: [
426
+ "The report bundle is newer than this version of ",
427
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("code", { children: "executable-stories-react" }),
428
+ ". Upgrade the package, or regenerate the report with an older formatters CLI."
429
+ ] }) : null,
430
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("details", { children: [
431
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("summary", { children: [
432
+ error.issues.length,
433
+ " validation issue",
434
+ error.issues.length === 1 ? "" : "s"
435
+ ] }),
436
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
437
+ ] }) : null
438
+ ] });
439
+ }
440
+
441
+ // src/interactive/ReportSearch.tsx
442
+ var import_react5 = require("react");
443
+ var import_jsx_runtime22 = require("react/jsx-runtime");
444
+ var ReportSearch = (0, import_react5.forwardRef)(
445
+ function ReportSearch2(props, ref) {
446
+ const {
447
+ value,
448
+ onChange,
449
+ matchedCount,
450
+ totalCount,
451
+ placeholder = "Search scenarios, tags, or step text\u2026",
452
+ className
453
+ } = props;
454
+ const inputId = (0, import_react5.useId)();
455
+ const showCounts = typeof matchedCount === "number" && typeof totalCount === "number";
456
+ function handleChange(e) {
457
+ onChange(e.target.value);
458
+ }
459
+ function handleKeyDown(e) {
460
+ if (e.key === "Escape" && value !== "") {
461
+ onChange("");
462
+ e.preventDefault();
463
+ }
464
+ }
465
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: ["es-search", className].filter(Boolean).join(" "), children: [
466
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { htmlFor: inputId, className: "es-search-label", children: "Search" }),
467
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
468
+ "input",
469
+ {
470
+ ref,
471
+ id: inputId,
472
+ type: "search",
473
+ value,
474
+ onChange: handleChange,
475
+ onKeyDown: handleKeyDown,
476
+ placeholder,
477
+ autoComplete: "off",
478
+ spellCheck: false,
479
+ "aria-keyshortcuts": "/"
480
+ }
481
+ ),
482
+ showCounts ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "es-search-counts", "aria-live": "polite", children: value ? `${matchedCount} of ${totalCount}` : `${totalCount} total` }) : null
483
+ ] });
484
+ }
485
+ );
486
+
487
+ // src/interactive/ReportFailureBanner.tsx
488
+ var import_react6 = require("react");
489
+ var import_jsx_runtime23 = require("react/jsx-runtime");
490
+ function ReportFailureBanner({ failures }) {
491
+ const first = failures[0];
492
+ const jumpToFirst = (0, import_react6.useCallback)(() => {
493
+ if (!first) return;
494
+ if (typeof window === "undefined") return;
495
+ const el = document.getElementById(first.scenarioId);
496
+ el?.scrollIntoView({ behavior: "smooth", block: "start" });
497
+ if (typeof history !== "undefined") {
498
+ history.replaceState(null, "", `#${first.scenarioId}`);
499
+ }
500
+ }, [first]);
501
+ if (failures.length === 0) return null;
502
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
503
+ "aside",
504
+ {
505
+ className: "es-failure-banner",
506
+ role: "status",
507
+ "aria-live": "polite",
508
+ "aria-label": "Failure summary",
509
+ children: [
510
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { className: "es-failure-banner-text", children: [
511
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("strong", { children: failures.length }),
512
+ " ",
513
+ "failure",
514
+ failures.length === 1 ? "" : "s"
515
+ ] }),
516
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
517
+ "button",
518
+ {
519
+ type: "button",
520
+ className: "es-failure-banner-jump",
521
+ onClick: jumpToFirst,
522
+ "aria-label": "Jump to first failure",
523
+ children: "Jump to first \u2193"
524
+ }
525
+ )
526
+ ]
527
+ }
528
+ );
529
+ }
530
+
531
+ // src/interactive/ReportShortcutsHelp.tsx
532
+ var import_react7 = require("react");
533
+ var import_jsx_runtime24 = require("react/jsx-runtime");
534
+ var SHORTCUTS = [
535
+ { keys: "/", description: "Focus search" },
536
+ { keys: "f", description: "Jump to next failure" },
537
+ { keys: "Shift+F", description: "Jump to previous failure" },
538
+ { keys: "Esc", description: "Clear search / close dialog" },
539
+ { keys: "?", description: "Toggle this help" }
540
+ ];
541
+ function ReportShortcutsHelp({ open, onClose }) {
542
+ const dialogRef = (0, import_react7.useRef)(null);
543
+ (0, import_react7.useEffect)(() => {
544
+ const dialog = dialogRef.current;
545
+ if (!dialog) return;
546
+ if (open && !dialog.open) dialog.showModal();
547
+ else if (!open && dialog.open) dialog.close();
548
+ }, [open]);
549
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
550
+ "dialog",
551
+ {
552
+ ref: dialogRef,
553
+ className: "es-shortcuts-help",
554
+ "aria-label": "Keyboard shortcuts",
555
+ onClose,
556
+ children: [
557
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h2", { children: "Keyboard shortcuts" }),
558
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dl", { children: SHORTCUTS.map((s) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { children: [
559
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dt", { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("kbd", { children: s.keys }) }),
560
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("dd", { children: s.description })
561
+ ] }, s.keys)) }),
562
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("form", { method: "dialog", children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "submit", className: "es-shortcuts-close", children: "Close" }) })
563
+ ]
564
+ }
565
+ );
566
+ }
567
+
568
+ // src/interactive/use-keyboard-shortcuts.ts
569
+ var import_react8 = require("react");
570
+ function isEditableTarget(target) {
571
+ if (!(target instanceof HTMLElement)) return false;
572
+ const tag = target.tagName;
573
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
574
+ if (target.isContentEditable) return true;
575
+ return false;
576
+ }
577
+ function useKeyboardShortcuts(handlers) {
578
+ const ref = (0, import_react8.useRef)(handlers);
579
+ ref.current = handlers;
580
+ (0, import_react8.useEffect)(() => {
581
+ function onKeyDown(e) {
582
+ const h = ref.current;
583
+ if (e.key === "Escape") {
584
+ h.onEscape?.();
585
+ return;
586
+ }
587
+ if (isEditableTarget(e.target)) return;
588
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
589
+ switch (e.key) {
590
+ case "/":
591
+ h.onFocusSearch?.();
592
+ e.preventDefault();
593
+ return;
594
+ case "?":
595
+ h.onToggleHelp?.();
596
+ e.preventDefault();
597
+ return;
598
+ case "f":
599
+ h.onNextFailure?.();
600
+ e.preventDefault();
601
+ return;
602
+ case "F":
603
+ h.onPrevFailure?.();
604
+ e.preventDefault();
605
+ return;
606
+ default:
607
+ return;
608
+ }
609
+ }
610
+ window.addEventListener("keydown", onKeyDown);
611
+ return () => window.removeEventListener("keydown", onKeyDown);
612
+ }, []);
613
+ }
614
+
615
+ // src/interactive/use-deep-link-scroll.ts
616
+ var import_react9 = require("react");
617
+ function useDeepLinkScroll() {
618
+ (0, import_react9.useEffect)(() => {
619
+ function scrollToHash() {
620
+ const hash = window.location.hash.replace(/^#/, "");
621
+ if (!hash) return;
622
+ const el = document.getElementById(hash);
623
+ if (!el) return;
624
+ requestAnimationFrame(() => {
625
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
626
+ });
627
+ }
628
+ scrollToHash();
629
+ window.addEventListener("hashchange", scrollToHash);
630
+ return () => window.removeEventListener("hashchange", scrollToHash);
631
+ }, []);
632
+ }
633
+
634
+ // src/interactive/filter.ts
635
+ function normalizeQuery(query) {
636
+ return query.trim().toLowerCase();
637
+ }
638
+ function scenarioMatches(scenario, q) {
639
+ if (q === "") return true;
640
+ if (scenario.title.toLowerCase().includes(q)) return true;
641
+ for (const tag of scenario.tags) {
642
+ if (tag.toLowerCase().includes(q)) return true;
643
+ }
644
+ for (const step of scenario.steps) {
645
+ if (step.text.toLowerCase().includes(q)) return true;
646
+ }
647
+ return false;
648
+ }
649
+ function summarizeScenarios(scenarios) {
650
+ let total = 0, passed = 0, failed = 0, skipped = 0, pending = 0, durationMs = 0;
651
+ for (const s of scenarios) {
652
+ total += 1;
653
+ durationMs += s.durationMs;
654
+ if (s.status === "passed") passed += 1;
655
+ else if (s.status === "failed") failed += 1;
656
+ else if (s.status === "skipped") skipped += 1;
657
+ else pending += 1;
658
+ }
659
+ return { total, passed, failed, skipped, pending, durationMs };
660
+ }
661
+ function filterReport(report, query) {
662
+ const q = normalizeQuery(query);
663
+ if (q === "") return report;
664
+ const features = [];
665
+ let topTotal = 0, topPassed = 0, topFailed = 0, topSkipped = 0, topPending = 0, topDuration = 0;
666
+ for (const feature of report.features) {
667
+ const matched = feature.scenarios.filter((s) => scenarioMatches(s, q));
668
+ if (matched.length === 0) continue;
669
+ const summary = summarizeScenarios(matched);
670
+ features.push({ ...feature, summary, scenarios: matched });
671
+ topTotal += summary.total;
672
+ topPassed += summary.passed;
673
+ topFailed += summary.failed;
674
+ topSkipped += summary.skipped;
675
+ topPending += summary.pending;
676
+ topDuration += summary.durationMs;
677
+ }
678
+ return {
679
+ ...report,
680
+ summary: {
681
+ total: topTotal,
682
+ passed: topPassed,
683
+ failed: topFailed,
684
+ skipped: topSkipped,
685
+ pending: topPending,
686
+ durationMs: topDuration
687
+ },
688
+ features
689
+ };
690
+ }
691
+ function listFailures(report) {
692
+ const out = [];
693
+ for (const feature of report.features) {
694
+ for (const scenario of feature.scenarios) {
695
+ if (scenario.status === "failed") {
696
+ const ref = {
697
+ featureId: feature.id,
698
+ scenarioId: scenario.id,
699
+ scenarioTitle: scenario.title
700
+ };
701
+ if (scenario.errorMessage !== void 0) {
702
+ ref.errorMessage = scenario.errorMessage;
703
+ }
704
+ out.push(ref);
705
+ }
706
+ }
707
+ }
708
+ return out;
709
+ }
710
+
711
+ // src/interactive/ReportInteractive.tsx
712
+ var import_jsx_runtime25 = require("react/jsx-runtime");
713
+ function isResult(value) {
714
+ return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean";
715
+ }
716
+ function ReportInteractive(props) {
717
+ const { report, className, title, dataTheme } = props;
718
+ if (isResult(report)) {
719
+ if (!report.ok) {
720
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
721
+ "main",
722
+ {
723
+ className: ["es-report", className].filter(Boolean).join(" "),
724
+ "aria-label": title ?? "Test report",
725
+ "data-theme": dataTheme,
726
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportSchemaError, { error: report.error })
727
+ }
728
+ );
729
+ }
730
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportInteractiveView, { ...props, report: report.data });
731
+ }
732
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportInteractiveView, { ...props, report });
733
+ }
734
+ function ReportInteractiveView({
735
+ report,
736
+ customRenderers,
737
+ renderers,
738
+ className,
739
+ title,
740
+ dataTheme
741
+ }) {
742
+ const [query, setQuery] = (0, import_react10.useState)("");
743
+ const [helpOpen, setHelpOpen] = (0, import_react10.useState)(false);
744
+ const searchRef = (0, import_react10.useRef)(null);
745
+ const failures = (0, import_react10.useMemo)(() => listFailures(report), [report]);
746
+ const filtered = (0, import_react10.useMemo)(() => filterReport(report, query), [report, query]);
747
+ const failureIndexRef = (0, import_react10.useRef)(0);
748
+ const focusSearch = (0, import_react10.useCallback)(() => {
749
+ searchRef.current?.focus();
750
+ }, []);
751
+ const scrollToScenario = (0, import_react10.useCallback)((scenarioId) => {
752
+ if (typeof document === "undefined") return;
753
+ const el = document.getElementById(scenarioId);
754
+ if (!el) return;
755
+ el.scrollIntoView({ behavior: "smooth", block: "start" });
756
+ if (typeof history !== "undefined") {
757
+ history.replaceState(null, "", `#${scenarioId}`);
758
+ }
759
+ }, []);
760
+ const stepFailure = (0, import_react10.useCallback)(
761
+ (direction) => {
762
+ if (failures.length === 0) return;
763
+ failureIndexRef.current = (failureIndexRef.current + direction + failures.length) % failures.length;
764
+ const target = failures[failureIndexRef.current];
765
+ if (target) scrollToScenario(target.scenarioId);
766
+ },
767
+ [failures, scrollToScenario]
768
+ );
769
+ const toggleHelp = (0, import_react10.useCallback)(() => {
770
+ setHelpOpen((v) => !v);
771
+ }, []);
772
+ const escape = (0, import_react10.useCallback)(() => {
773
+ if (helpOpen) setHelpOpen(false);
774
+ else if (query !== "") setQuery("");
775
+ }, [helpOpen, query]);
776
+ useKeyboardShortcuts({
777
+ onFocusSearch: focusSearch,
778
+ onNextFailure: () => stepFailure(1),
779
+ onPrevFailure: () => stepFailure(-1),
780
+ onToggleHelp: toggleHelp,
781
+ onEscape: escape
782
+ });
783
+ useDeepLinkScroll();
784
+ const hasContent = filtered.features.length > 0;
785
+ const totalScenarios = report.summary.total;
786
+ const matchedScenarios = filtered.summary.total;
787
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
788
+ ReportRoot,
789
+ {
790
+ report: filtered,
791
+ customRenderers,
792
+ renderers,
793
+ children: /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
794
+ "main",
795
+ {
796
+ className: ["es-report", "es-report-interactive", className].filter(Boolean).join(" "),
797
+ "aria-label": title ?? "Test report",
798
+ "data-theme": dataTheme,
799
+ children: [
800
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("header", { className: "es-report-header", children: [
801
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("h1", { children: title ?? "Story Report" }),
802
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportSummary, {}),
803
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
804
+ ReportSearch,
805
+ {
806
+ ref: searchRef,
807
+ value: query,
808
+ onChange: setQuery,
809
+ matchedCount: matchedScenarios,
810
+ totalCount: totalScenarios
811
+ }
812
+ )
813
+ ] }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportFailureBanner, { failures }),
815
+ hasContent ? /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportFeatureList, {}) : /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportEmpty, { message: query ? "No scenarios match the search." : void 0 }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
817
+ "button",
818
+ {
819
+ type: "button",
820
+ className: "es-shortcuts-trigger",
821
+ "aria-label": "Keyboard shortcuts",
822
+ "aria-keyshortcuts": "Shift+?",
823
+ onClick: toggleHelp,
824
+ children: "?"
825
+ }
826
+ ),
827
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(ReportShortcutsHelp, { open: helpOpen, onClose: () => setHelpOpen(false) })
828
+ ]
829
+ }
830
+ )
831
+ }
832
+ );
833
+ }
834
+ // Annotate the CommonJS export names for ESM import in node:
835
+ 0 && (module.exports = {
836
+ ReportFailureBanner,
837
+ ReportInteractive,
838
+ ReportSearch,
839
+ ReportShortcutsHelp,
840
+ filterReport,
841
+ listFailures,
842
+ normalizeQuery,
843
+ useDeepLinkScroll,
844
+ useKeyboardShortcuts
845
+ });
846
+ //# sourceMappingURL=interactive.cjs.map