executable-stories-react 0.5.0 → 0.6.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.
@@ -202,7 +202,7 @@ function DocTable({ entry }) {
202
202
  entry.label ? /* @__PURE__ */ jsx6("figcaption", { className: "mb-1.5 text-xs font-medium text-muted-foreground", children: entry.label }) : null,
203
203
  /* @__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: [
204
204
  /* @__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)) }) }),
205
- /* @__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)) })
205
+ /* @__PURE__ */ jsx6("tbody", { children: entry.rows.map((row, i) => /* @__PURE__ */ jsx6("tr", { children: row.map((cell2, j) => /* @__PURE__ */ jsx6("td", { className: "border-b border-border px-3 py-2 text-foreground", children: cell2 }, j)) }, i)) })
206
206
  ] }) })
207
207
  ] });
208
208
  }
@@ -855,20 +855,118 @@ function useCollapseState(computeDefault) {
855
855
 
856
856
  // src/interactive/scenario-actions.tsx
857
857
  import { createContext as createContext4, useContext as useContext5 } from "react";
858
+
859
+ // ../executable-stories-core/dist/utils/scenario-markdown.js
860
+ function cell(value) {
861
+ return value.replaceAll("|", "\\|").replaceAll("\n", " ");
862
+ }
863
+ function stringify(value) {
864
+ if (typeof value === "string") return value;
865
+ try {
866
+ return JSON.stringify(value, null, 2) ?? String(value);
867
+ } catch {
868
+ return String(value);
869
+ }
870
+ }
871
+ function docEntryToMarkdown(entry) {
872
+ const lines = [];
873
+ switch (entry.kind) {
874
+ case "note":
875
+ lines.push(entry.text);
876
+ break;
877
+ case "tag":
878
+ lines.push(entry.names.map((n) => `\`@${n}\``).join(" "));
879
+ break;
880
+ case "kv":
881
+ lines.push(`**${entry.label}:** ${stringify(entry.value)}`);
882
+ break;
883
+ case "code":
884
+ lines.push(`**${entry.label}**`, "", "```" + (entry.lang ?? ""), entry.content, "```");
885
+ break;
886
+ case "table": {
887
+ lines.push(`**${entry.label}**`, "");
888
+ lines.push(`| ${entry.columns.map(cell).join(" | ")} |`);
889
+ lines.push(`| ${entry.columns.map(() => "---").join(" | ")} |`);
890
+ for (const row of entry.rows) lines.push(`| ${row.map(cell).join(" | ")} |`);
891
+ break;
892
+ }
893
+ case "link":
894
+ lines.push(`[${entry.label}](${entry.url})`);
895
+ break;
896
+ case "section":
897
+ lines.push(`### ${entry.title}`, "", entry.markdown);
898
+ break;
899
+ case "mermaid":
900
+ if (entry.title) lines.push(`**${entry.title}**`, "");
901
+ lines.push("```mermaid", entry.code, "```");
902
+ break;
903
+ case "screenshot":
904
+ lines.push(`![${entry.alt ?? "screenshot"}](${entry.path})`);
905
+ break;
906
+ case "video":
907
+ lines.push(`Video: ${entry.path}${entry.caption ? ` \u2014 ${entry.caption}` : ""}`);
908
+ break;
909
+ case "html": {
910
+ const htmlTitle = entry.title ?? "Embedded HTML";
911
+ if (entry.content) {
912
+ lines.push(`**${htmlTitle}**`, "", "```html", entry.content, "```");
913
+ } else if (entry.url) {
914
+ lines.push(`[${htmlTitle}](${entry.url})`);
915
+ } else {
916
+ lines.push(`Embedded HTML: ${entry.path ?? "(inline)"}`);
917
+ }
918
+ break;
919
+ }
920
+ case "custom":
921
+ lines.push(`**${entry.type}**`, "", "```json", stringify(entry.data), "```");
922
+ break;
923
+ }
924
+ for (const child of entry.children ?? []) {
925
+ lines.push("", ...docEntryToMarkdown(child));
926
+ }
927
+ return lines;
928
+ }
929
+ function stepLines(step, ordinal, full) {
930
+ const status = step.status === "passed" ? "" : ` \u2014 **${step.status}**`;
931
+ const lines = [`${ordinal}. **${step.keyword}** ${step.text}${status}`];
932
+ if (!full) return lines;
933
+ if (step.errorMessage) {
934
+ lines.push(...step.errorMessage.split("\n").map((l) => ` > ${l}`));
935
+ }
936
+ for (const entry of step.docEntries) {
937
+ lines.push("", ...docEntryToMarkdown(entry).map((l) => l === "" ? l : ` ${l}`));
938
+ }
939
+ return lines;
940
+ }
941
+ function scenarioToMarkdown(scenario, options = {}) {
942
+ const { variant = "full", meta = [] } = options;
943
+ const full = variant === "full";
944
+ const lines = full ? [`# ${scenario.title}`, ""] : [`## ${scenario.title} _(${scenario.status})_`, ""];
945
+ if (full && meta.length > 0) lines.push(...meta);
946
+ if (scenario.steps.length > 0) {
947
+ if (full) lines.push("", "## Steps", "");
948
+ scenario.steps.forEach((step, i) => lines.push(...stepLines(step, i + 1, full)));
949
+ }
950
+ if (scenario.errorMessage) {
951
+ if (full) lines.push("", "## Failure", "");
952
+ else lines.push("");
953
+ lines.push("```", scenario.errorMessage.trim(), "```");
954
+ }
955
+ if (full && scenario.docEntries.length > 0) {
956
+ lines.push("", "## Docs", "");
957
+ for (const entry of scenario.docEntries) lines.push(...docEntryToMarkdown(entry), "");
958
+ }
959
+ return lines.join("\n").replaceAll(/\n{3,}/g, "\n\n").trimEnd() + (full ? "\n" : "");
960
+ }
961
+
962
+ // src/interactive/scenario-actions.tsx
858
963
  var ScenarioActionsContext = createContext4(null);
859
964
  var ScenarioActionsProvider = ScenarioActionsContext.Provider;
860
965
  function useScenarioActions() {
861
966
  return useContext5(ScenarioActionsContext);
862
967
  }
863
- function scenarioToMarkdown(scenario) {
864
- const lines = [`## ${scenario.title} _(${scenario.status})_`, ""];
865
- for (const step of scenario.steps) {
866
- lines.push(`- **${step.keyword}** ${step.text}`);
867
- }
868
- if (scenario.errorMessage) {
869
- lines.push("", "```", scenario.errorMessage.trim(), "```");
870
- }
871
- return lines.join("\n");
968
+ function scenarioToMarkdown2(scenario) {
969
+ return scenarioToMarkdown(scenario, { variant: "compact" });
872
970
  }
873
971
  function scenarioToPrompt(scenario) {
874
972
  const lines = [
@@ -2036,6 +2134,33 @@ function listFailures(report) {
2036
2134
  return out;
2037
2135
  }
2038
2136
 
2137
+ // src/interactive/triage-default.ts
2138
+ function needsAttention(status) {
2139
+ return status === "failed";
2140
+ }
2141
+ function defaultCollapsedIds(report) {
2142
+ const collapsed = /* @__PURE__ */ new Set();
2143
+ if (report.summary.failed === 0) return collapsed;
2144
+ for (const feature of report.features) {
2145
+ const featureHasFailure = feature.scenarios.some((s) => needsAttention(s.status));
2146
+ if (!featureHasFailure) collapsed.add(feature.id);
2147
+ for (const scenario of feature.scenarios) {
2148
+ if (!needsAttention(scenario.status)) collapsed.add(scenario.id);
2149
+ }
2150
+ }
2151
+ return collapsed;
2152
+ }
2153
+ function failuresFirst(report) {
2154
+ if (report.summary.failed === 0) return report;
2155
+ const withFailures = [];
2156
+ const rest = [];
2157
+ for (const feature of report.features) {
2158
+ (feature.scenarios.some((s) => needsAttention(s.status)) ? withFailures : rest).push(feature);
2159
+ }
2160
+ if (withFailures.length === 0) return report;
2161
+ return { ...report, features: [...withFailures, ...rest] };
2162
+ }
2163
+
2039
2164
  // src/interactive/ReportFilters.tsx
2040
2165
  import { useState as useState3 } from "react";
2041
2166
  import { jsx as jsx41, jsxs as jsxs26 } from "react/jsx-runtime";
@@ -2326,7 +2451,9 @@ function ReportInteractiveView({
2326
2451
  const deferredQuery = useDeferredValue(query);
2327
2452
  const failures = useMemo3(() => listFailures(report), [report]);
2328
2453
  const filtered = useMemo3(
2329
- () => filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags }),
2454
+ // Features carrying failures float to the top so the first thing on screen
2455
+ // is the thing that needs attention; an all-green run keeps source order.
2456
+ () => failuresFirst(filterReport(report, { query: deferredQuery, status: statusFilter, tags: activeTags })),
2330
2457
  [report, deferredQuery, statusFilter, activeTags]
2331
2458
  );
2332
2459
  const isFiltering = query !== deferredQuery;
@@ -2364,7 +2491,7 @@ function ReportInteractiveView({
2364
2491
  const scenarioActions = useMemo3(
2365
2492
  () => ({
2366
2493
  copyLink: (s) => copy(scenarioPermalink(s), "Link copied"),
2367
- copyMarkdown: (s) => copy(scenarioToMarkdown(s), "Markdown copied"),
2494
+ copyMarkdown: (s) => copy(scenarioToMarkdown2(s), "Markdown copied"),
2368
2495
  copyPrompt: (s) => copy(scenarioToPrompt(s), "Prompt copied")
2369
2496
  }),
2370
2497
  [copy]
@@ -2374,9 +2501,9 @@ function ReportInteractiveView({
2374
2501
  () => report.features.flatMap((f) => [f.id, ...f.scenarios.map((s) => s.id)]),
2375
2502
  [report]
2376
2503
  );
2377
- const collapse = useCollapseState();
2504
+ const collapse = useCollapseState(useCallback4(() => defaultCollapsedIds(report), [report]));
2378
2505
  const collapseAll = useCallback4(() => collapse.collapseAll(allCollapsibleIds), [collapse, allCollapsibleIds]);
2379
- const [expandedAll, setExpandedAll] = useState7(true);
2506
+ const [expandedAll, setExpandedAll] = useState7(() => report.summary.failed === 0);
2380
2507
  const setExpanded = useCallback4(
2381
2508
  (expanded) => {
2382
2509
  if (expanded) collapse.expandAll();