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.
package/dist/index.js ADDED
@@ -0,0 +1,964 @@
1
+ "use client";
2
+
3
+ // src/context/ReportRoot.tsx
4
+ import { useMemo } from "react";
5
+
6
+ // src/context/ReportContext.ts
7
+ import { createContext } from "react";
8
+ var ReportContext = createContext(null);
9
+
10
+ // src/context/ReportRoot.tsx
11
+ import { jsx } from "react/jsx-runtime";
12
+ var EMPTY_CUSTOM = {};
13
+ var EMPTY_RENDERERS = {};
14
+ function ReportRoot({
15
+ report,
16
+ customRenderers,
17
+ renderers,
18
+ children
19
+ }) {
20
+ const value = useMemo(
21
+ () => ({
22
+ report,
23
+ customRenderers: customRenderers ?? EMPTY_CUSTOM,
24
+ renderers: renderers ?? EMPTY_RENDERERS
25
+ }),
26
+ [report, customRenderers, renderers]
27
+ );
28
+ return /* @__PURE__ */ jsx(ReportContext.Provider, { value, children });
29
+ }
30
+
31
+ // src/hooks/useReport.ts
32
+ import { useContext } from "react";
33
+ function useReport() {
34
+ const ctx = useContext(ReportContext);
35
+ if (!ctx) {
36
+ throw new Error(
37
+ "useReport must be used inside <ReportRoot> or <Report>. Wrap your tree with one of those."
38
+ );
39
+ }
40
+ return ctx.report;
41
+ }
42
+
43
+ // src/components/ReportSummary.tsx
44
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
45
+ function ReportSummary({ className }) {
46
+ const report = useReport();
47
+ return /* @__PURE__ */ jsx2(
48
+ ReportSummaryView,
49
+ {
50
+ summary: report.summary,
51
+ ...className !== void 0 && { className },
52
+ ariaLabel: "Run summary"
53
+ }
54
+ );
55
+ }
56
+ function ReportSummaryView({ summary, className, ariaLabel }) {
57
+ return /* @__PURE__ */ jsxs(
58
+ "p",
59
+ {
60
+ className: ["es-report-summary", className].filter(Boolean).join(" "),
61
+ "aria-label": ariaLabel,
62
+ children: [
63
+ /* @__PURE__ */ jsxs("span", { children: [
64
+ /* @__PURE__ */ jsx2("strong", { children: summary.total }),
65
+ " scenario",
66
+ summary.total === 1 ? "" : "s"
67
+ ] }),
68
+ " \xB7 ",
69
+ /* @__PURE__ */ jsxs("span", { "data-status": "passed", children: [
70
+ summary.passed,
71
+ " passed"
72
+ ] }),
73
+ " \xB7 ",
74
+ /* @__PURE__ */ jsxs("span", { "data-status": "failed", children: [
75
+ summary.failed,
76
+ " failed"
77
+ ] }),
78
+ summary.skipped > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
79
+ " \xB7 ",
80
+ /* @__PURE__ */ jsxs("span", { "data-status": "skipped", children: [
81
+ summary.skipped,
82
+ " skipped"
83
+ ] })
84
+ ] }) : null,
85
+ summary.pending > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ " \xB7 ",
87
+ /* @__PURE__ */ jsxs("span", { "data-status": "pending", children: [
88
+ summary.pending,
89
+ " pending"
90
+ ] })
91
+ ] }) : null
92
+ ]
93
+ }
94
+ );
95
+ }
96
+
97
+ // src/components/doc/DocNote.tsx
98
+ import { jsx as jsx3 } from "react/jsx-runtime";
99
+ function DocNote({ entry }) {
100
+ return /* @__PURE__ */ jsx3("p", { className: "es-doc es-doc-note", children: entry.text });
101
+ }
102
+
103
+ // src/components/doc/DocTag.tsx
104
+ import { jsx as jsx4 } from "react/jsx-runtime";
105
+ function DocTag({ entry }) {
106
+ return /* @__PURE__ */ jsx4("ul", { className: "es-doc es-tags", "aria-label": "Tags", children: entry.names.map((n) => /* @__PURE__ */ jsx4("li", { children: n }, n)) });
107
+ }
108
+
109
+ // src/components/doc/DocKv.tsx
110
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
111
+ function formatValue(value) {
112
+ if (value === null) return "null";
113
+ if (typeof value === "string") return value;
114
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
115
+ try {
116
+ return JSON.stringify(value);
117
+ } catch {
118
+ return String(value);
119
+ }
120
+ }
121
+ function DocKv({ entry }) {
122
+ return /* @__PURE__ */ jsxs2("dl", { className: "es-doc es-doc-kv", children: [
123
+ /* @__PURE__ */ jsx5("dt", { children: entry.label }),
124
+ /* @__PURE__ */ jsx5("dd", { children: formatValue(entry.value) })
125
+ ] });
126
+ }
127
+
128
+ // src/hooks/useRenderers.ts
129
+ import { useContext as useContext2 } from "react";
130
+ var EMPTY_CUSTOM2 = {};
131
+ var EMPTY_RENDERERS2 = {};
132
+ function useCustomRenderers() {
133
+ const ctx = useContext2(ReportContext);
134
+ return ctx?.customRenderers ?? EMPTY_CUSTOM2;
135
+ }
136
+ function useBuiltinRenderers() {
137
+ const ctx = useContext2(ReportContext);
138
+ return ctx?.renderers ?? EMPTY_RENDERERS2;
139
+ }
140
+
141
+ // src/components/doc/DocCode.tsx
142
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
143
+ function DocCode({ entry }) {
144
+ const renderers = useBuiltinRenderers();
145
+ if (renderers.code) {
146
+ return /* @__PURE__ */ jsx6(Fragment2, { children: renderers.code(entry) });
147
+ }
148
+ return /* @__PURE__ */ jsxs3("figure", { className: "es-doc es-doc-code", children: [
149
+ /* @__PURE__ */ jsx6("figcaption", { children: entry.label }),
150
+ /* @__PURE__ */ jsx6("pre", { children: /* @__PURE__ */ jsx6("code", { className: entry.lang ? `language-${entry.lang}` : void 0, children: entry.content }) })
151
+ ] });
152
+ }
153
+
154
+ // src/components/doc/DocTable.tsx
155
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
156
+ function DocTable({ entry }) {
157
+ return /* @__PURE__ */ jsxs4("figure", { className: "es-doc es-doc-table", children: [
158
+ /* @__PURE__ */ jsx7("figcaption", { children: entry.label }),
159
+ /* @__PURE__ */ jsxs4("table", { children: [
160
+ /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsx7("tr", { children: entry.columns.map((c) => /* @__PURE__ */ jsx7("th", { scope: "col", children: c }, c)) }) }),
161
+ /* @__PURE__ */ jsx7("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx7("tr", { children: row.map((cell, j) => /* @__PURE__ */ jsx7("td", { children: cell }, j)) }, i)) })
162
+ ] })
163
+ ] });
164
+ }
165
+
166
+ // src/components/doc/DocLink.tsx
167
+ import { jsx as jsx8 } from "react/jsx-runtime";
168
+ function DocLink({ entry }) {
169
+ return /* @__PURE__ */ jsx8(
170
+ "a",
171
+ {
172
+ className: "es-doc es-doc-link",
173
+ href: entry.url,
174
+ rel: "noreferrer noopener",
175
+ target: "_blank",
176
+ children: entry.label
177
+ }
178
+ );
179
+ }
180
+
181
+ // src/components/doc/DocSection.tsx
182
+ import { marked } from "marked";
183
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
184
+ function safeMarkdownHtml(markdown) {
185
+ const raw = marked.parse(markdown, { async: false });
186
+ 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='#'");
187
+ }
188
+ function DocSection({ entry }) {
189
+ const renderers = useBuiltinRenderers();
190
+ if (renderers.section) {
191
+ return /* @__PURE__ */ jsx9(Fragment3, { children: renderers.section(entry) });
192
+ }
193
+ const html = safeMarkdownHtml(entry.markdown);
194
+ return /* @__PURE__ */ jsxs5("section", { className: "es-doc es-doc-section", "aria-label": entry.title, children: [
195
+ entry.title ? /* @__PURE__ */ jsx9("h4", { className: "es-doc-section-title", children: entry.title }) : null,
196
+ /* @__PURE__ */ jsx9(
197
+ "div",
198
+ {
199
+ className: "es-doc-section-content",
200
+ dangerouslySetInnerHTML: { __html: html }
201
+ }
202
+ )
203
+ ] });
204
+ }
205
+
206
+ // src/components/doc/DocMermaid.tsx
207
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
208
+ function DocMermaid({ entry }) {
209
+ const renderers = useBuiltinRenderers();
210
+ if (renderers.mermaid) {
211
+ return /* @__PURE__ */ jsx10(Fragment4, { children: renderers.mermaid(entry) });
212
+ }
213
+ return /* @__PURE__ */ jsxs6(
214
+ "figure",
215
+ {
216
+ className: "es-doc es-doc-mermaid",
217
+ "aria-label": entry.title ?? "Diagram",
218
+ children: [
219
+ entry.title ? /* @__PURE__ */ jsx10("figcaption", { children: entry.title }) : null,
220
+ /* @__PURE__ */ jsx10("pre", { "data-mermaid": true, children: entry.code })
221
+ ]
222
+ }
223
+ );
224
+ }
225
+
226
+ // src/components/doc/DocScreenshot.tsx
227
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
228
+ function DocScreenshot({ entry }) {
229
+ return /* @__PURE__ */ jsxs7("figure", { className: "es-doc es-doc-screenshot", children: [
230
+ /* @__PURE__ */ jsx11("img", { src: entry.path, alt: entry.alt ?? "", loading: "lazy" }),
231
+ entry.alt ? /* @__PURE__ */ jsx11("figcaption", { children: entry.alt }) : null
232
+ ] });
233
+ }
234
+
235
+ // src/components/doc/DocCustom.tsx
236
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
237
+ function DocCustom({ entry }) {
238
+ const renderers = useCustomRenderers();
239
+ const renderer = renderers[entry.type];
240
+ if (renderer) {
241
+ return /* @__PURE__ */ jsx12(Fragment5, { children: renderer(entry) });
242
+ }
243
+ return /* @__PURE__ */ jsxs8("div", { className: "es-doc es-doc-custom", "data-type": entry.type, children: [
244
+ /* @__PURE__ */ jsx12("p", { className: "es-doc-custom-type", children: entry.type }),
245
+ /* @__PURE__ */ jsx12("pre", { children: safeStringify(entry.data) })
246
+ ] });
247
+ }
248
+ function safeStringify(value) {
249
+ try {
250
+ return JSON.stringify(value, null, 2);
251
+ } catch {
252
+ return String(value);
253
+ }
254
+ }
255
+
256
+ // src/components/doc/DocEntry.tsx
257
+ import { jsx as jsx13 } from "react/jsx-runtime";
258
+ function DocEntry({ entry }) {
259
+ switch (entry.kind) {
260
+ case "note":
261
+ return /* @__PURE__ */ jsx13(DocNote, { entry });
262
+ case "tag":
263
+ return /* @__PURE__ */ jsx13(DocTag, { entry });
264
+ case "kv":
265
+ return /* @__PURE__ */ jsx13(DocKv, { entry });
266
+ case "code":
267
+ return /* @__PURE__ */ jsx13(DocCode, { entry });
268
+ case "table":
269
+ return /* @__PURE__ */ jsx13(DocTable, { entry });
270
+ case "link":
271
+ return /* @__PURE__ */ jsx13(DocLink, { entry });
272
+ case "section":
273
+ return /* @__PURE__ */ jsx13(DocSection, { entry });
274
+ case "mermaid":
275
+ return /* @__PURE__ */ jsx13(DocMermaid, { entry });
276
+ case "screenshot":
277
+ return /* @__PURE__ */ jsx13(DocScreenshot, { entry });
278
+ case "custom":
279
+ return /* @__PURE__ */ jsx13(DocCustom, { entry });
280
+ }
281
+ }
282
+
283
+ // src/components/ReportDocEntries.tsx
284
+ import { Fragment as Fragment6, jsx as jsx14 } from "react/jsx-runtime";
285
+ function ReportDocEntries({ entries }) {
286
+ if (entries.length === 0) return null;
287
+ return /* @__PURE__ */ jsx14(Fragment6, { children: entries.map((entry, i) => /* @__PURE__ */ jsx14(DocEntry, { entry }, i)) });
288
+ }
289
+
290
+ // src/components/ReportSteps.tsx
291
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
292
+ function ReportSteps({ scenario }) {
293
+ if (scenario.steps.length === 0) return null;
294
+ return /* @__PURE__ */ jsx15("ol", { className: "es-steps", children: scenario.steps.map((step) => /* @__PURE__ */ jsx15(ReportStepItem, { step }, step.id)) });
295
+ }
296
+ function ReportStepItem({ step }) {
297
+ return /* @__PURE__ */ jsxs9(
298
+ "li",
299
+ {
300
+ id: step.id,
301
+ className: `es-step es-step-${step.status}`,
302
+ "data-status": step.status,
303
+ children: [
304
+ /* @__PURE__ */ jsx15("span", { className: "es-step-keyword", children: step.keyword }),
305
+ /* @__PURE__ */ jsx15("span", { className: "es-step-text", children: step.text }),
306
+ step.errorMessage ? /* @__PURE__ */ jsx15("pre", { className: "es-scenario-error", role: "alert", children: step.errorMessage }) : null,
307
+ step.docEntries.length > 0 ? /* @__PURE__ */ jsx15("div", { className: "es-step-docs", children: /* @__PURE__ */ jsx15(ReportDocEntries, { entries: step.docEntries }) }) : null
308
+ ]
309
+ }
310
+ );
311
+ }
312
+
313
+ // src/components/ReportScenario.tsx
314
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
315
+ var STATUS_LABEL = {
316
+ passed: "Passed",
317
+ failed: "Failed",
318
+ skipped: "Skipped",
319
+ pending: "Pending"
320
+ };
321
+ function ReportScenario({ scenario }) {
322
+ const titleId = `${scenario.id}-title`;
323
+ return /* @__PURE__ */ jsxs10(
324
+ "article",
325
+ {
326
+ id: scenario.id,
327
+ className: `es-scenario es-status-${scenario.status}`,
328
+ "aria-labelledby": titleId,
329
+ "data-status": scenario.status,
330
+ children: [
331
+ /* @__PURE__ */ jsxs10("h3", { id: titleId, className: "es-scenario-title", children: [
332
+ /* @__PURE__ */ jsx16("span", { children: scenario.title }),
333
+ /* @__PURE__ */ jsx16("span", { className: "es-scenario-status", "aria-label": `Status: ${STATUS_LABEL[scenario.status]}`, children: STATUS_LABEL[scenario.status] })
334
+ ] }),
335
+ scenario.tags.length > 0 ? /* @__PURE__ */ jsx16("ul", { className: "es-tags", "aria-label": "Tags", children: scenario.tags.map((t) => /* @__PURE__ */ jsx16("li", { children: t }, t)) }) : null,
336
+ scenario.errorMessage ? /* @__PURE__ */ jsx16("pre", { className: "es-scenario-error", role: "alert", children: scenario.errorMessage }) : null,
337
+ scenario.docEntries.length > 0 ? /* @__PURE__ */ jsx16("div", { className: "es-scenario-docs", children: /* @__PURE__ */ jsx16(ReportDocEntries, { entries: scenario.docEntries }) }) : null,
338
+ /* @__PURE__ */ jsx16(ReportSteps, { scenario })
339
+ ]
340
+ }
341
+ );
342
+ }
343
+
344
+ // src/components/ReportScenarioList.tsx
345
+ import { Fragment as Fragment7, jsx as jsx17 } from "react/jsx-runtime";
346
+ function ReportScenarioList({ feature }) {
347
+ return /* @__PURE__ */ jsx17(Fragment7, { children: feature.scenarios.map((scenario) => /* @__PURE__ */ jsx17(ReportScenario, { scenario }, scenario.id)) });
348
+ }
349
+
350
+ // src/components/ReportFeature.tsx
351
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
352
+ function ReportFeature({ feature }) {
353
+ const titleId = `${feature.id}-title`;
354
+ return /* @__PURE__ */ jsxs11(
355
+ "section",
356
+ {
357
+ id: feature.id,
358
+ className: "es-feature",
359
+ "aria-labelledby": titleId,
360
+ children: [
361
+ /* @__PURE__ */ jsx18("h2", { id: titleId, className: "es-feature-title", children: feature.title }),
362
+ /* @__PURE__ */ jsx18("p", { className: "es-feature-source", children: feature.sourceFile }),
363
+ /* @__PURE__ */ jsx18(ReportSummaryView, { summary: feature.summary, className: "es-feature-summary" }),
364
+ /* @__PURE__ */ jsx18(ReportScenarioList, { feature })
365
+ ]
366
+ }
367
+ );
368
+ }
369
+
370
+ // src/components/ReportFeatureList.tsx
371
+ import { Fragment as Fragment8, jsx as jsx19 } from "react/jsx-runtime";
372
+ function ReportFeatureList() {
373
+ const report = useReport();
374
+ return /* @__PURE__ */ jsx19(Fragment8, { children: report.features.map((feature) => /* @__PURE__ */ jsx19(ReportFeature, { feature }, feature.id)) });
375
+ }
376
+
377
+ // src/components/ReportEmpty.tsx
378
+ import { jsx as jsx20 } from "react/jsx-runtime";
379
+ function ReportEmpty({ message }) {
380
+ return /* @__PURE__ */ jsx20("section", { className: "es-empty", "aria-live": "polite", children: /* @__PURE__ */ jsx20("p", { children: message ?? "No scenarios in this report." }) });
381
+ }
382
+
383
+ // src/components/ReportSchemaError.tsx
384
+ import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
385
+ function ReportSchemaError({ error }) {
386
+ return /* @__PURE__ */ jsxs12("section", { className: "es-schema-error", role: "alert", "aria-live": "assertive", children: [
387
+ /* @__PURE__ */ jsx21("p", { children: /* @__PURE__ */ jsx21("strong", { children: "Report could not be displayed." }) }),
388
+ /* @__PURE__ */ jsx21("p", { children: error.message }),
389
+ error.code === "SCHEMA_VERSION_MISMATCH" ? /* @__PURE__ */ jsxs12("p", { children: [
390
+ "The report bundle is newer than this version of ",
391
+ /* @__PURE__ */ jsx21("code", { children: "executable-stories-react" }),
392
+ ". Upgrade the package, or regenerate the report with an older formatters CLI."
393
+ ] }) : null,
394
+ error.issues && error.issues.length > 0 ? /* @__PURE__ */ jsxs12("details", { children: [
395
+ /* @__PURE__ */ jsxs12("summary", { children: [
396
+ error.issues.length,
397
+ " validation issue",
398
+ error.issues.length === 1 ? "" : "s"
399
+ ] }),
400
+ /* @__PURE__ */ jsx21("pre", { children: error.issues.slice(0, 20).map((i) => `${i.path}: ${i.message}`).join("\n") })
401
+ ] }) : null
402
+ ] });
403
+ }
404
+
405
+ // src/components/Report.tsx
406
+ import { jsx as jsx22, jsxs as jsxs13 } from "react/jsx-runtime";
407
+ function isResult(value) {
408
+ return typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean";
409
+ }
410
+ function Report(props) {
411
+ const { report, customRenderers, renderers, className, title, dataTheme } = props;
412
+ if (isResult(report)) {
413
+ if (!report.ok) {
414
+ return /* @__PURE__ */ jsx22(
415
+ "main",
416
+ {
417
+ className: ["es-report", className].filter(Boolean).join(" "),
418
+ "aria-label": title ?? "Test report",
419
+ "data-theme": dataTheme,
420
+ children: /* @__PURE__ */ jsx22(ReportSchemaError, { error: report.error })
421
+ }
422
+ );
423
+ }
424
+ return /* @__PURE__ */ jsx22(
425
+ ReportView,
426
+ {
427
+ report: report.data,
428
+ customRenderers,
429
+ renderers,
430
+ className,
431
+ title,
432
+ dataTheme
433
+ }
434
+ );
435
+ }
436
+ return /* @__PURE__ */ jsx22(
437
+ ReportView,
438
+ {
439
+ report,
440
+ customRenderers,
441
+ renderers,
442
+ className,
443
+ title,
444
+ dataTheme
445
+ }
446
+ );
447
+ }
448
+ function ReportView({
449
+ report,
450
+ customRenderers,
451
+ renderers,
452
+ className,
453
+ title,
454
+ dataTheme
455
+ }) {
456
+ const hasContent = report.features.length > 0;
457
+ return /* @__PURE__ */ jsx22(ReportRoot, { report, customRenderers, renderers, children: /* @__PURE__ */ jsxs13(
458
+ "main",
459
+ {
460
+ className: ["es-report", className].filter(Boolean).join(" "),
461
+ "aria-label": title ?? "Test report",
462
+ "data-theme": dataTheme,
463
+ children: [
464
+ /* @__PURE__ */ jsxs13("header", { className: "es-report-header", children: [
465
+ /* @__PURE__ */ jsx22("h1", { children: title ?? "Story Report" }),
466
+ /* @__PURE__ */ jsx22(ReportSummary, {})
467
+ ] }),
468
+ hasContent ? /* @__PURE__ */ jsx22(ReportFeatureList, {}) : /* @__PURE__ */ jsx22(ReportEmpty, {})
469
+ ]
470
+ }
471
+ ) });
472
+ }
473
+
474
+ // src/result.ts
475
+ var ok = (data) => ({ ok: true, data });
476
+ var err = (error) => ({ ok: false, error });
477
+
478
+ // src/schema/story-report.schema.ts
479
+ import { z } from "zod";
480
+
481
+ // ../executable-stories-formatters/schemas/story-report-v1.json
482
+ var story_report_v1_default = {
483
+ $schema: "https://json-schema.org/draft/2020-12/schema",
484
+ $id: "https://executable-stories.dev/schemas/story-report-v1.schema.json",
485
+ title: "StoryReport",
486
+ description: "Pre-grouped, denormalized report shape consumed by UI renderers (React, Svelte, Vue, etc.). Stable public contract \u2014 additive-only within a major. Distinct from internal TestRunResult.",
487
+ type: "object",
488
+ $ref: "#/$defs/StoryReport",
489
+ $defs: {
490
+ StoryReport: {
491
+ type: "object",
492
+ description: "Top-level report containing all features, runtime metadata, and a pre-computed summary.",
493
+ properties: {
494
+ schemaVersion: {
495
+ type: "string",
496
+ pattern: "^1\\.[0-9]+$",
497
+ description: "Schema version as 'major.minor'. Major bumps are breaking; minors are additive-only. UI packages must accept any 1.x."
498
+ },
499
+ runId: {
500
+ type: "string",
501
+ minLength: 1,
502
+ description: "Unique deterministic identifier for this run."
503
+ },
504
+ startedAtMs: {
505
+ type: "number",
506
+ minimum: 0,
507
+ description: "Run start time as Unix epoch milliseconds."
508
+ },
509
+ finishedAtMs: {
510
+ type: "number",
511
+ minimum: 0,
512
+ description: "Run finish time as Unix epoch milliseconds."
513
+ },
514
+ durationMs: {
515
+ type: "number",
516
+ minimum: 0,
517
+ description: "Total run duration in milliseconds."
518
+ },
519
+ projectRoot: {
520
+ type: "string",
521
+ minLength: 1,
522
+ description: "Absolute path to the project root (for context only; relative paths in features.sourceFile are preferred)."
523
+ },
524
+ packageVersion: {
525
+ type: "string",
526
+ description: "Version of the package under test, if known."
527
+ },
528
+ gitSha: {
529
+ type: "string",
530
+ description: "Git commit SHA at the time of the run."
531
+ },
532
+ ci: {
533
+ $ref: "#/$defs/CIInfo"
534
+ },
535
+ coverage: {
536
+ $ref: "#/$defs/CoverageSummary"
537
+ },
538
+ summary: {
539
+ $ref: "#/$defs/Summary",
540
+ description: "Pre-computed counts across all features and scenarios."
541
+ },
542
+ features: {
543
+ type: "array",
544
+ items: { $ref: "#/$defs/Feature" },
545
+ description: "Features grouped by sourceFile, sorted by title."
546
+ }
547
+ },
548
+ required: ["schemaVersion", "runId", "startedAtMs", "finishedAtMs", "durationMs", "projectRoot", "summary", "features"],
549
+ additionalProperties: false
550
+ },
551
+ Summary: {
552
+ type: "object",
553
+ description: "Counts by status. Sums equal 'total'.",
554
+ properties: {
555
+ total: { type: "integer", minimum: 0 },
556
+ passed: { type: "integer", minimum: 0 },
557
+ failed: { type: "integer", minimum: 0 },
558
+ skipped: { type: "integer", minimum: 0 },
559
+ pending: { type: "integer", minimum: 0 },
560
+ durationMs: { type: "number", minimum: 0 }
561
+ },
562
+ required: ["total", "passed", "failed", "skipped", "pending", "durationMs"],
563
+ additionalProperties: false
564
+ },
565
+ Feature: {
566
+ type: "object",
567
+ description: "A group of scenarios from the same source file.",
568
+ properties: {
569
+ id: {
570
+ type: "string",
571
+ minLength: 1,
572
+ description: "Stable slug derived from the relative source path. Suitable for use as a deep-link anchor."
573
+ },
574
+ title: {
575
+ type: "string",
576
+ minLength: 1,
577
+ description: "Display title. Derived from describe block when present, otherwise the file basename."
578
+ },
579
+ sourceFile: {
580
+ type: "string",
581
+ minLength: 1,
582
+ description: "Source path, relative to projectRoot when possible."
583
+ },
584
+ summary: { $ref: "#/$defs/Summary" },
585
+ scenarios: {
586
+ type: "array",
587
+ items: { $ref: "#/$defs/Scenario" },
588
+ description: "Scenarios in this feature, in declaration order."
589
+ }
590
+ },
591
+ required: ["id", "title", "sourceFile", "summary", "scenarios"],
592
+ additionalProperties: false
593
+ },
594
+ Scenario: {
595
+ type: "object",
596
+ description: "A single scenario with its steps, story-level doc entries, and attachments.",
597
+ properties: {
598
+ id: {
599
+ type: "string",
600
+ minLength: 1,
601
+ description: "Stable identifier: '<feature.id>--<slug-of-title>'. Suitable for use as a deep-link anchor."
602
+ },
603
+ title: { type: "string", minLength: 1 },
604
+ status: { $ref: "#/$defs/TestStatus" },
605
+ durationMs: { type: "number", minimum: 0 },
606
+ tags: {
607
+ type: "array",
608
+ items: { type: "string" },
609
+ description: "Normalized tags, deduplicated, lowercased."
610
+ },
611
+ tickets: {
612
+ type: "array",
613
+ items: { $ref: "#/$defs/Ticket" }
614
+ },
615
+ sourceLine: { type: "integer", minimum: 1 },
616
+ errorMessage: { type: "string" },
617
+ errorStack: { type: "string" },
618
+ retry: { type: "integer", minimum: 0 },
619
+ retries: { type: "integer", minimum: 0 },
620
+ docEntries: {
621
+ type: "array",
622
+ items: { $ref: "#/$defs/DocEntry" },
623
+ description: "Story-level doc entries (rendered before steps)."
624
+ },
625
+ steps: {
626
+ type: "array",
627
+ items: { $ref: "#/$defs/Step" }
628
+ },
629
+ attachments: {
630
+ type: "array",
631
+ items: { $ref: "#/$defs/Attachment" }
632
+ }
633
+ },
634
+ required: ["id", "title", "status", "durationMs", "tags", "retry", "retries", "docEntries", "steps", "attachments"],
635
+ additionalProperties: false
636
+ },
637
+ Step: {
638
+ type: "object",
639
+ description: "A single BDD step.",
640
+ properties: {
641
+ id: { type: "string", minLength: 1 },
642
+ index: { type: "integer", minimum: 0 },
643
+ keyword: { $ref: "#/$defs/StepKeyword" },
644
+ text: { type: "string" },
645
+ status: { $ref: "#/$defs/TestStatus" },
646
+ durationMs: { type: "number", minimum: 0 },
647
+ errorMessage: { type: "string" },
648
+ mode: { $ref: "#/$defs/StepMode" },
649
+ docEntries: {
650
+ type: "array",
651
+ items: { $ref: "#/$defs/DocEntry" },
652
+ description: "Doc entries attached to this step."
653
+ }
654
+ },
655
+ required: ["id", "index", "keyword", "text", "status", "durationMs", "docEntries"],
656
+ additionalProperties: false
657
+ },
658
+ StepKeyword: {
659
+ type: "string",
660
+ enum: ["Given", "When", "Then", "And", "But"]
661
+ },
662
+ StepMode: {
663
+ type: "string",
664
+ enum: ["normal", "skip", "only", "todo", "fails", "concurrent"]
665
+ },
666
+ TestStatus: {
667
+ type: "string",
668
+ enum: ["passed", "failed", "skipped", "pending"]
669
+ },
670
+ Ticket: {
671
+ type: "object",
672
+ properties: {
673
+ id: { type: "string", minLength: 1 },
674
+ url: { type: "string" }
675
+ },
676
+ required: ["id"],
677
+ additionalProperties: false
678
+ },
679
+ Attachment: {
680
+ type: "object",
681
+ properties: {
682
+ name: { type: "string" },
683
+ mediaType: { type: "string", minLength: 1 },
684
+ body: { type: "string" },
685
+ contentEncoding: {
686
+ type: "string",
687
+ enum: ["BASE64", "IDENTITY"]
688
+ }
689
+ },
690
+ required: ["name", "mediaType", "body", "contentEncoding"],
691
+ additionalProperties: false
692
+ },
693
+ CIInfo: {
694
+ type: "object",
695
+ properties: {
696
+ name: { type: "string" },
697
+ url: { type: "string" },
698
+ buildNumber: { type: "string" },
699
+ branch: { type: "string" },
700
+ commitSha: { type: "string" },
701
+ prNumber: { type: "string" }
702
+ },
703
+ required: ["name"],
704
+ additionalProperties: false
705
+ },
706
+ CoverageSummary: {
707
+ type: "object",
708
+ properties: {
709
+ linesPct: { type: "number", minimum: 0, maximum: 100 },
710
+ branchesPct: { type: "number", minimum: 0, maximum: 100 },
711
+ functionsPct: { type: "number", minimum: 0, maximum: 100 },
712
+ statementsPct: { type: "number", minimum: 0, maximum: 100 }
713
+ },
714
+ additionalProperties: false
715
+ },
716
+ DocPhase: {
717
+ type: "string",
718
+ enum: ["static", "runtime"]
719
+ },
720
+ DocEntry: {
721
+ oneOf: [
722
+ { $ref: "#/$defs/DocNote" },
723
+ { $ref: "#/$defs/DocTag" },
724
+ { $ref: "#/$defs/DocKv" },
725
+ { $ref: "#/$defs/DocCode" },
726
+ { $ref: "#/$defs/DocTable" },
727
+ { $ref: "#/$defs/DocLink" },
728
+ { $ref: "#/$defs/DocSection" },
729
+ { $ref: "#/$defs/DocMermaid" },
730
+ { $ref: "#/$defs/DocScreenshot" },
731
+ { $ref: "#/$defs/DocCustom" }
732
+ ]
733
+ },
734
+ DocNote: {
735
+ type: "object",
736
+ properties: {
737
+ kind: { const: "note" },
738
+ text: { type: "string" },
739
+ phase: { $ref: "#/$defs/DocPhase" },
740
+ children: {
741
+ type: "array",
742
+ items: { $ref: "#/$defs/DocEntry" }
743
+ }
744
+ },
745
+ required: ["kind", "text", "phase"],
746
+ additionalProperties: false
747
+ },
748
+ DocTag: {
749
+ type: "object",
750
+ properties: {
751
+ kind: { const: "tag" },
752
+ names: { type: "array", items: { type: "string" } },
753
+ phase: { $ref: "#/$defs/DocPhase" },
754
+ children: {
755
+ type: "array",
756
+ items: { $ref: "#/$defs/DocEntry" }
757
+ }
758
+ },
759
+ required: ["kind", "names", "phase"],
760
+ additionalProperties: false
761
+ },
762
+ DocKv: {
763
+ type: "object",
764
+ properties: {
765
+ kind: { const: "kv" },
766
+ label: { type: "string" },
767
+ value: {},
768
+ phase: { $ref: "#/$defs/DocPhase" },
769
+ children: {
770
+ type: "array",
771
+ items: { $ref: "#/$defs/DocEntry" }
772
+ }
773
+ },
774
+ required: ["kind", "label", "value", "phase"],
775
+ additionalProperties: false
776
+ },
777
+ DocCode: {
778
+ type: "object",
779
+ properties: {
780
+ kind: { const: "code" },
781
+ label: { type: "string" },
782
+ content: { type: "string" },
783
+ lang: { type: "string" },
784
+ phase: { $ref: "#/$defs/DocPhase" },
785
+ children: {
786
+ type: "array",
787
+ items: { $ref: "#/$defs/DocEntry" }
788
+ }
789
+ },
790
+ required: ["kind", "label", "content", "phase"],
791
+ additionalProperties: false
792
+ },
793
+ DocTable: {
794
+ type: "object",
795
+ properties: {
796
+ kind: { const: "table" },
797
+ label: { type: "string" },
798
+ columns: { type: "array", items: { type: "string" } },
799
+ rows: {
800
+ type: "array",
801
+ items: {
802
+ type: "array",
803
+ items: { type: "string" }
804
+ }
805
+ },
806
+ phase: { $ref: "#/$defs/DocPhase" },
807
+ children: {
808
+ type: "array",
809
+ items: { $ref: "#/$defs/DocEntry" }
810
+ }
811
+ },
812
+ required: ["kind", "label", "columns", "rows", "phase"],
813
+ additionalProperties: false
814
+ },
815
+ DocLink: {
816
+ type: "object",
817
+ properties: {
818
+ kind: { const: "link" },
819
+ label: { type: "string" },
820
+ url: { type: "string" },
821
+ phase: { $ref: "#/$defs/DocPhase" },
822
+ children: {
823
+ type: "array",
824
+ items: { $ref: "#/$defs/DocEntry" }
825
+ }
826
+ },
827
+ required: ["kind", "label", "url", "phase"],
828
+ additionalProperties: false
829
+ },
830
+ DocSection: {
831
+ type: "object",
832
+ properties: {
833
+ kind: { const: "section" },
834
+ title: { type: "string" },
835
+ markdown: { type: "string" },
836
+ phase: { $ref: "#/$defs/DocPhase" },
837
+ children: {
838
+ type: "array",
839
+ items: { $ref: "#/$defs/DocEntry" }
840
+ }
841
+ },
842
+ required: ["kind", "title", "markdown", "phase"],
843
+ additionalProperties: false
844
+ },
845
+ DocMermaid: {
846
+ type: "object",
847
+ properties: {
848
+ kind: { const: "mermaid" },
849
+ code: { type: "string" },
850
+ title: { type: "string" },
851
+ phase: { $ref: "#/$defs/DocPhase" },
852
+ children: {
853
+ type: "array",
854
+ items: { $ref: "#/$defs/DocEntry" }
855
+ }
856
+ },
857
+ required: ["kind", "code", "phase"],
858
+ additionalProperties: false
859
+ },
860
+ DocScreenshot: {
861
+ type: "object",
862
+ properties: {
863
+ kind: { const: "screenshot" },
864
+ path: { type: "string" },
865
+ alt: { type: "string" },
866
+ phase: { $ref: "#/$defs/DocPhase" },
867
+ children: {
868
+ type: "array",
869
+ items: { $ref: "#/$defs/DocEntry" }
870
+ }
871
+ },
872
+ required: ["kind", "path", "phase"],
873
+ additionalProperties: false
874
+ },
875
+ DocCustom: {
876
+ type: "object",
877
+ properties: {
878
+ kind: { const: "custom" },
879
+ type: { type: "string", minLength: 1 },
880
+ data: {},
881
+ phase: { $ref: "#/$defs/DocPhase" },
882
+ children: {
883
+ type: "array",
884
+ items: { $ref: "#/$defs/DocEntry" }
885
+ }
886
+ },
887
+ required: ["kind", "type", "data", "phase"],
888
+ additionalProperties: false
889
+ }
890
+ }
891
+ };
892
+
893
+ // src/schema/story-report.schema.ts
894
+ var compiled = z.fromJSONSchema(story_report_v1_default);
895
+ var storyReportSchema = compiled;
896
+ var STORY_REPORT_SCHEMA_MAJOR = 1;
897
+
898
+ // src/schema/parse.ts
899
+ function parseStoryReport(input) {
900
+ if (input === null || typeof input !== "object") {
901
+ return err({
902
+ message: "Expected a StoryReport object.",
903
+ code: "INVALID_INPUT"
904
+ });
905
+ }
906
+ const versionRaw = input.schemaVersion;
907
+ if (typeof versionRaw === "string") {
908
+ const major = versionRaw.split(".")[0];
909
+ if (major !== String(STORY_REPORT_SCHEMA_MAJOR)) {
910
+ return err({
911
+ message: `Schema major ${major} is not supported by this version of executable-stories-react (expected ${STORY_REPORT_SCHEMA_MAJOR}.x). Upgrade the package.`,
912
+ code: "SCHEMA_VERSION_MISMATCH"
913
+ });
914
+ }
915
+ }
916
+ const parsed = storyReportSchema.safeParse(input);
917
+ if (parsed.success) {
918
+ return ok(parsed.data);
919
+ }
920
+ const issues = parsed.error.issues.map((i) => ({
921
+ path: i.path.join(".") || "/",
922
+ message: i.message
923
+ }));
924
+ return err({
925
+ message: `StoryReport failed validation (${issues.length} issue${issues.length === 1 ? "" : "s"}).`,
926
+ code: "VALIDATION_FAILED",
927
+ issues
928
+ });
929
+ }
930
+ export {
931
+ DocCode,
932
+ DocCustom,
933
+ DocEntry,
934
+ DocKv,
935
+ DocLink,
936
+ DocMermaid,
937
+ DocNote,
938
+ DocScreenshot,
939
+ DocSection,
940
+ DocTable,
941
+ DocTag,
942
+ Report,
943
+ ReportDocEntries,
944
+ ReportEmpty,
945
+ ReportFeature,
946
+ ReportFeatureList,
947
+ ReportRoot,
948
+ ReportScenario,
949
+ ReportScenarioList,
950
+ ReportSchemaError,
951
+ ReportStepItem,
952
+ ReportSteps,
953
+ ReportSummary,
954
+ ReportSummaryView,
955
+ STORY_REPORT_SCHEMA_MAJOR,
956
+ err,
957
+ ok,
958
+ parseStoryReport,
959
+ storyReportSchema,
960
+ useBuiltinRenderers,
961
+ useCustomRenderers,
962
+ useReport
963
+ };
964
+ //# sourceMappingURL=index.js.map