executable-stories-formatters 0.13.0 → 0.15.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/cli.js +706 -236
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +49 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +49 -52
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.cts
CHANGED
|
@@ -360,6 +360,12 @@ interface MarkdownFormatterOptions {
|
|
|
360
360
|
includeSourceLinks?: boolean;
|
|
361
361
|
/** Custom renderers for doc entries */
|
|
362
362
|
customRenderers?: MarkdownRenderers;
|
|
363
|
+
/** Emit a stable per-scenario anchor for deep-linking. Returns the id, or undefined to skip. */
|
|
364
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
365
|
+
/** Render a badge line under a scenario heading (e.g. a what's-changed marker). Undefined to skip. */
|
|
366
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
367
|
+
/** Render a business-context link line under a scenario heading. Undefined to skip. */
|
|
368
|
+
scenarioNoteLink?: (tc: TestCaseResult) => string | undefined;
|
|
363
369
|
}
|
|
364
370
|
|
|
365
371
|
/** Astro/Starlight formatter options */
|
|
@@ -487,6 +493,9 @@ interface ResolvedFormatterOptions {
|
|
|
487
493
|
ticketUrlTemplate?: string;
|
|
488
494
|
traceUrlTemplate?: string;
|
|
489
495
|
customRenderers?: MarkdownRenderers;
|
|
496
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
497
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
498
|
+
scenarioNoteLink?: (tc: TestCaseResult) => string | undefined;
|
|
490
499
|
};
|
|
491
500
|
};
|
|
492
501
|
assetMode: "none" | "copy";
|
|
@@ -1924,6 +1933,25 @@ interface MarkdownOptions {
|
|
|
1924
1933
|
includeSourceLinks?: boolean;
|
|
1925
1934
|
/** Custom renderers for doc entries */
|
|
1926
1935
|
customRenderers?: MarkdownRenderers;
|
|
1936
|
+
/**
|
|
1937
|
+
* Emit a stable in-page anchor before each scenario heading, so external tools
|
|
1938
|
+
* can deep-link to a scenario by fragment. Given a test case, return the anchor
|
|
1939
|
+
* id (without `#`), or undefined to skip. Off by default — only the living-docs
|
|
1940
|
+
* site opts in, so plain markdown output is unchanged.
|
|
1941
|
+
*/
|
|
1942
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
1943
|
+
/**
|
|
1944
|
+
* Render a short badge line directly under a scenario heading (e.g. a
|
|
1945
|
+
* what's-changed marker like "🆕 New" / "⚠️ Regressed"). Return the markdown
|
|
1946
|
+
* line, or undefined to skip. Off by default — only the living-docs site opts in.
|
|
1947
|
+
*/
|
|
1948
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
1949
|
+
/**
|
|
1950
|
+
* Render a link line under a scenario heading pointing at hand-written context
|
|
1951
|
+
* (the scenario's note page). Return the markdown line, or undefined to skip.
|
|
1952
|
+
* Off by default — only the living-docs site opts in.
|
|
1953
|
+
*/
|
|
1954
|
+
scenarioNoteLink?: (tc: TestCaseResult) => string | undefined;
|
|
1927
1955
|
}
|
|
1928
1956
|
/**
|
|
1929
1957
|
* Markdown Formatter.
|
package/dist/index.d.ts
CHANGED
|
@@ -360,6 +360,12 @@ interface MarkdownFormatterOptions {
|
|
|
360
360
|
includeSourceLinks?: boolean;
|
|
361
361
|
/** Custom renderers for doc entries */
|
|
362
362
|
customRenderers?: MarkdownRenderers;
|
|
363
|
+
/** Emit a stable per-scenario anchor for deep-linking. Returns the id, or undefined to skip. */
|
|
364
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
365
|
+
/** Render a badge line under a scenario heading (e.g. a what's-changed marker). Undefined to skip. */
|
|
366
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
367
|
+
/** Render a business-context link line under a scenario heading. Undefined to skip. */
|
|
368
|
+
scenarioNoteLink?: (tc: TestCaseResult) => string | undefined;
|
|
363
369
|
}
|
|
364
370
|
|
|
365
371
|
/** Astro/Starlight formatter options */
|
|
@@ -487,6 +493,9 @@ interface ResolvedFormatterOptions {
|
|
|
487
493
|
ticketUrlTemplate?: string;
|
|
488
494
|
traceUrlTemplate?: string;
|
|
489
495
|
customRenderers?: MarkdownRenderers;
|
|
496
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
497
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
498
|
+
scenarioNoteLink?: (tc: TestCaseResult) => string | undefined;
|
|
490
499
|
};
|
|
491
500
|
};
|
|
492
501
|
assetMode: "none" | "copy";
|
|
@@ -1924,6 +1933,25 @@ interface MarkdownOptions {
|
|
|
1924
1933
|
includeSourceLinks?: boolean;
|
|
1925
1934
|
/** Custom renderers for doc entries */
|
|
1926
1935
|
customRenderers?: MarkdownRenderers;
|
|
1936
|
+
/**
|
|
1937
|
+
* Emit a stable in-page anchor before each scenario heading, so external tools
|
|
1938
|
+
* can deep-link to a scenario by fragment. Given a test case, return the anchor
|
|
1939
|
+
* id (without `#`), or undefined to skip. Off by default — only the living-docs
|
|
1940
|
+
* site opts in, so plain markdown output is unchanged.
|
|
1941
|
+
*/
|
|
1942
|
+
scenarioAnchor?: (tc: TestCaseResult) => string | undefined;
|
|
1943
|
+
/**
|
|
1944
|
+
* Render a short badge line directly under a scenario heading (e.g. a
|
|
1945
|
+
* what's-changed marker like "🆕 New" / "⚠️ Regressed"). Return the markdown
|
|
1946
|
+
* line, or undefined to skip. Off by default — only the living-docs site opts in.
|
|
1947
|
+
*/
|
|
1948
|
+
scenarioBadge?: (tc: TestCaseResult) => string | undefined;
|
|
1949
|
+
/**
|
|
1950
|
+
* Render a link line under a scenario heading pointing at hand-written context
|
|
1951
|
+
* (the scenario's note page). Return the markdown line, or undefined to skip.
|
|
1952
|
+
* Off by default — only the living-docs site opts in.
|
|
1953
|
+
*/
|
|
1954
|
+
scenarioNoteLink?: (tc: TestCaseResult) => string | undefined;
|
|
1927
1955
|
}
|
|
1928
1956
|
/**
|
|
1929
1957
|
* Markdown Formatter.
|
package/dist/index.js
CHANGED
|
@@ -15124,7 +15124,10 @@ var MarkdownFormatter = class {
|
|
|
15124
15124
|
ticketUrlTemplate: options.ticketUrlTemplate,
|
|
15125
15125
|
traceUrlTemplate: options.traceUrlTemplate,
|
|
15126
15126
|
includeSourceLinks: options.includeSourceLinks ?? true,
|
|
15127
|
-
customRenderers: options.customRenderers
|
|
15127
|
+
customRenderers: options.customRenderers,
|
|
15128
|
+
scenarioAnchor: options.scenarioAnchor,
|
|
15129
|
+
scenarioBadge: options.scenarioBadge,
|
|
15130
|
+
scenarioNoteLink: options.scenarioNoteLink
|
|
15128
15131
|
};
|
|
15129
15132
|
}
|
|
15130
15133
|
/**
|
|
@@ -15311,6 +15314,11 @@ var MarkdownFormatter = class {
|
|
|
15311
15314
|
* Render a single scenario.
|
|
15312
15315
|
*/
|
|
15313
15316
|
renderScenario(lines, tc) {
|
|
15317
|
+
const anchorId = this.options.scenarioAnchor?.(tc);
|
|
15318
|
+
if (anchorId) {
|
|
15319
|
+
lines.push(`<a id="${anchorId}"></a>`);
|
|
15320
|
+
lines.push("");
|
|
15321
|
+
}
|
|
15314
15322
|
if (this.options.customRenderers?.renderScenarioHeader) {
|
|
15315
15323
|
const custom = this.options.customRenderers.renderScenarioHeader(tc);
|
|
15316
15324
|
if (custom !== null) {
|
|
@@ -15326,6 +15334,14 @@ var MarkdownFormatter = class {
|
|
|
15326
15334
|
icon = this.getStatusIcon(tc.status) + " ";
|
|
15327
15335
|
}
|
|
15328
15336
|
lines.push(`${headingPrefix} ${icon}${tc.story.scenario}`);
|
|
15337
|
+
const badge = this.options.scenarioBadge?.(tc);
|
|
15338
|
+
if (badge) {
|
|
15339
|
+
lines.push(badge);
|
|
15340
|
+
}
|
|
15341
|
+
const noteLink = this.options.scenarioNoteLink?.(tc);
|
|
15342
|
+
if (noteLink) {
|
|
15343
|
+
lines.push(noteLink);
|
|
15344
|
+
}
|
|
15329
15345
|
if (this.options.includeSourceLinks && this.options.permalinkBaseUrl && tc.sourceFile !== "unknown") {
|
|
15330
15346
|
const permalink = this.buildPermalink(tc);
|
|
15331
15347
|
lines.push(`Source: [${tc.sourceFile}](${permalink})`);
|
|
@@ -18142,12 +18158,16 @@ function groupBy7(items, keyFn) {
|
|
|
18142
18158
|
import * as fs5 from "fs";
|
|
18143
18159
|
import * as path6 from "path";
|
|
18144
18160
|
var SKIP_PREFIXES = ["http://", "https://", "data:", "#"];
|
|
18145
|
-
function
|
|
18161
|
+
function isRemoteRef(src) {
|
|
18146
18162
|
const trimmed = src.trim();
|
|
18147
|
-
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18163
|
+
return SKIP_PREFIXES.some((prefix) => trimmed.startsWith(prefix));
|
|
18164
|
+
}
|
|
18165
|
+
function isAbsoluteRef(src) {
|
|
18166
|
+
const trimmed = src.trim();
|
|
18167
|
+
return path6.posix.isAbsolute(trimmed) || path6.win32.isAbsolute(trimmed);
|
|
18168
|
+
}
|
|
18169
|
+
function isRelativeLocalPath(src) {
|
|
18170
|
+
return !isRemoteRef(src) && !isAbsoluteRef(src);
|
|
18151
18171
|
}
|
|
18152
18172
|
function stripCodeContent(markdown) {
|
|
18153
18173
|
let result = markdown.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1\s*$/gm, "");
|
|
@@ -18163,21 +18183,21 @@ function scanMarkdownAssets(markdown) {
|
|
|
18163
18183
|
let match;
|
|
18164
18184
|
while ((match = mdImageRe.exec(stripped)) !== null) {
|
|
18165
18185
|
const src = match[1].trim();
|
|
18166
|
-
if (
|
|
18186
|
+
if (!isRemoteRef(src)) {
|
|
18167
18187
|
found.add(src);
|
|
18168
18188
|
}
|
|
18169
18189
|
}
|
|
18170
18190
|
const htmlSrcRe = /<(?:img|source|video)[^>]+\bsrc=["']([^"']+)["'][^>]*>/gi;
|
|
18171
18191
|
while ((match = htmlSrcRe.exec(stripped)) !== null) {
|
|
18172
18192
|
const src = match[1].trim();
|
|
18173
|
-
if (
|
|
18193
|
+
if (!isRemoteRef(src)) {
|
|
18174
18194
|
found.add(src);
|
|
18175
18195
|
}
|
|
18176
18196
|
}
|
|
18177
18197
|
const posterRe = /<video[^>]+\bposter=["']([^"']+)["'][^>]*>/gi;
|
|
18178
18198
|
while ((match = posterRe.exec(stripped)) !== null) {
|
|
18179
18199
|
const src = match[1].trim();
|
|
18180
|
-
if (
|
|
18200
|
+
if (!isRemoteRef(src)) {
|
|
18181
18201
|
found.add(src);
|
|
18182
18202
|
}
|
|
18183
18203
|
}
|
|
@@ -18203,48 +18223,21 @@ function isCode(segment) {
|
|
|
18203
18223
|
const trimmed = segment.trimStart();
|
|
18204
18224
|
return trimmed.startsWith("`") || trimmed.startsWith("~") || trimmed.startsWith("<pre") || trimmed.startsWith("<code");
|
|
18205
18225
|
}
|
|
18226
|
+
function resolveRewrite(trimmed, assetsBaseUrl, pathMap) {
|
|
18227
|
+
if (isRemoteRef(trimmed)) return null;
|
|
18228
|
+
if (pathMap) {
|
|
18229
|
+
const mapped = pathMap.get(trimmed);
|
|
18230
|
+
return mapped === void 0 ? null : `${assetsBaseUrl}/${mapped}`;
|
|
18231
|
+
}
|
|
18232
|
+
if (!isRelativeLocalPath(trimmed)) return null;
|
|
18233
|
+
return `${assetsBaseUrl}/${trimmed}`;
|
|
18234
|
+
}
|
|
18206
18235
|
function rewriteProseSegment(prose, assetsBaseUrl, pathMap) {
|
|
18207
|
-
|
|
18208
|
-
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18213
|
-
if (pathMap) {
|
|
18214
|
-
const mapped = pathMap.get(trimmed);
|
|
18215
|
-
if (mapped === void 0) return full;
|
|
18216
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18217
|
-
}
|
|
18218
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18219
|
-
}
|
|
18220
|
-
);
|
|
18221
|
-
result = result.replace(
|
|
18222
|
-
/(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi,
|
|
18223
|
-
(full, pre, src, post) => {
|
|
18224
|
-
const trimmed = src.trim();
|
|
18225
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18226
|
-
if (pathMap) {
|
|
18227
|
-
const mapped = pathMap.get(trimmed);
|
|
18228
|
-
if (mapped === void 0) return full;
|
|
18229
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18230
|
-
}
|
|
18231
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18232
|
-
}
|
|
18233
|
-
);
|
|
18234
|
-
result = result.replace(
|
|
18235
|
-
/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi,
|
|
18236
|
-
(full, pre, src, post) => {
|
|
18237
|
-
const trimmed = src.trim();
|
|
18238
|
-
if (!isLocalPath(trimmed)) return full;
|
|
18239
|
-
if (pathMap) {
|
|
18240
|
-
const mapped = pathMap.get(trimmed);
|
|
18241
|
-
if (mapped === void 0) return full;
|
|
18242
|
-
return `${pre}${assetsBaseUrl}/${mapped}${post}`;
|
|
18243
|
-
}
|
|
18244
|
-
return `${pre}${assetsBaseUrl}/${trimmed}${post}`;
|
|
18245
|
-
}
|
|
18246
|
-
);
|
|
18247
|
-
return result;
|
|
18236
|
+
const rewrite = (full, pre, src, post) => {
|
|
18237
|
+
const target = resolveRewrite(src.trim(), assetsBaseUrl, pathMap);
|
|
18238
|
+
return target === null ? full : `${pre}${target}${post}`;
|
|
18239
|
+
};
|
|
18240
|
+
return prose.replace(/(!\[[^\]]*\]\()([^)"'\s]+)((?:\s+["'][^"']*["'])?\s*\))/g, rewrite).replace(/(<(?:img|source|video)[^>]+\bsrc=["'])([^"']+)(["'][^>]*>)/gi, rewrite).replace(/(<video[^>]+\bposter=["'])([^"']+)(["'][^>]*>)/gi, rewrite);
|
|
18248
18241
|
}
|
|
18249
18242
|
function rewriteAssetPaths(markdown, assetsBaseUrl, pathMap) {
|
|
18250
18243
|
return splitByCode(markdown).map((seg) => isCode(seg) ? seg : rewriteProseSegment(seg, assetsBaseUrl, pathMap)).join("");
|
|
@@ -18261,8 +18254,9 @@ function copyMarkdownAssets(options) {
|
|
|
18261
18254
|
const pathMap = /* @__PURE__ */ new Map();
|
|
18262
18255
|
const missing = [];
|
|
18263
18256
|
for (const ref of refs) {
|
|
18264
|
-
const absPath = path6.resolve(markdownDir, ref);
|
|
18257
|
+
const absPath = isAbsoluteRef(ref) ? ref : path6.resolve(markdownDir, ref);
|
|
18265
18258
|
if (!fs5.existsSync(absPath)) {
|
|
18259
|
+
if (isAbsoluteRef(ref)) continue;
|
|
18266
18260
|
if (!allowMissing) {
|
|
18267
18261
|
throw new Error(`Asset not found: ${absPath}`);
|
|
18268
18262
|
}
|
|
@@ -21888,7 +21882,10 @@ var ReportGenerator = class {
|
|
|
21888
21882
|
permalinkBaseUrl: options.astro?.markdown?.permalinkBaseUrl,
|
|
21889
21883
|
ticketUrlTemplate: options.astro?.markdown?.ticketUrlTemplate,
|
|
21890
21884
|
traceUrlTemplate: options.astro?.markdown?.traceUrlTemplate,
|
|
21891
|
-
customRenderers: options.astro?.markdown?.customRenderers
|
|
21885
|
+
customRenderers: options.astro?.markdown?.customRenderers,
|
|
21886
|
+
scenarioAnchor: options.astro?.markdown?.scenarioAnchor,
|
|
21887
|
+
scenarioBadge: options.astro?.markdown?.scenarioBadge,
|
|
21888
|
+
scenarioNoteLink: options.astro?.markdown?.scenarioNoteLink
|
|
21892
21889
|
}
|
|
21893
21890
|
},
|
|
21894
21891
|
assetMode: options.assetMode ?? "none",
|