executable-stories-jest 2.0.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.cjs ADDED
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ STORY_META_KEY: () => import_executable_stories_formatters.STORY_META_KEY,
34
+ story: () => story
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+
38
+ // src/story-api.ts
39
+ var fs = __toESM(require("fs"), 1);
40
+ var path = __toESM(require("path"), 1);
41
+ var import_node_crypto = require("crypto");
42
+ var storyRegistry = globalThis.__jestExecutableStoriesRegistry ??= /* @__PURE__ */ new Map();
43
+ var attachmentRegistry = /* @__PURE__ */ new Map();
44
+ var exitHandlerRegistered = globalThis.__jestExecutableStoriesExitHandler ?? false;
45
+ function getOutputDir() {
46
+ const baseDir = process.env.JEST_STORY_DOCS_DIR ?? ".jest-executable-stories";
47
+ return path.resolve(process.cwd(), baseDir);
48
+ }
49
+ function flushStories() {
50
+ if (storyRegistry.size === 0) return;
51
+ const workerId = process.env.JEST_WORKER_ID ?? "0";
52
+ const outputDir = path.join(getOutputDir(), `worker-${workerId}`);
53
+ fs.mkdirSync(outputDir, { recursive: true });
54
+ for (const [testFilePath, scenarios] of storyRegistry) {
55
+ if (!scenarios.length) continue;
56
+ const hash = (0, import_node_crypto.createHash)("sha1").update(testFilePath).digest("hex").slice(0, 12);
57
+ const baseName = testFilePath === "unknown" ? "unknown" : path.basename(testFilePath);
58
+ const outFile = path.join(outputDir, `${baseName}.${hash}.json`);
59
+ const fileAttachments = attachmentRegistry.get(testFilePath);
60
+ const scenariosWithAttachments = scenarios.map((s) => ({
61
+ ...s,
62
+ _attachments: fileAttachments?.get(s.scenario) ?? []
63
+ }));
64
+ const payload = { testFilePath, scenarios: scenariosWithAttachments };
65
+ fs.writeFileSync(outFile, JSON.stringify(payload, null, 2) + "\n", "utf8");
66
+ }
67
+ storyRegistry.clear();
68
+ attachmentRegistry.clear();
69
+ }
70
+ function registerExitHandler() {
71
+ if (exitHandlerRegistered) return;
72
+ exitHandlerRegistered = true;
73
+ globalThis.__jestExecutableStoriesExitHandler = true;
74
+ process.on("exit", () => {
75
+ flushStories();
76
+ });
77
+ }
78
+ var activeContext = null;
79
+ var sourceOrderCounter = 0;
80
+ function getContext() {
81
+ if (!activeContext) {
82
+ throw new Error(
83
+ "story.init() must be called first. Use: it('name', () => { story.init(); ... });"
84
+ );
85
+ }
86
+ return activeContext;
87
+ }
88
+ function normalizeTickets(ticket) {
89
+ if (!ticket) return void 0;
90
+ return Array.isArray(ticket) ? ticket : [ticket];
91
+ }
92
+ function extractSuitePath(currentTestName) {
93
+ const parts = currentTestName.split(" > ");
94
+ if (parts.length <= 1) {
95
+ return { testName: currentTestName };
96
+ }
97
+ const testName = parts[parts.length - 1];
98
+ const suitePath = parts.slice(0, -1);
99
+ return { suitePath, testName };
100
+ }
101
+ function convertStoryDocsToEntries(docs) {
102
+ const entries = [];
103
+ if (docs.note) {
104
+ entries.push({ kind: "note", text: docs.note, phase: "runtime" });
105
+ }
106
+ if (docs.tag) {
107
+ const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];
108
+ entries.push({ kind: "tag", names, phase: "runtime" });
109
+ }
110
+ if (docs.kv) {
111
+ for (const [label, value] of Object.entries(docs.kv)) {
112
+ entries.push({ kind: "kv", label, value, phase: "runtime" });
113
+ }
114
+ }
115
+ if (docs.code) {
116
+ entries.push({ kind: "code", label: docs.code.label, content: docs.code.content, lang: docs.code.lang, phase: "runtime" });
117
+ }
118
+ if (docs.json) {
119
+ entries.push({ kind: "code", label: docs.json.label, content: JSON.stringify(docs.json.value, null, 2), lang: "json", phase: "runtime" });
120
+ }
121
+ if (docs.table) {
122
+ entries.push({ kind: "table", label: docs.table.label, columns: docs.table.columns, rows: docs.table.rows, phase: "runtime" });
123
+ }
124
+ if (docs.link) {
125
+ entries.push({ kind: "link", label: docs.link.label, url: docs.link.url, phase: "runtime" });
126
+ }
127
+ if (docs.section) {
128
+ entries.push({ kind: "section", title: docs.section.title, markdown: docs.section.markdown, phase: "runtime" });
129
+ }
130
+ if (docs.mermaid) {
131
+ entries.push({ kind: "mermaid", code: docs.mermaid.code, title: docs.mermaid.title, phase: "runtime" });
132
+ }
133
+ if (docs.screenshot) {
134
+ entries.push({ kind: "screenshot", path: docs.screenshot.path, alt: docs.screenshot.alt, phase: "runtime" });
135
+ }
136
+ if (docs.custom) {
137
+ entries.push({ kind: "custom", type: docs.custom.type, data: docs.custom.data, phase: "runtime" });
138
+ }
139
+ return entries;
140
+ }
141
+ function attachDoc(entry) {
142
+ const ctx = getContext();
143
+ if (ctx.currentStep) {
144
+ ctx.currentStep.docs ??= [];
145
+ ctx.currentStep.docs.push(entry);
146
+ } else {
147
+ ctx.meta.docs ??= [];
148
+ ctx.meta.docs.push(entry);
149
+ }
150
+ }
151
+ function createStepMarker(keyword) {
152
+ return function stepMarker(text, docs) {
153
+ const ctx = getContext();
154
+ const step = {
155
+ id: `step-${ctx.stepCounter++}`,
156
+ keyword,
157
+ text,
158
+ docs: docs ? convertStoryDocsToEntries(docs) : []
159
+ };
160
+ ctx.meta.steps.push(step);
161
+ ctx.currentStep = step;
162
+ };
163
+ }
164
+ function init(options) {
165
+ const state = expect.getState();
166
+ const currentTestName = state.currentTestName || "Unknown test";
167
+ const testPath = state.testPath || "unknown";
168
+ const { suitePath, testName } = extractSuitePath(currentTestName);
169
+ const meta = {
170
+ scenario: testName,
171
+ steps: [],
172
+ suitePath,
173
+ tags: options?.tags,
174
+ tickets: normalizeTickets(options?.ticket),
175
+ meta: options?.meta,
176
+ sourceOrder: sourceOrderCounter++
177
+ };
178
+ const existing = storyRegistry.get(testPath);
179
+ if (existing) {
180
+ existing.push(meta);
181
+ } else {
182
+ storyRegistry.set(testPath, [meta]);
183
+ }
184
+ registerExitHandler();
185
+ activeContext = {
186
+ meta,
187
+ currentStep: null,
188
+ stepCounter: 0,
189
+ attachments: [],
190
+ activeTimers: /* @__PURE__ */ new Map(),
191
+ timerCounter: 0
192
+ };
193
+ if (!attachmentRegistry.has(testPath)) {
194
+ attachmentRegistry.set(testPath, /* @__PURE__ */ new Map());
195
+ }
196
+ attachmentRegistry.get(testPath).set(meta.scenario, activeContext.attachments);
197
+ }
198
+ function fn(keyword, text, body) {
199
+ const ctx = getContext();
200
+ const step = {
201
+ id: `step-${ctx.stepCounter++}`,
202
+ keyword,
203
+ text,
204
+ docs: [],
205
+ wrapped: true
206
+ };
207
+ ctx.meta.steps.push(step);
208
+ ctx.currentStep = step;
209
+ const start = performance.now();
210
+ try {
211
+ const result = body();
212
+ if (result instanceof Promise) {
213
+ return result.then(
214
+ (val) => {
215
+ step.durationMs = performance.now() - start;
216
+ return val;
217
+ },
218
+ (err) => {
219
+ step.durationMs = performance.now() - start;
220
+ throw err;
221
+ }
222
+ );
223
+ }
224
+ step.durationMs = performance.now() - start;
225
+ return result;
226
+ } catch (err) {
227
+ step.durationMs = performance.now() - start;
228
+ throw err;
229
+ }
230
+ }
231
+ function storyExpect(text, body) {
232
+ return fn("Then", text, body);
233
+ }
234
+ var story = {
235
+ // Jest-specific init
236
+ init,
237
+ // BDD step markers
238
+ given: createStepMarker("Given"),
239
+ when: createStepMarker("When"),
240
+ then: createStepMarker("Then"),
241
+ and: createStepMarker("And"),
242
+ but: createStepMarker("But"),
243
+ // AAA pattern aliases
244
+ arrange: createStepMarker("Given"),
245
+ act: createStepMarker("When"),
246
+ assert: createStepMarker("Then"),
247
+ // Additional aliases
248
+ setup: createStepMarker("Given"),
249
+ context: createStepMarker("Given"),
250
+ execute: createStepMarker("When"),
251
+ action: createStepMarker("When"),
252
+ verify: createStepMarker("Then"),
253
+ // Standalone doc methods
254
+ note(text) {
255
+ attachDoc({ kind: "note", text, phase: "runtime" });
256
+ },
257
+ tag(name) {
258
+ const names = Array.isArray(name) ? name : [name];
259
+ attachDoc({ kind: "tag", names, phase: "runtime" });
260
+ },
261
+ kv(options) {
262
+ attachDoc({ kind: "kv", label: options.label, value: options.value, phase: "runtime" });
263
+ },
264
+ json(options) {
265
+ const content = JSON.stringify(options.value, null, 2);
266
+ attachDoc({ kind: "code", label: options.label, content, lang: "json", phase: "runtime" });
267
+ },
268
+ code(options) {
269
+ attachDoc({ kind: "code", label: options.label, content: options.content, lang: options.lang, phase: "runtime" });
270
+ },
271
+ table(options) {
272
+ attachDoc({ kind: "table", label: options.label, columns: options.columns, rows: options.rows, phase: "runtime" });
273
+ },
274
+ link(options) {
275
+ attachDoc({ kind: "link", label: options.label, url: options.url, phase: "runtime" });
276
+ },
277
+ section(options) {
278
+ attachDoc({ kind: "section", title: options.title, markdown: options.markdown, phase: "runtime" });
279
+ },
280
+ mermaid(options) {
281
+ attachDoc({ kind: "mermaid", code: options.code, title: options.title, phase: "runtime" });
282
+ },
283
+ screenshot(options) {
284
+ attachDoc({ kind: "screenshot", path: options.path, alt: options.alt, phase: "runtime" });
285
+ },
286
+ custom(options) {
287
+ attachDoc({ kind: "custom", type: options.type, data: options.data, phase: "runtime" });
288
+ },
289
+ // Attachments
290
+ attach(options) {
291
+ const ctx = getContext();
292
+ const stepIndex = ctx.currentStep ? ctx.meta.steps.indexOf(ctx.currentStep) : void 0;
293
+ ctx.attachments.push({
294
+ ...options,
295
+ stepIndex: stepIndex !== void 0 && stepIndex >= 0 ? stepIndex : void 0,
296
+ stepId: ctx.currentStep?.id
297
+ });
298
+ },
299
+ // Step wrappers
300
+ fn,
301
+ expect: storyExpect,
302
+ // Step timing
303
+ startTimer() {
304
+ const ctx = getContext();
305
+ const token = ctx.timerCounter++;
306
+ const stepIndex = ctx.currentStep ? ctx.meta.steps.indexOf(ctx.currentStep) : void 0;
307
+ ctx.activeTimers.set(token, {
308
+ start: performance.now(),
309
+ stepIndex: stepIndex !== void 0 && stepIndex >= 0 ? stepIndex : void 0,
310
+ stepId: ctx.currentStep?.id,
311
+ consumed: false
312
+ });
313
+ return token;
314
+ },
315
+ endTimer(token) {
316
+ const ctx = getContext();
317
+ const entry = ctx.activeTimers.get(token);
318
+ if (!entry || entry.consumed) return;
319
+ entry.consumed = true;
320
+ const durationMs = performance.now() - entry.start;
321
+ let step;
322
+ if (entry.stepId) {
323
+ step = ctx.meta.steps.find((s) => s.id === entry.stepId);
324
+ }
325
+ if (!step && entry.stepIndex !== void 0) {
326
+ step = ctx.meta.steps[entry.stepIndex];
327
+ }
328
+ if (step) {
329
+ step.durationMs = durationMs;
330
+ }
331
+ }
332
+ };
333
+
334
+ // src/types.ts
335
+ var import_executable_stories_formatters = require("executable-stories-formatters");
336
+ // Annotate the CommonJS export names for ESM import in node:
337
+ 0 && (module.exports = {
338
+ STORY_META_KEY,
339
+ story
340
+ });
341
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/story-api.ts","../src/types.ts"],"sourcesContent":["/**\n * Jest Executable Stories\n *\n * BDD-style executable documentation for Jest.\n *\n * @example\n * ```ts\n * import { story } from 'executable-stories-jest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', () => {\n * story.init();\n *\n * story.given('two numbers 5 and 3');\n * const a = 5;\n * const b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\n\n// Story API\nexport { story } from \"./story-api\";\nexport type { Story } from \"./story-api\";\n\n// Re-export types\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StepMode,\n DocPhase,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n CustomOptions,\n} from \"./types\";\n\nexport { STORY_META_KEY } from \"./types\";\n","/**\n * Jest story.* API for executable-stories.\n *\n * Uses native Jest describe/it/test with opt-in documentation:\n *\n * @example\n * ```ts\n * import { story } from 'executable-stories-jest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', () => {\n * story.init();\n *\n * story.given('two numbers 5 and 3');\n * const a = 5;\n * const b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport type {\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryMeta,\n StoryOptions,\n StoryStep,\n ScopedAttachment,\n AttachmentOptions,\n KvOptions,\n JsonOptions,\n CodeOptions,\n TableOptions,\n LinkOptions,\n SectionOptions,\n MermaidOptions,\n ScreenshotOptions,\n CustomOptions,\n} from './types';\n\n// Re-export types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryOptions,\n AttachmentOptions,\n} from './types';\n\n// ============================================================================\n// Story Context\n// ============================================================================\n\n/** Internal timer entry */\ninterface TimerEntry {\n start: number;\n stepIndex?: number;\n stepId?: string;\n consumed: boolean;\n}\n\ninterface StoryContext {\n /** The story metadata being built */\n meta: StoryMeta;\n /** The current step (for attaching docs) */\n currentStep: StoryStep | null;\n /** Deterministic step counter (resets per test case) */\n stepCounter: number;\n /** Collected attachments with step scope */\n attachments: ScopedAttachment[];\n /** Active timers keyed by token */\n activeTimers: Map<number, TimerEntry>;\n /** Monotonic timer token counter */\n timerCounter: number;\n}\n\n// ============================================================================\n// File-based story collection (works across Jest worker processes)\n// ============================================================================\n\n// Use globalThis to ensure the registry is shared across module instances\n// This is needed because Jest may load the setup file and test files as separate module instances\ndeclare global {\n // eslint-disable-next-line no-var\n var __jestExecutableStoriesRegistry: Map<string, StoryMeta[]> | undefined;\n // eslint-disable-next-line no-var\n var __jestExecutableStoriesExitHandler: boolean | undefined;\n}\n\n/** Stories collected during test execution, keyed by test file path */\nconst storyRegistry: Map<string, StoryMeta[]> = globalThis.__jestExecutableStoriesRegistry ??= new Map();\n\n/** Attachments collected per story, keyed by test file path → scenario name → attachments */\nconst attachmentRegistry = new Map<string, Map<string, ScopedAttachment[]>>();\n\n/** Track if we've registered the process exit handler */\nlet exitHandlerRegistered = globalThis.__jestExecutableStoriesExitHandler ?? false;\n\n/** Get the output directory for story JSON files */\nfunction getOutputDir(): string {\n const baseDir = process.env.JEST_STORY_DOCS_DIR ?? \".jest-executable-stories\";\n return path.resolve(process.cwd(), baseDir);\n}\n\n/** Flush all collected stories to JSON files */\nfunction flushStories(): void {\n if (storyRegistry.size === 0) return;\n\n const workerId = process.env.JEST_WORKER_ID ?? \"0\";\n const outputDir = path.join(getOutputDir(), `worker-${workerId}`);\n fs.mkdirSync(outputDir, { recursive: true });\n\n for (const [testFilePath, scenarios] of storyRegistry) {\n if (!scenarios.length) continue;\n const hash = createHash(\"sha1\").update(testFilePath).digest(\"hex\").slice(0, 12);\n const baseName = testFilePath === \"unknown\" ? \"unknown\" : path.basename(testFilePath);\n const outFile = path.join(outputDir, `${baseName}.${hash}.json`);\n\n // Include attachments per scenario\n const fileAttachments = attachmentRegistry.get(testFilePath);\n const scenariosWithAttachments = scenarios.map((s) => ({\n ...s,\n _attachments: fileAttachments?.get(s.scenario) ?? [],\n }));\n\n const payload = { testFilePath, scenarios: scenariosWithAttachments };\n fs.writeFileSync(outFile, JSON.stringify(payload, null, 2) + \"\\n\", \"utf8\");\n }\n storyRegistry.clear();\n attachmentRegistry.clear();\n}\n\n/** Register process exit handler to flush stories (once per worker) */\nfunction registerExitHandler(): void {\n if (exitHandlerRegistered) return;\n exitHandlerRegistered = true;\n globalThis.__jestExecutableStoriesExitHandler = true;\n // Use 'exit' event - always fired when Node.js is about to exit\n // Note: Only sync operations work here, which is fine for fs.writeFileSync\n process.on(\"exit\", () => {\n flushStories();\n });\n}\n\n// ============================================================================\n// Jest-specific context\n// ============================================================================\n\n/** Active story context - set by story.init() */\nlet activeContext: StoryContext | null = null;\n\n/** Counter to track source order of stories (increments on each story.init call) */\nlet sourceOrderCounter = 0;\n\n/**\n * Get the current story context. Throws if story.init() wasn't called.\n */\nfunction getContext(): StoryContext {\n if (!activeContext) {\n throw new Error(\n \"story.init() must be called first. Use: it('name', () => { story.init(); ... });\"\n );\n }\n return activeContext;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Normalize ticket option to array format.\n */\nfunction normalizeTickets(ticket: string | string[] | undefined): string[] | undefined {\n if (!ticket) return undefined;\n return Array.isArray(ticket) ? ticket : [ticket];\n}\n\n/**\n * Extract the suite path from Jest's currentTestName.\n * Jest's currentTestName is formatted as: \"describe1 > describe2 > test name\"\n */\nfunction extractSuitePath(currentTestName: string): { suitePath?: string[]; testName: string } {\n const parts = currentTestName.split(\" > \");\n if (parts.length <= 1) {\n return { testName: currentTestName };\n }\n const testName = parts[parts.length - 1];\n const suitePath = parts.slice(0, -1);\n return { suitePath, testName };\n}\n\n/**\n * Convert StoryDocs inline options to DocEntry array.\n */\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n if (docs.note) {\n entries.push({ kind: \"note\", text: docs.note, phase: \"runtime\" });\n }\n if (docs.tag) {\n const names = Array.isArray(docs.tag) ? docs.tag : [docs.tag];\n entries.push({ kind: \"tag\", names, phase: \"runtime\" });\n }\n if (docs.kv) {\n for (const [label, value] of Object.entries(docs.kv)) {\n entries.push({ kind: \"kv\", label, value, phase: \"runtime\" });\n }\n }\n if (docs.code) {\n entries.push({ kind: \"code\", label: docs.code.label, content: docs.code.content, lang: docs.code.lang, phase: \"runtime\" });\n }\n if (docs.json) {\n entries.push({ kind: \"code\", label: docs.json.label, content: JSON.stringify(docs.json.value, null, 2), lang: \"json\", phase: \"runtime\" });\n }\n if (docs.table) {\n entries.push({ kind: \"table\", label: docs.table.label, columns: docs.table.columns, rows: docs.table.rows, phase: \"runtime\" });\n }\n if (docs.link) {\n entries.push({ kind: \"link\", label: docs.link.label, url: docs.link.url, phase: \"runtime\" });\n }\n if (docs.section) {\n entries.push({ kind: \"section\", title: docs.section.title, markdown: docs.section.markdown, phase: \"runtime\" });\n }\n if (docs.mermaid) {\n entries.push({ kind: \"mermaid\", code: docs.mermaid.code, title: docs.mermaid.title, phase: \"runtime\" });\n }\n if (docs.screenshot) {\n entries.push({ kind: \"screenshot\", path: docs.screenshot.path, alt: docs.screenshot.alt, phase: \"runtime\" });\n }\n if (docs.custom) {\n entries.push({ kind: \"custom\", type: docs.custom.type, data: docs.custom.data, phase: \"runtime\" });\n }\n\n return entries;\n}\n\n// ============================================================================\n// Helper to attach doc entry to current step or story-level\n// ============================================================================\n\nfunction attachDoc(entry: DocEntry): void {\n const ctx = getContext();\n if (ctx.currentStep) {\n ctx.currentStep.docs ??= [];\n ctx.currentStep.docs.push(entry);\n } else {\n ctx.meta.docs ??= [];\n ctx.meta.docs.push(entry);\n }\n}\n\n// ============================================================================\n// Step Markers\n// ============================================================================\n\nfunction createStepMarker(keyword: StepKeyword) {\n return function stepMarker(text: string, docs?: StoryDocs): void {\n const ctx = getContext();\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: docs ? convertStoryDocsToEntries(docs) : [],\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n };\n}\n\n// ============================================================================\n// story.init() - Jest-specific\n// ============================================================================\n\n/**\n * Initialize a story for the current test.\n * Must be called at the start of each test that wants documentation.\n *\n * @param options - Optional story configuration (tags, ticket, meta)\n *\n * @example\n * ```ts\n * it('adds two numbers', () => {\n * story.init();\n * // ... rest of test\n * });\n * ```\n */\nfunction init(options?: StoryOptions): void {\n // Get current test info from Jest globals\n const state = expect.getState();\n const currentTestName = state.currentTestName || \"Unknown test\";\n const testPath = state.testPath || \"unknown\";\n\n const { suitePath, testName } = extractSuitePath(currentTestName);\n\n const meta: StoryMeta = {\n scenario: testName,\n steps: [],\n suitePath,\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // Store in registry for this file\n const existing = storyRegistry.get(testPath);\n if (existing) {\n existing.push(meta);\n } else {\n storyRegistry.set(testPath, [meta]);\n }\n\n // Register exit handler to flush stories when worker exits\n registerExitHandler();\n\n // Set active context\n activeContext = {\n meta,\n currentStep: null,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n };\n\n // Link attachments to the registry for this test file + scenario\n if (!attachmentRegistry.has(testPath)) {\n attachmentRegistry.set(testPath, new Map());\n }\n attachmentRegistry.get(testPath)!.set(meta.scenario, activeContext.attachments);\n}\n\n// ============================================================================\n// Step Wrappers: story.fn() and story.expect()\n// ============================================================================\n\n/**\n * Wrap a function body as a step. Records the step with timing and `wrapped: true`.\n * Supports both sync and async functions. Returns whatever the function returns.\n *\n * @param keyword - The BDD keyword (Given, When, Then, And, But)\n * @param text - Step description\n * @param body - The function to execute\n * @returns The return value of body (or a Promise of it if body is async)\n *\n * @example\n * ```ts\n * const data = story.fn('Given', 'setup data', () => ({ a: 5, b: 3 }));\n * const result = await story.fn('When', 'call API', async () => fetch('/api'));\n * ```\n */\nfunction fn<T>(keyword: StepKeyword, text: string, body: () => T): T {\n const ctx = getContext();\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword,\n text,\n docs: [],\n wrapped: true,\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n\n const start = performance.now();\n\n try {\n const result = body();\n\n // Handle async functions\n if (result instanceof Promise) {\n return result.then(\n (val) => {\n step.durationMs = performance.now() - start;\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n throw err;\n },\n ) as T;\n }\n\n step.durationMs = performance.now() - start;\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n throw err;\n }\n}\n\n/**\n * Wrap an assertion as a Then step. Shorthand for `story.fn('Then', text, body)`.\n *\n * @param text - Step description\n * @param body - The assertion function to execute\n *\n * @example\n * ```ts\n * story.expect('the result is 8', () => { expect(result).toBe(8); });\n * ```\n */\nfunction storyExpect<T>(text: string, body: () => T): T {\n return fn('Then', text, body);\n}\n\n// ============================================================================\n// Export story object\n// ============================================================================\n\n/**\n * The main story API object for Jest.\n *\n * @example\n * ```ts\n * import { story } from 'executable-stories-jest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', () => {\n * story.init();\n *\n * story.given('two numbers 5 and 3');\n * const a = 5, b = 3;\n *\n * story.when('I add them together');\n * const result = a + b;\n *\n * story.then('the result is 8');\n * expect(result).toBe(8);\n * });\n * });\n * ```\n */\nexport const story = {\n // Jest-specific init\n init,\n\n // BDD step markers\n given: createStepMarker(\"Given\"),\n when: createStepMarker(\"When\"),\n then: createStepMarker(\"Then\"),\n and: createStepMarker(\"And\"),\n but: createStepMarker(\"But\"),\n\n // AAA pattern aliases\n arrange: createStepMarker(\"Given\"),\n act: createStepMarker(\"When\"),\n assert: createStepMarker(\"Then\"),\n\n // Additional aliases\n setup: createStepMarker(\"Given\"),\n context: createStepMarker(\"Given\"),\n execute: createStepMarker(\"When\"),\n action: createStepMarker(\"When\"),\n verify: createStepMarker(\"Then\"),\n\n // Standalone doc methods\n note(text: string): void {\n attachDoc({ kind: \"note\", text, phase: \"runtime\" });\n },\n\n tag(name: string | string[]): void {\n const names = Array.isArray(name) ? name : [name];\n attachDoc({ kind: \"tag\", names, phase: \"runtime\" });\n },\n\n kv(options: KvOptions): void {\n attachDoc({ kind: \"kv\", label: options.label, value: options.value, phase: \"runtime\" });\n },\n\n json(options: JsonOptions): void {\n const content = JSON.stringify(options.value, null, 2);\n attachDoc({ kind: \"code\", label: options.label, content, lang: \"json\", phase: \"runtime\" });\n },\n\n code(options: CodeOptions): void {\n attachDoc({ kind: \"code\", label: options.label, content: options.content, lang: options.lang, phase: \"runtime\" });\n },\n\n table(options: TableOptions): void {\n attachDoc({ kind: \"table\", label: options.label, columns: options.columns, rows: options.rows, phase: \"runtime\" });\n },\n\n link(options: LinkOptions): void {\n attachDoc({ kind: \"link\", label: options.label, url: options.url, phase: \"runtime\" });\n },\n\n section(options: SectionOptions): void {\n attachDoc({ kind: \"section\", title: options.title, markdown: options.markdown, phase: \"runtime\" });\n },\n\n mermaid(options: MermaidOptions): void {\n attachDoc({ kind: \"mermaid\", code: options.code, title: options.title, phase: \"runtime\" });\n },\n\n screenshot(options: ScreenshotOptions): void {\n attachDoc({ kind: \"screenshot\", path: options.path, alt: options.alt, phase: \"runtime\" });\n },\n\n custom(options: CustomOptions): void {\n attachDoc({ kind: \"custom\", type: options.type, data: options.data, phase: \"runtime\" });\n },\n\n // Attachments\n attach(options: AttachmentOptions): void {\n const ctx = getContext();\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.attachments.push({\n ...options,\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n });\n },\n\n // Step wrappers\n fn,\n expect: storyExpect,\n\n // Step timing\n startTimer(): number {\n const ctx = getContext();\n const token = ctx.timerCounter++;\n const stepIndex = ctx.currentStep\n ? ctx.meta.steps.indexOf(ctx.currentStep)\n : undefined;\n ctx.activeTimers.set(token, {\n start: performance.now(),\n stepIndex: stepIndex !== undefined && stepIndex >= 0 ? stepIndex : undefined,\n stepId: ctx.currentStep?.id,\n consumed: false,\n });\n return token;\n },\n\n endTimer(token: number): void {\n const ctx = getContext();\n const entry = ctx.activeTimers.get(token);\n if (!entry || entry.consumed) return;\n\n entry.consumed = true;\n const durationMs = performance.now() - entry.start;\n\n let step: StoryStep | undefined;\n if (entry.stepId) {\n step = ctx.meta.steps.find((s) => s.id === entry.stepId);\n }\n if (!step && entry.stepIndex !== undefined) {\n step = ctx.meta.steps[entry.stepIndex];\n }\n\n if (step) {\n step.durationMs = durationMs;\n }\n },\n};\n\nexport type Story = typeof story;\n\n// ============================================================================\n// Internal exports for setup file\n// ============================================================================\n\n/**\n * Internal API for the setup file and tests. Not for public use.\n * @internal\n */\nexport const _internal = {\n flushStories,\n /** Clear active context (for tests that assert getContext() throws). */\n clearContext(): void {\n activeContext = null;\n },\n};\n","/**\n * Type definitions for executable-stories-jest.\n *\n * Shared story types are imported from executable-stories-formatters.\n * This module re-exports them and adds Jest-specific types.\n */\n\n// Re-export shared story types from formatters\nexport type {\n StepKeyword,\n StepMode,\n DocPhase,\n DocEntry,\n StoryStep,\n StoryMeta,\n} from 'executable-stories-formatters';\n\nexport { STORY_META_KEY } from 'executable-stories-formatters';\n\n// ============================================================================\n// Doc Options (for inline docs and standalone methods)\n// ============================================================================\n\n/** Options for kv() - key-value pair */\nexport interface KvOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for json() - JSON code block */\nexport interface JsonOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for code() - code block with optional language */\nexport interface CodeOptions {\n label: string;\n content: string;\n lang?: string;\n}\n\n/** Options for table() - markdown table */\nexport interface TableOptions {\n label: string;\n columns: string[];\n rows: string[][];\n}\n\n/** Options for link() - hyperlink */\nexport interface LinkOptions {\n label: string;\n url: string;\n}\n\n/** Options for section() - titled markdown section */\nexport interface SectionOptions {\n title: string;\n markdown: string;\n}\n\n/** Options for mermaid() - Mermaid diagram */\nexport interface MermaidOptions {\n code: string;\n title?: string;\n}\n\n/** Options for screenshot() - screenshot reference */\nexport interface ScreenshotOptions {\n path: string;\n alt?: string;\n}\n\n/** Options for custom() - custom doc entry */\nexport interface CustomOptions {\n type: string;\n data: unknown;\n}\n\n// ============================================================================\n// Inline Docs for Steps\n// ============================================================================\n\n/**\n * Inline documentation options for step markers.\n * Pass to story.given(), story.when(), story.then() as second argument.\n */\nexport interface StoryDocs {\n note?: string;\n tag?: string | string[];\n kv?: Record<string, unknown>;\n code?: CodeOptions;\n json?: JsonOptions;\n table?: TableOptions;\n link?: LinkOptions;\n section?: SectionOptions;\n mermaid?: MermaidOptions;\n screenshot?: ScreenshotOptions;\n custom?: CustomOptions;\n}\n\n// ============================================================================\n// Attachment Types\n// ============================================================================\n\n/** Options for attaching files or inline content to a test */\nexport interface AttachmentOptions {\n name: string;\n mediaType: string;\n path?: string;\n body?: string;\n encoding?: \"BASE64\" | \"IDENTITY\";\n charset?: string;\n fileName?: string;\n}\n\n/** Internal: attachment with step scope info */\nexport interface ScopedAttachment extends AttachmentOptions {\n stepIndex?: number;\n stepId?: string;\n}\n\n// ============================================================================\n// Story Options\n// ============================================================================\n\n/**\n * Options for configuring a story via story.init().\n */\nexport interface StoryOptions {\n tags?: string[];\n ticket?: string | string[];\n meta?: Record<string, unknown>;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,225 @@
1
+ import { StoryMeta, StepKeyword } from 'executable-stories-formatters';
2
+ export { DocEntry, DocPhase, STORY_META_KEY, StepKeyword, StepMode, StoryMeta, StoryStep } from 'executable-stories-formatters';
3
+
4
+ /**
5
+ * Type definitions for executable-stories-jest.
6
+ *
7
+ * Shared story types are imported from executable-stories-formatters.
8
+ * This module re-exports them and adds Jest-specific types.
9
+ */
10
+
11
+ /** Options for kv() - key-value pair */
12
+ interface KvOptions {
13
+ label: string;
14
+ value: unknown;
15
+ }
16
+ /** Options for json() - JSON code block */
17
+ interface JsonOptions {
18
+ label: string;
19
+ value: unknown;
20
+ }
21
+ /** Options for code() - code block with optional language */
22
+ interface CodeOptions {
23
+ label: string;
24
+ content: string;
25
+ lang?: string;
26
+ }
27
+ /** Options for table() - markdown table */
28
+ interface TableOptions {
29
+ label: string;
30
+ columns: string[];
31
+ rows: string[][];
32
+ }
33
+ /** Options for link() - hyperlink */
34
+ interface LinkOptions {
35
+ label: string;
36
+ url: string;
37
+ }
38
+ /** Options for section() - titled markdown section */
39
+ interface SectionOptions {
40
+ title: string;
41
+ markdown: string;
42
+ }
43
+ /** Options for mermaid() - Mermaid diagram */
44
+ interface MermaidOptions {
45
+ code: string;
46
+ title?: string;
47
+ }
48
+ /** Options for screenshot() - screenshot reference */
49
+ interface ScreenshotOptions {
50
+ path: string;
51
+ alt?: string;
52
+ }
53
+ /** Options for custom() - custom doc entry */
54
+ interface CustomOptions {
55
+ type: string;
56
+ data: unknown;
57
+ }
58
+ /**
59
+ * Inline documentation options for step markers.
60
+ * Pass to story.given(), story.when(), story.then() as second argument.
61
+ */
62
+ interface StoryDocs {
63
+ note?: string;
64
+ tag?: string | string[];
65
+ kv?: Record<string, unknown>;
66
+ code?: CodeOptions;
67
+ json?: JsonOptions;
68
+ table?: TableOptions;
69
+ link?: LinkOptions;
70
+ section?: SectionOptions;
71
+ mermaid?: MermaidOptions;
72
+ screenshot?: ScreenshotOptions;
73
+ custom?: CustomOptions;
74
+ }
75
+ /** Options for attaching files or inline content to a test */
76
+ interface AttachmentOptions {
77
+ name: string;
78
+ mediaType: string;
79
+ path?: string;
80
+ body?: string;
81
+ encoding?: "BASE64" | "IDENTITY";
82
+ charset?: string;
83
+ fileName?: string;
84
+ }
85
+ /**
86
+ * Options for configuring a story via story.init().
87
+ */
88
+ interface StoryOptions {
89
+ tags?: string[];
90
+ ticket?: string | string[];
91
+ meta?: Record<string, unknown>;
92
+ }
93
+
94
+ /**
95
+ * Jest story.* API for executable-stories.
96
+ *
97
+ * Uses native Jest describe/it/test with opt-in documentation:
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * import { story } from 'executable-stories-jest';
102
+ *
103
+ * describe('Calculator', () => {
104
+ * it('adds two numbers', () => {
105
+ * story.init();
106
+ *
107
+ * story.given('two numbers 5 and 3');
108
+ * const a = 5;
109
+ * const b = 3;
110
+ *
111
+ * story.when('I add them together');
112
+ * const result = a + b;
113
+ *
114
+ * story.then('the result is 8');
115
+ * expect(result).toBe(8);
116
+ * });
117
+ * });
118
+ * ```
119
+ */
120
+
121
+ declare global {
122
+ var __jestExecutableStoriesRegistry: Map<string, StoryMeta[]> | undefined;
123
+ var __jestExecutableStoriesExitHandler: boolean | undefined;
124
+ }
125
+ /**
126
+ * Initialize a story for the current test.
127
+ * Must be called at the start of each test that wants documentation.
128
+ *
129
+ * @param options - Optional story configuration (tags, ticket, meta)
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * it('adds two numbers', () => {
134
+ * story.init();
135
+ * // ... rest of test
136
+ * });
137
+ * ```
138
+ */
139
+ declare function init(options?: StoryOptions): void;
140
+ /**
141
+ * Wrap a function body as a step. Records the step with timing and `wrapped: true`.
142
+ * Supports both sync and async functions. Returns whatever the function returns.
143
+ *
144
+ * @param keyword - The BDD keyword (Given, When, Then, And, But)
145
+ * @param text - Step description
146
+ * @param body - The function to execute
147
+ * @returns The return value of body (or a Promise of it if body is async)
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const data = story.fn('Given', 'setup data', () => ({ a: 5, b: 3 }));
152
+ * const result = await story.fn('When', 'call API', async () => fetch('/api'));
153
+ * ```
154
+ */
155
+ declare function fn<T>(keyword: StepKeyword, text: string, body: () => T): T;
156
+ /**
157
+ * Wrap an assertion as a Then step. Shorthand for `story.fn('Then', text, body)`.
158
+ *
159
+ * @param text - Step description
160
+ * @param body - The assertion function to execute
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * story.expect('the result is 8', () => { expect(result).toBe(8); });
165
+ * ```
166
+ */
167
+ declare function storyExpect<T>(text: string, body: () => T): T;
168
+ /**
169
+ * The main story API object for Jest.
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * import { story } from 'executable-stories-jest';
174
+ *
175
+ * describe('Calculator', () => {
176
+ * it('adds two numbers', () => {
177
+ * story.init();
178
+ *
179
+ * story.given('two numbers 5 and 3');
180
+ * const a = 5, b = 3;
181
+ *
182
+ * story.when('I add them together');
183
+ * const result = a + b;
184
+ *
185
+ * story.then('the result is 8');
186
+ * expect(result).toBe(8);
187
+ * });
188
+ * });
189
+ * ```
190
+ */
191
+ declare const story: {
192
+ init: typeof init;
193
+ given: (text: string, docs?: StoryDocs) => void;
194
+ when: (text: string, docs?: StoryDocs) => void;
195
+ then: (text: string, docs?: StoryDocs) => void;
196
+ and: (text: string, docs?: StoryDocs) => void;
197
+ but: (text: string, docs?: StoryDocs) => void;
198
+ arrange: (text: string, docs?: StoryDocs) => void;
199
+ act: (text: string, docs?: StoryDocs) => void;
200
+ assert: (text: string, docs?: StoryDocs) => void;
201
+ setup: (text: string, docs?: StoryDocs) => void;
202
+ context: (text: string, docs?: StoryDocs) => void;
203
+ execute: (text: string, docs?: StoryDocs) => void;
204
+ action: (text: string, docs?: StoryDocs) => void;
205
+ verify: (text: string, docs?: StoryDocs) => void;
206
+ note(text: string): void;
207
+ tag(name: string | string[]): void;
208
+ kv(options: KvOptions): void;
209
+ json(options: JsonOptions): void;
210
+ code(options: CodeOptions): void;
211
+ table(options: TableOptions): void;
212
+ link(options: LinkOptions): void;
213
+ section(options: SectionOptions): void;
214
+ mermaid(options: MermaidOptions): void;
215
+ screenshot(options: ScreenshotOptions): void;
216
+ custom(options: CustomOptions): void;
217
+ attach(options: AttachmentOptions): void;
218
+ fn: typeof fn;
219
+ expect: typeof storyExpect;
220
+ startTimer(): number;
221
+ endTimer(token: number): void;
222
+ };
223
+ type Story = typeof story;
224
+
225
+ export { type AttachmentOptions, type CodeOptions, type CustomOptions, type JsonOptions, type KvOptions, type LinkOptions, type MermaidOptions, type ScreenshotOptions, type SectionOptions, type Story, type StoryDocs, type StoryOptions, type TableOptions, story };