executable-stories-jest 8.8.13 → 8.9.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/reporter.cjs +8 -1
- package/dist/reporter.cjs.map +1 -1
- package/dist/reporter.d.cts +1 -0
- package/dist/reporter.d.ts +1 -0
- package/dist/reporter.js +8 -1
- package/dist/reporter.js.map +1 -1
- package/package.json +3 -3
package/dist/reporter.cjs
CHANGED
|
@@ -36,6 +36,7 @@ module.exports = __toCommonJS(reporter_exports);
|
|
|
36
36
|
var fs = __toESM(require("fs"), 1);
|
|
37
37
|
var path = __toESM(require("path"), 1);
|
|
38
38
|
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
39
|
+
var import_assertion_comparison = require("executable-stories-core/utils/assertion-comparison");
|
|
39
40
|
var import_executable_stories_formatters = require("executable-stories-formatters");
|
|
40
41
|
function toRelativePosix(absolutePath, projectRoot) {
|
|
41
42
|
return path.relative(projectRoot, absolutePath).split(path.sep).join("/");
|
|
@@ -154,7 +155,13 @@ var StoryReporter = class {
|
|
|
154
155
|
sourceLine: Math.max(1, meta.sourceOrder ?? 1),
|
|
155
156
|
status,
|
|
156
157
|
durationMs: matchingTest?.duration ?? 0,
|
|
157
|
-
error: matchingTest?.failureMessages?.length ? {
|
|
158
|
+
error: matchingTest?.failureMessages?.length ? {
|
|
159
|
+
message: matchingTest.failureMessages.join("\n"),
|
|
160
|
+
// Jest sets matcherResult on `expect` failures only.
|
|
161
|
+
...(0, import_assertion_comparison.assertionComparison)(
|
|
162
|
+
matchingTest.failureDetails?.[0]?.matcherResult
|
|
163
|
+
)
|
|
164
|
+
} : void 0,
|
|
158
165
|
attachments: rawAttachments.length > 0 ? rawAttachments : void 0,
|
|
159
166
|
stepEvents: stepEvents.length > 0 ? stepEvents : void 0,
|
|
160
167
|
retry: 0,
|
package/dist/reporter.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Jest reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n *\n * Uses file-based communication: story.init() writes JSON files to\n * .jest-executable-stories/ which this reporter reads.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\n/** Shape of JSON files written by story.init() */\ninterface StoryFileReport {\n testFilePath: string;\n scenarios: (StoryMeta & {\n _attachments?: Array<{\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n stepIndex?: number;\n stepId?: string;\n }>;\n })[];\n}\n\ninterface JestTestResult {\n testFilePath: string;\n testResults: Array<{\n fullName: string;\n title?: string;\n status: \"passed\" | \"failed\" | \"pending\" | \"todo\";\n duration?: number;\n failureMessages?: string[];\n }>;\n}\n\ninterface JestAggregatedResult {\n testResults: JestTestResult[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter {\n private options: StoryReporterOptions;\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n /**\n * Left unknown when Jest hands us no global config. Claiming full coverage\n * without having looked would let a later merge retire scenarios on a guess.\n */\n private runScope: \"full\" | \"filtered\" | undefined;\n\n constructor(globalConfig: unknown, reporterOptions: StoryReporterOptions = {}) {\n this.options = reporterOptions;\n // `jest -t` runs only the matching tests, so a run carrying a pattern\n // reports a subset of each file it touches and must not be read as that\n // file's full contents.\n const config = globalConfig as { testNamePattern?: unknown } | undefined;\n if (config) {\n const pattern = config.testNamePattern;\n const filtered = typeof pattern === \"string\" ? pattern.length > 0 : pattern != null;\n this.runScope = filtered ? \"filtered\" : \"full\";\n }\n }\n\n /** Get the output directory for story JSON files */\n private getOutputDir(): string {\n const baseDir = process.env.JEST_STORY_DOCS_DIR ?? \".jest-executable-stories\";\n return path.resolve(process.cwd(), baseDir);\n }\n\n /** Reset run artifacts (clear previous story files) */\n private resetRunArtifacts(): void {\n try {\n fs.rmSync(this.getOutputDir(), { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /** Read story reports written by story.init() during test execution */\n private readStoryReports(): StoryFileReport[] {\n const outputDir = this.getOutputDir();\n if (!fs.existsSync(outputDir)) return [];\n const files = fg.sync(\"**/*.json\", {\n cwd: outputDir,\n onlyFiles: true,\n absolute: true,\n });\n const reports: StoryFileReport[] = [];\n for (const file of files) {\n try {\n const raw = fs.readFileSync(file, \"utf8\");\n const parsed = JSON.parse(raw) as StoryFileReport;\n if (!parsed?.testFilePath || !Array.isArray(parsed.scenarios)) continue;\n reports.push(parsed);\n } catch {\n // Ignore unreadable or malformed files\n }\n }\n return reports;\n }\n\n onRunStart(): void {\n this.resetRunArtifacts();\n this.startTime = Date.now();\n const root = process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(root);\n this.gitSha = readGitSha(root);\n }\n }\n\n async onRunComplete(\n _testContexts: Set<unknown>,\n results: JestAggregatedResult\n ): Promise<void> {\n const root = process.cwd();\n\n // Build map of test results by file for status lookup\n const fileResults = new Map<string, JestTestResult>();\n for (const testFileResult of results.testResults) {\n fileResults.set(testFileResult.testFilePath, testFileResult);\n }\n\n // Read story data from JSON files written by story.init()\n const reports = this.readStoryReports();\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = [];\n\n // Which tests actually declared a story, per file. A failed test missing\n // from here never got the chance to declare one.\n const declaredByFile = new Map<string, Set<string>>();\n\n for (const report of reports) {\n const fileResult = fileResults.get(report.testFilePath);\n const sourceFile = toRelativePosix(report.testFilePath, root);\n const matchedFullNames = new Set<string>();\n declaredByFile.set(sourceFile, matchedFullNames);\n\n for (const meta of report.scenarios) {\n if (!meta?.scenario) continue;\n\n // Find matching test result\n const matchingTest = fileResult?.testResults.find((test) => {\n const expectedFullName = meta.suitePath\n ? [...meta.suitePath, meta.scenario].join(\" > \")\n : meta.scenario;\n return test.fullName === expectedFullName;\n });\n if (matchingTest) matchedFullNames.add(matchingTest.fullName);\n\n // Map Jest status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n pending: \"pending\",\n todo: \"pending\",\n };\n\n const status = matchingTest\n ? statusMap[matchingTest.status] ?? \"unknown\"\n : \"pass\";\n\n // Map attachments\n const rawAttachments: RawAttachment[] = (meta._attachments ?? []).map((a: NonNullable<typeof meta._attachments>[number]) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: a.body,\n encoding: a.encoding,\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath\n ? [...meta.suitePath, meta.scenario]\n : [meta.scenario],\n story: meta,\n sourceFile,\n sourceLine: Math.max(1, meta.sourceOrder ?? 1),\n status,\n durationMs: matchingTest?.duration ?? 0,\n error: matchingTest?.failureMessages?.length\n ? { message: matchingTest.failureMessages.join(\"\\n\") }\n : undefined,\n attachments: rawAttachments.length > 0 ? rawAttachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n // Bodyless `it.todo(...)` never runs `story.init()`, so it has no story\n // meta — but Jest still reports it with status \"todo\". Emit those as\n // planned scenarios. Only files with story reports reach this loop, so\n // plain (non-story) suites full of todos never leak into the docs.\n for (const test of fileResult?.testResults ?? []) {\n if (test.status !== \"todo\" || matchedFullNames.has(test.fullName)) continue;\n const title = test.title ?? test.fullName;\n matchedFullNames.add(test.fullName);\n rawTestCases.push({\n title,\n titlePath: [title],\n story: { scenario: title, steps: [] },\n sourceFile,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n }\n\n // Every file Jest executed, whether or not it produced a story. Without\n // this a file whose last story was deleted looks like a file that did not\n // run, and keeps its old scenarios for good.\n const coveredSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .map((r) => r.testFilePath)\n .filter((f): f is string => typeof f === \"string\" && f.length > 0)\n .map((f) => toRelativePosix(f, root))\n ),\n ].sort();\n\n // A story that never declared itself — a throwing beforeAll, an import\n // error, a timeout before `story.init()` — is missing because the run\n // broke, not because someone deleted it. Checked per test, not per file:\n // one healthy story in a file does not vouch for its broken siblings.\n const incompleteSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .filter((r) => {\n const declared =\n declaredByFile.get(toRelativePosix(r.testFilePath, root)) ??\n new Set<string>();\n const undeclaredFailure = (r.testResults ?? []).some(\n (t) => t.status === \"failed\" && !declared.has(t.fullName)\n );\n // No tests at all plus a failure means the module never loaded.\n const suiteError =\n Boolean((r as { testExecError?: unknown }).testExecError) ||\n ((r.testResults ?? []).length === 0 &&\n Boolean((r as { failureMessage?: string | null }).failureMessage));\n return undeclaredFailure || suiteError;\n })\n .map((r) => toRelativePosix(r.testFilePath, root))\n ),\n ].sort();\n\n // Nothing ran and nothing was covered: there is genuinely nothing to say.\n if (rawTestCases.length === 0 && coveredSourceFiles.length === 0) return;\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: root,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n ...(this.runScope ? { runScope: this.runScope } : {}),\n ...(coveredSourceFiles.length > 0 ? { coveredSourceFiles } : {}),\n ...(incompleteSourceFiles.length > 0 ? { incompleteSourceFiles } : {}),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(root, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":"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","names":["fg"]}
|
|
1
|
+
{"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Jest reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n *\n * Uses file-based communication: story.init() writes JSON files to\n * .jest-executable-stories/ which this reporter reads.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport fg from \"fast-glob\";\nimport { assertionComparison } from \"executable-stories-core/utils/assertion-comparison\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\n/** Shape of JSON files written by story.init() */\ninterface StoryFileReport {\n testFilePath: string;\n scenarios: (StoryMeta & {\n _attachments?: Array<{\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n stepIndex?: number;\n stepId?: string;\n }>;\n })[];\n}\n\ninterface JestTestResult {\n testFilePath: string;\n testResults: Array<{\n fullName: string;\n title?: string;\n status: \"passed\" | \"failed\" | \"pending\" | \"todo\";\n duration?: number;\n failureMessages?: string[];\n failureDetails?: unknown[];\n }>;\n}\n\ninterface JestAggregatedResult {\n testResults: JestTestResult[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter {\n private options: StoryReporterOptions;\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n /**\n * Left unknown when Jest hands us no global config. Claiming full coverage\n * without having looked would let a later merge retire scenarios on a guess.\n */\n private runScope: \"full\" | \"filtered\" | undefined;\n\n constructor(globalConfig: unknown, reporterOptions: StoryReporterOptions = {}) {\n this.options = reporterOptions;\n // `jest -t` runs only the matching tests, so a run carrying a pattern\n // reports a subset of each file it touches and must not be read as that\n // file's full contents.\n const config = globalConfig as { testNamePattern?: unknown } | undefined;\n if (config) {\n const pattern = config.testNamePattern;\n const filtered = typeof pattern === \"string\" ? pattern.length > 0 : pattern != null;\n this.runScope = filtered ? \"filtered\" : \"full\";\n }\n }\n\n /** Get the output directory for story JSON files */\n private getOutputDir(): string {\n const baseDir = process.env.JEST_STORY_DOCS_DIR ?? \".jest-executable-stories\";\n return path.resolve(process.cwd(), baseDir);\n }\n\n /** Reset run artifacts (clear previous story files) */\n private resetRunArtifacts(): void {\n try {\n fs.rmSync(this.getOutputDir(), { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /** Read story reports written by story.init() during test execution */\n private readStoryReports(): StoryFileReport[] {\n const outputDir = this.getOutputDir();\n if (!fs.existsSync(outputDir)) return [];\n const files = fg.sync(\"**/*.json\", {\n cwd: outputDir,\n onlyFiles: true,\n absolute: true,\n });\n const reports: StoryFileReport[] = [];\n for (const file of files) {\n try {\n const raw = fs.readFileSync(file, \"utf8\");\n const parsed = JSON.parse(raw) as StoryFileReport;\n if (!parsed?.testFilePath || !Array.isArray(parsed.scenarios)) continue;\n reports.push(parsed);\n } catch {\n // Ignore unreadable or malformed files\n }\n }\n return reports;\n }\n\n onRunStart(): void {\n this.resetRunArtifacts();\n this.startTime = Date.now();\n const root = process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(root);\n this.gitSha = readGitSha(root);\n }\n }\n\n async onRunComplete(\n _testContexts: Set<unknown>,\n results: JestAggregatedResult\n ): Promise<void> {\n const root = process.cwd();\n\n // Build map of test results by file for status lookup\n const fileResults = new Map<string, JestTestResult>();\n for (const testFileResult of results.testResults) {\n fileResults.set(testFileResult.testFilePath, testFileResult);\n }\n\n // Read story data from JSON files written by story.init()\n const reports = this.readStoryReports();\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = [];\n\n // Which tests actually declared a story, per file. A failed test missing\n // from here never got the chance to declare one.\n const declaredByFile = new Map<string, Set<string>>();\n\n for (const report of reports) {\n const fileResult = fileResults.get(report.testFilePath);\n const sourceFile = toRelativePosix(report.testFilePath, root);\n const matchedFullNames = new Set<string>();\n declaredByFile.set(sourceFile, matchedFullNames);\n\n for (const meta of report.scenarios) {\n if (!meta?.scenario) continue;\n\n // Find matching test result\n const matchingTest = fileResult?.testResults.find((test) => {\n const expectedFullName = meta.suitePath\n ? [...meta.suitePath, meta.scenario].join(\" > \")\n : meta.scenario;\n return test.fullName === expectedFullName;\n });\n if (matchingTest) matchedFullNames.add(matchingTest.fullName);\n\n // Map Jest status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n pending: \"pending\",\n todo: \"pending\",\n };\n\n const status = matchingTest\n ? statusMap[matchingTest.status] ?? \"unknown\"\n : \"pass\";\n\n // Map attachments\n const rawAttachments: RawAttachment[] = (meta._attachments ?? []).map((a: NonNullable<typeof meta._attachments>[number]) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: a.body,\n encoding: a.encoding,\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath\n ? [...meta.suitePath, meta.scenario]\n : [meta.scenario],\n story: meta,\n sourceFile,\n sourceLine: Math.max(1, meta.sourceOrder ?? 1),\n status,\n durationMs: matchingTest?.duration ?? 0,\n error: matchingTest?.failureMessages?.length\n ? {\n message: matchingTest.failureMessages.join(\"\\n\"),\n // Jest sets matcherResult on `expect` failures only.\n ...assertionComparison(\n (matchingTest.failureDetails?.[0] as { matcherResult?: unknown } | undefined)\n ?.matcherResult,\n ),\n }\n : undefined,\n attachments: rawAttachments.length > 0 ? rawAttachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n // Bodyless `it.todo(...)` never runs `story.init()`, so it has no story\n // meta — but Jest still reports it with status \"todo\". Emit those as\n // planned scenarios. Only files with story reports reach this loop, so\n // plain (non-story) suites full of todos never leak into the docs.\n for (const test of fileResult?.testResults ?? []) {\n if (test.status !== \"todo\" || matchedFullNames.has(test.fullName)) continue;\n const title = test.title ?? test.fullName;\n matchedFullNames.add(test.fullName);\n rawTestCases.push({\n title,\n titlePath: [title],\n story: { scenario: title, steps: [] },\n sourceFile,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n }\n\n // Every file Jest executed, whether or not it produced a story. Without\n // this a file whose last story was deleted looks like a file that did not\n // run, and keeps its old scenarios for good.\n const coveredSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .map((r) => r.testFilePath)\n .filter((f): f is string => typeof f === \"string\" && f.length > 0)\n .map((f) => toRelativePosix(f, root))\n ),\n ].sort();\n\n // A story that never declared itself — a throwing beforeAll, an import\n // error, a timeout before `story.init()` — is missing because the run\n // broke, not because someone deleted it. Checked per test, not per file:\n // one healthy story in a file does not vouch for its broken siblings.\n const incompleteSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .filter((r) => {\n const declared =\n declaredByFile.get(toRelativePosix(r.testFilePath, root)) ??\n new Set<string>();\n const undeclaredFailure = (r.testResults ?? []).some(\n (t) => t.status === \"failed\" && !declared.has(t.fullName)\n );\n // No tests at all plus a failure means the module never loaded.\n const suiteError =\n Boolean((r as { testExecError?: unknown }).testExecError) ||\n ((r.testResults ?? []).length === 0 &&\n Boolean((r as { failureMessage?: string | null }).failureMessage));\n return undeclaredFailure || suiteError;\n })\n .map((r) => toRelativePosix(r.testFilePath, root))\n ),\n ].sort();\n\n // Nothing ran and nothing was covered: there is genuinely nothing to say.\n if (rawTestCases.length === 0 && coveredSourceFiles.length === 0) return;\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: root,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n ...(this.runScope ? { runScope: this.runScope } : {}),\n ...(coveredSourceFiles.length > 0 ? { coveredSourceFiles } : {}),\n ...(incompleteSourceFiles.length > 0 ? { incompleteSourceFiles } : {}),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(root, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":"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","names":["fg"]}
|
package/dist/reporter.d.cts
CHANGED
package/dist/reporter.d.ts
CHANGED
package/dist/reporter.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import fg from "fast-glob";
|
|
5
|
+
import { assertionComparison } from "executable-stories-core/utils/assertion-comparison";
|
|
5
6
|
import {
|
|
6
7
|
ReportGenerator,
|
|
7
8
|
canonicalizeRun,
|
|
@@ -126,7 +127,13 @@ var StoryReporter = class {
|
|
|
126
127
|
sourceLine: Math.max(1, meta.sourceOrder ?? 1),
|
|
127
128
|
status,
|
|
128
129
|
durationMs: matchingTest?.duration ?? 0,
|
|
129
|
-
error: matchingTest?.failureMessages?.length ? {
|
|
130
|
+
error: matchingTest?.failureMessages?.length ? {
|
|
131
|
+
message: matchingTest.failureMessages.join("\n"),
|
|
132
|
+
// Jest sets matcherResult on `expect` failures only.
|
|
133
|
+
...assertionComparison(
|
|
134
|
+
matchingTest.failureDetails?.[0]?.matcherResult
|
|
135
|
+
)
|
|
136
|
+
} : void 0,
|
|
130
137
|
attachments: rawAttachments.length > 0 ? rawAttachments : void 0,
|
|
131
138
|
stepEvents: stepEvents.length > 0 ? stepEvents : void 0,
|
|
132
139
|
retry: 0,
|
package/dist/reporter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Jest reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n *\n * Uses file-based communication: story.init() writes JSON files to\n * .jest-executable-stories/ which this reporter reads.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport fg from \"fast-glob\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\n/** Shape of JSON files written by story.init() */\ninterface StoryFileReport {\n testFilePath: string;\n scenarios: (StoryMeta & {\n _attachments?: Array<{\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n stepIndex?: number;\n stepId?: string;\n }>;\n })[];\n}\n\ninterface JestTestResult {\n testFilePath: string;\n testResults: Array<{\n fullName: string;\n title?: string;\n status: \"passed\" | \"failed\" | \"pending\" | \"todo\";\n duration?: number;\n failureMessages?: string[];\n }>;\n}\n\ninterface JestAggregatedResult {\n testResults: JestTestResult[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter {\n private options: StoryReporterOptions;\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n /**\n * Left unknown when Jest hands us no global config. Claiming full coverage\n * without having looked would let a later merge retire scenarios on a guess.\n */\n private runScope: \"full\" | \"filtered\" | undefined;\n\n constructor(globalConfig: unknown, reporterOptions: StoryReporterOptions = {}) {\n this.options = reporterOptions;\n // `jest -t` runs only the matching tests, so a run carrying a pattern\n // reports a subset of each file it touches and must not be read as that\n // file's full contents.\n const config = globalConfig as { testNamePattern?: unknown } | undefined;\n if (config) {\n const pattern = config.testNamePattern;\n const filtered = typeof pattern === \"string\" ? pattern.length > 0 : pattern != null;\n this.runScope = filtered ? \"filtered\" : \"full\";\n }\n }\n\n /** Get the output directory for story JSON files */\n private getOutputDir(): string {\n const baseDir = process.env.JEST_STORY_DOCS_DIR ?? \".jest-executable-stories\";\n return path.resolve(process.cwd(), baseDir);\n }\n\n /** Reset run artifacts (clear previous story files) */\n private resetRunArtifacts(): void {\n try {\n fs.rmSync(this.getOutputDir(), { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /** Read story reports written by story.init() during test execution */\n private readStoryReports(): StoryFileReport[] {\n const outputDir = this.getOutputDir();\n if (!fs.existsSync(outputDir)) return [];\n const files = fg.sync(\"**/*.json\", {\n cwd: outputDir,\n onlyFiles: true,\n absolute: true,\n });\n const reports: StoryFileReport[] = [];\n for (const file of files) {\n try {\n const raw = fs.readFileSync(file, \"utf8\");\n const parsed = JSON.parse(raw) as StoryFileReport;\n if (!parsed?.testFilePath || !Array.isArray(parsed.scenarios)) continue;\n reports.push(parsed);\n } catch {\n // Ignore unreadable or malformed files\n }\n }\n return reports;\n }\n\n onRunStart(): void {\n this.resetRunArtifacts();\n this.startTime = Date.now();\n const root = process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(root);\n this.gitSha = readGitSha(root);\n }\n }\n\n async onRunComplete(\n _testContexts: Set<unknown>,\n results: JestAggregatedResult\n ): Promise<void> {\n const root = process.cwd();\n\n // Build map of test results by file for status lookup\n const fileResults = new Map<string, JestTestResult>();\n for (const testFileResult of results.testResults) {\n fileResults.set(testFileResult.testFilePath, testFileResult);\n }\n\n // Read story data from JSON files written by story.init()\n const reports = this.readStoryReports();\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = [];\n\n // Which tests actually declared a story, per file. A failed test missing\n // from here never got the chance to declare one.\n const declaredByFile = new Map<string, Set<string>>();\n\n for (const report of reports) {\n const fileResult = fileResults.get(report.testFilePath);\n const sourceFile = toRelativePosix(report.testFilePath, root);\n const matchedFullNames = new Set<string>();\n declaredByFile.set(sourceFile, matchedFullNames);\n\n for (const meta of report.scenarios) {\n if (!meta?.scenario) continue;\n\n // Find matching test result\n const matchingTest = fileResult?.testResults.find((test) => {\n const expectedFullName = meta.suitePath\n ? [...meta.suitePath, meta.scenario].join(\" > \")\n : meta.scenario;\n return test.fullName === expectedFullName;\n });\n if (matchingTest) matchedFullNames.add(matchingTest.fullName);\n\n // Map Jest status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n pending: \"pending\",\n todo: \"pending\",\n };\n\n const status = matchingTest\n ? statusMap[matchingTest.status] ?? \"unknown\"\n : \"pass\";\n\n // Map attachments\n const rawAttachments: RawAttachment[] = (meta._attachments ?? []).map((a: NonNullable<typeof meta._attachments>[number]) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: a.body,\n encoding: a.encoding,\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath\n ? [...meta.suitePath, meta.scenario]\n : [meta.scenario],\n story: meta,\n sourceFile,\n sourceLine: Math.max(1, meta.sourceOrder ?? 1),\n status,\n durationMs: matchingTest?.duration ?? 0,\n error: matchingTest?.failureMessages?.length\n ? { message: matchingTest.failureMessages.join(\"\\n\") }\n : undefined,\n attachments: rawAttachments.length > 0 ? rawAttachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n // Bodyless `it.todo(...)` never runs `story.init()`, so it has no story\n // meta — but Jest still reports it with status \"todo\". Emit those as\n // planned scenarios. Only files with story reports reach this loop, so\n // plain (non-story) suites full of todos never leak into the docs.\n for (const test of fileResult?.testResults ?? []) {\n if (test.status !== \"todo\" || matchedFullNames.has(test.fullName)) continue;\n const title = test.title ?? test.fullName;\n matchedFullNames.add(test.fullName);\n rawTestCases.push({\n title,\n titlePath: [title],\n story: { scenario: title, steps: [] },\n sourceFile,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n }\n\n // Every file Jest executed, whether or not it produced a story. Without\n // this a file whose last story was deleted looks like a file that did not\n // run, and keeps its old scenarios for good.\n const coveredSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .map((r) => r.testFilePath)\n .filter((f): f is string => typeof f === \"string\" && f.length > 0)\n .map((f) => toRelativePosix(f, root))\n ),\n ].sort();\n\n // A story that never declared itself — a throwing beforeAll, an import\n // error, a timeout before `story.init()` — is missing because the run\n // broke, not because someone deleted it. Checked per test, not per file:\n // one healthy story in a file does not vouch for its broken siblings.\n const incompleteSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .filter((r) => {\n const declared =\n declaredByFile.get(toRelativePosix(r.testFilePath, root)) ??\n new Set<string>();\n const undeclaredFailure = (r.testResults ?? []).some(\n (t) => t.status === \"failed\" && !declared.has(t.fullName)\n );\n // No tests at all plus a failure means the module never loaded.\n const suiteError =\n Boolean((r as { testExecError?: unknown }).testExecError) ||\n ((r.testResults ?? []).length === 0 &&\n Boolean((r as { failureMessage?: string | null }).failureMessage));\n return undeclaredFailure || suiteError;\n })\n .map((r) => toRelativePosix(r.testFilePath, root))\n ),\n ].sort();\n\n // Nothing ran and nothing was covered: there is genuinely nothing to say.\n if (rawTestCases.length === 0 && coveredSourceFiles.length === 0) return;\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: root,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n ...(this.runScope ? { runScope: this.runScope } : {}),\n ...(coveredSourceFiles.length > 0 ? { coveredSourceFiles } : {}),\n ...(incompleteSourceFiles.length > 0 ? { incompleteSourceFiles } : {}),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(root, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/reporter.ts"],"sourcesContent":["/**\n * Jest reporter for executable-stories.\n * Generates reports using the executable-stories-formatters package.\n *\n * Uses file-based communication: story.init() writes JSON files to\n * .jest-executable-stories/ which this reporter reads.\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport fg from \"fast-glob\";\nimport { assertionComparison } from \"executable-stories-core/utils/assertion-comparison\";\nimport type { StoryMeta } from \"executable-stories-formatters\";\n\n// Import from formatters package\nimport {\n ReportGenerator,\n canonicalizeRun,\n readGitSha,\n readPackageVersion,\n detectCI,\n type RawRun,\n type RawTestCase,\n type RawAttachment,\n type RawStepEvent,\n type FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// Re-export types from formatters for convenience\nexport type {\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from \"executable-stories-formatters\";\n\n// ============================================================================\n// Reporter Options (delegates to FormatterOptions)\n// ============================================================================\n\nexport interface StoryReporterOptions extends FormatterOptions {\n /** If set, write raw run JSON (schemaVersion 1) to this path for use with the executable-stories CLI/binary */\n rawRunPath?: string;\n}\n\n// ============================================================================\n// Internal Types\n// ============================================================================\n\n/** Shape of JSON files written by story.init() */\ninterface StoryFileReport {\n testFilePath: string;\n scenarios: (StoryMeta & {\n _attachments?: Array<{\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n stepIndex?: number;\n stepId?: string;\n }>;\n })[];\n}\n\ninterface JestTestResult {\n testFilePath: string;\n testResults: Array<{\n fullName: string;\n title?: string;\n status: \"passed\" | \"failed\" | \"pending\" | \"todo\";\n duration?: number;\n failureMessages?: string[];\n failureDetails?: unknown[];\n }>;\n}\n\ninterface JestAggregatedResult {\n testResults: JestTestResult[];\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Convert path to relative posix format.\n */\nfunction toRelativePosix(absolutePath: string, projectRoot: string): string {\n return path.relative(projectRoot, absolutePath).split(path.sep).join(\"/\");\n}\n\n// ============================================================================\n// Reporter Implementation\n// ============================================================================\n\nexport default class StoryReporter {\n private options: StoryReporterOptions;\n private startTime = 0;\n private packageVersion: string | undefined;\n private gitSha: string | undefined;\n /**\n * Left unknown when Jest hands us no global config. Claiming full coverage\n * without having looked would let a later merge retire scenarios on a guess.\n */\n private runScope: \"full\" | \"filtered\" | undefined;\n\n constructor(globalConfig: unknown, reporterOptions: StoryReporterOptions = {}) {\n this.options = reporterOptions;\n // `jest -t` runs only the matching tests, so a run carrying a pattern\n // reports a subset of each file it touches and must not be read as that\n // file's full contents.\n const config = globalConfig as { testNamePattern?: unknown } | undefined;\n if (config) {\n const pattern = config.testNamePattern;\n const filtered = typeof pattern === \"string\" ? pattern.length > 0 : pattern != null;\n this.runScope = filtered ? \"filtered\" : \"full\";\n }\n }\n\n /** Get the output directory for story JSON files */\n private getOutputDir(): string {\n const baseDir = process.env.JEST_STORY_DOCS_DIR ?? \".jest-executable-stories\";\n return path.resolve(process.cwd(), baseDir);\n }\n\n /** Reset run artifacts (clear previous story files) */\n private resetRunArtifacts(): void {\n try {\n fs.rmSync(this.getOutputDir(), { recursive: true, force: true });\n } catch {\n // Ignore cleanup errors\n }\n }\n\n /** Read story reports written by story.init() during test execution */\n private readStoryReports(): StoryFileReport[] {\n const outputDir = this.getOutputDir();\n if (!fs.existsSync(outputDir)) return [];\n const files = fg.sync(\"**/*.json\", {\n cwd: outputDir,\n onlyFiles: true,\n absolute: true,\n });\n const reports: StoryFileReport[] = [];\n for (const file of files) {\n try {\n const raw = fs.readFileSync(file, \"utf8\");\n const parsed = JSON.parse(raw) as StoryFileReport;\n if (!parsed?.testFilePath || !Array.isArray(parsed.scenarios)) continue;\n reports.push(parsed);\n } catch {\n // Ignore unreadable or malformed files\n }\n }\n return reports;\n }\n\n onRunStart(): void {\n this.resetRunArtifacts();\n this.startTime = Date.now();\n const root = process.cwd();\n const includeMetadata = this.options.markdown?.includeMetadata ?? true;\n if (includeMetadata) {\n this.packageVersion = readPackageVersion(root);\n this.gitSha = readGitSha(root);\n }\n }\n\n async onRunComplete(\n _testContexts: Set<unknown>,\n results: JestAggregatedResult\n ): Promise<void> {\n const root = process.cwd();\n\n // Build map of test results by file for status lookup\n const fileResults = new Map<string, JestTestResult>();\n for (const testFileResult of results.testResults) {\n fileResults.set(testFileResult.testFilePath, testFileResult);\n }\n\n // Read story data from JSON files written by story.init()\n const reports = this.readStoryReports();\n\n // Collect test cases\n const rawTestCases: RawTestCase[] = [];\n\n // Which tests actually declared a story, per file. A failed test missing\n // from here never got the chance to declare one.\n const declaredByFile = new Map<string, Set<string>>();\n\n for (const report of reports) {\n const fileResult = fileResults.get(report.testFilePath);\n const sourceFile = toRelativePosix(report.testFilePath, root);\n const matchedFullNames = new Set<string>();\n declaredByFile.set(sourceFile, matchedFullNames);\n\n for (const meta of report.scenarios) {\n if (!meta?.scenario) continue;\n\n // Find matching test result\n const matchingTest = fileResult?.testResults.find((test) => {\n const expectedFullName = meta.suitePath\n ? [...meta.suitePath, meta.scenario].join(\" > \")\n : meta.scenario;\n return test.fullName === expectedFullName;\n });\n if (matchingTest) matchedFullNames.add(matchingTest.fullName);\n\n // Map Jest status to raw status\n const statusMap: Record<string, RawTestCase[\"status\"]> = {\n passed: \"pass\",\n failed: \"fail\",\n pending: \"pending\",\n todo: \"pending\",\n };\n\n const status = matchingTest\n ? statusMap[matchingTest.status] ?? \"unknown\"\n : \"pass\";\n\n // Map attachments\n const rawAttachments: RawAttachment[] = (meta._attachments ?? []).map((a: NonNullable<typeof meta._attachments>[number]) => ({\n name: a.name,\n mediaType: a.mediaType,\n path: a.path,\n body: a.body,\n encoding: a.encoding,\n charset: a.charset,\n fileName: a.fileName,\n stepIndex: a.stepIndex,\n stepId: a.stepId,\n }));\n\n // Extract step events (timing)\n const stepEvents: RawStepEvent[] = meta.steps\n .filter((s: { durationMs?: number }) => s.durationMs !== undefined)\n .map((s: { durationMs?: number; text: string }, i: number) => ({\n index: i,\n title: s.text,\n durationMs: s.durationMs,\n }));\n\n rawTestCases.push({\n title: meta.scenario,\n titlePath: meta.suitePath\n ? [...meta.suitePath, meta.scenario]\n : [meta.scenario],\n story: meta,\n sourceFile,\n sourceLine: Math.max(1, meta.sourceOrder ?? 1),\n status,\n durationMs: matchingTest?.duration ?? 0,\n error: matchingTest?.failureMessages?.length\n ? {\n message: matchingTest.failureMessages.join(\"\\n\"),\n // Jest sets matcherResult on `expect` failures only.\n ...assertionComparison(\n (matchingTest.failureDetails?.[0] as { matcherResult?: unknown } | undefined)\n ?.matcherResult,\n ),\n }\n : undefined,\n attachments: rawAttachments.length > 0 ? rawAttachments : undefined,\n stepEvents: stepEvents.length > 0 ? stepEvents : undefined,\n retry: 0,\n retries: 0,\n });\n }\n\n // Bodyless `it.todo(...)` never runs `story.init()`, so it has no story\n // meta — but Jest still reports it with status \"todo\". Emit those as\n // planned scenarios. Only files with story reports reach this loop, so\n // plain (non-story) suites full of todos never leak into the docs.\n for (const test of fileResult?.testResults ?? []) {\n if (test.status !== \"todo\" || matchedFullNames.has(test.fullName)) continue;\n const title = test.title ?? test.fullName;\n matchedFullNames.add(test.fullName);\n rawTestCases.push({\n title,\n titlePath: [title],\n story: { scenario: title, steps: [] },\n sourceFile,\n sourceLine: 1,\n status: \"todo\",\n durationMs: 0,\n retry: 0,\n retries: 0,\n });\n }\n }\n\n // Every file Jest executed, whether or not it produced a story. Without\n // this a file whose last story was deleted looks like a file that did not\n // run, and keeps its old scenarios for good.\n const coveredSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .map((r) => r.testFilePath)\n .filter((f): f is string => typeof f === \"string\" && f.length > 0)\n .map((f) => toRelativePosix(f, root))\n ),\n ].sort();\n\n // A story that never declared itself — a throwing beforeAll, an import\n // error, a timeout before `story.init()` — is missing because the run\n // broke, not because someone deleted it. Checked per test, not per file:\n // one healthy story in a file does not vouch for its broken siblings.\n const incompleteSourceFiles = [\n ...new Set(\n (results.testResults ?? [])\n .filter((r) => {\n const declared =\n declaredByFile.get(toRelativePosix(r.testFilePath, root)) ??\n new Set<string>();\n const undeclaredFailure = (r.testResults ?? []).some(\n (t) => t.status === \"failed\" && !declared.has(t.fullName)\n );\n // No tests at all plus a failure means the module never loaded.\n const suiteError =\n Boolean((r as { testExecError?: unknown }).testExecError) ||\n ((r.testResults ?? []).length === 0 &&\n Boolean((r as { failureMessage?: string | null }).failureMessage));\n return undeclaredFailure || suiteError;\n })\n .map((r) => toRelativePosix(r.testFilePath, root))\n ),\n ].sort();\n\n // Nothing ran and nothing was covered: there is genuinely nothing to say.\n if (rawTestCases.length === 0 && coveredSourceFiles.length === 0) return;\n\n // Build RawRun\n const rawRun: RawRun = {\n testCases: rawTestCases,\n startedAtMs: this.startTime,\n finishedAtMs: Date.now(),\n projectRoot: root,\n packageVersion: this.packageVersion,\n gitSha: this.gitSha,\n ci: detectCI(),\n ...(this.runScope ? { runScope: this.runScope } : {}),\n ...(coveredSourceFiles.length > 0 ? { coveredSourceFiles } : {}),\n ...(incompleteSourceFiles.length > 0 ? { incompleteSourceFiles } : {}),\n };\n\n // Optionally write raw run JSON for CLI/binary consumption\n const rawRunPath = this.options.rawRunPath;\n if (rawRunPath) {\n const absolutePath = path.isAbsolute(rawRunPath)\n ? rawRunPath\n : path.join(root, rawRunPath);\n const dir = path.dirname(absolutePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n const payload = { schemaVersion: 1, ...rawRun };\n fs.writeFileSync(absolutePath, JSON.stringify(payload, null, 2), \"utf8\");\n }\n\n // Canonicalize\n const canonicalRun = canonicalizeRun(rawRun);\n\n // Generate reports\n const generator = new ReportGenerator(this.options);\n try {\n await generator.generate(canonicalRun);\n } catch (err) {\n console.error(\"Failed to generate reports:\", err);\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "executable-stories-jest",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.9.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "BDD-style executable stories for Jest with documentation generation",
|
|
6
6
|
"type": "module",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"fast-glob": "^3.3.3",
|
|
37
|
-
"executable-stories-core": "0.
|
|
38
|
-
"executable-stories-formatters": "1.
|
|
37
|
+
"executable-stories-core": "0.28.0",
|
|
38
|
+
"executable-stories-formatters": "1.22.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@jest/globals": "^30.5.1",
|