executable-stories-vitest 8.0.0 → 8.1.1
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 +74 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -15
- package/dist/index.d.ts +52 -15
- package/dist/index.js +74 -43
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { StepKeyword } from 'executable-stories-formatters';
|
|
2
|
-
export { ColocatedStyle, DocEntry, DocPhase, FormatterOptions, OutputFormat, OutputMode, OutputRule, STORY_META_KEY, StepKeyword, StepMode, StoryMeta, StoryStep } from 'executable-stories-formatters';
|
|
1
|
+
import { DocEntry, StepKeyword } from 'executable-stories-formatters';
|
|
2
|
+
export { ColocatedStyle, DocEntry, DocPhase, FormatterOptions, NormalizedTicket, OutputFormat, OutputMode, OutputRule, STORY_META_KEY, StepKeyword, StepMode, StoryMeta, StoryStep } from 'executable-stories-formatters';
|
|
3
3
|
export { StoryReporterOptions } from './reporter.js';
|
|
4
4
|
import 'vitest/node';
|
|
5
5
|
|
|
@@ -11,6 +11,11 @@ import 'vitest/node';
|
|
|
11
11
|
* This module re-exports them and adds Vitest-specific types.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
/** A ticket reference: either a plain string ID or an object with id and optional url */
|
|
15
|
+
type TicketInput = string | {
|
|
16
|
+
id: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
};
|
|
14
19
|
/**
|
|
15
20
|
* Inline documentation options for step markers.
|
|
16
21
|
* Pass to story.given(), story.when(), story.then() as second argument.
|
|
@@ -90,7 +95,7 @@ interface StoryOptions {
|
|
|
90
95
|
/** Tags for filtering and categorizing stories */
|
|
91
96
|
tags?: string[];
|
|
92
97
|
/** Ticket/issue reference(s) for requirements traceability */
|
|
93
|
-
ticket?:
|
|
98
|
+
ticket?: TicketInput | TicketInput[];
|
|
94
99
|
/** Arbitrary user-defined metadata */
|
|
95
100
|
meta?: Record<string, unknown>;
|
|
96
101
|
/** URL template for OTel trace links. Uses {traceId} placeholder. Also settable via OTEL_TRACE_URL_TEMPLATE env var. */
|
|
@@ -199,7 +204,7 @@ declare function init(task: TaskLike, options?: StoryOptions): void;
|
|
|
199
204
|
/**
|
|
200
205
|
* Add a free-text note to the current step or story-level if before any step.
|
|
201
206
|
*/
|
|
202
|
-
declare function note(text: string):
|
|
207
|
+
declare function note(text: string, children?: DocEntry[]): DocEntry;
|
|
203
208
|
/** Options for kv() - key-value pair */
|
|
204
209
|
interface KvOptions {
|
|
205
210
|
label: string;
|
|
@@ -251,52 +256,52 @@ interface CustomOptions {
|
|
|
251
256
|
* Add a key-value pair to the current step or story-level.
|
|
252
257
|
* @example story.kv({ label: 'Payment ID', value: 'pay_123' })
|
|
253
258
|
*/
|
|
254
|
-
declare function kv(options: KvOptions):
|
|
259
|
+
declare function kv(options: KvOptions, children?: DocEntry[]): DocEntry;
|
|
255
260
|
/**
|
|
256
261
|
* Add a JSON code block to the current step or story-level.
|
|
257
262
|
* @example story.json({ label: 'Order', value: { id: 123 } })
|
|
258
263
|
*/
|
|
259
|
-
declare function json(options: JsonOptions):
|
|
264
|
+
declare function json(options: JsonOptions, children?: DocEntry[]): DocEntry;
|
|
260
265
|
/**
|
|
261
266
|
* Add a code block with optional language to the current step or story-level.
|
|
262
267
|
* @example story.code({ label: 'Config', content: 'port: 3000', lang: 'yaml' })
|
|
263
268
|
*/
|
|
264
|
-
declare function code(options: CodeOptions):
|
|
269
|
+
declare function code(options: CodeOptions, children?: DocEntry[]): DocEntry;
|
|
265
270
|
/**
|
|
266
271
|
* Add a markdown table to the current step or story-level.
|
|
267
272
|
* @example story.table({ label: 'Users', columns: ['Name', 'Role'], rows: [['Alice', 'Admin']] })
|
|
268
273
|
*/
|
|
269
|
-
declare function table(options: TableOptions):
|
|
274
|
+
declare function table(options: TableOptions, children?: DocEntry[]): DocEntry;
|
|
270
275
|
/**
|
|
271
276
|
* Add a hyperlink to the current step or story-level.
|
|
272
277
|
* @example story.link({ label: 'API Docs', url: 'https://docs.example.com' })
|
|
273
278
|
*/
|
|
274
|
-
declare function link(options: LinkOptions):
|
|
279
|
+
declare function link(options: LinkOptions, children?: DocEntry[]): DocEntry;
|
|
275
280
|
/**
|
|
276
281
|
* Add a titled section with markdown content to the current step or story-level.
|
|
277
282
|
* @example story.section({ title: 'Details', markdown: 'This is **important**' })
|
|
278
283
|
*/
|
|
279
|
-
declare function section(options: SectionOptions):
|
|
284
|
+
declare function section(options: SectionOptions, children?: DocEntry[]): DocEntry;
|
|
280
285
|
/**
|
|
281
286
|
* Add a Mermaid diagram to the current step or story-level.
|
|
282
287
|
* @example story.mermaid({ code: 'graph LR; A-->B', title: 'Flow' })
|
|
283
288
|
*/
|
|
284
|
-
declare function mermaid(options: MermaidOptions):
|
|
289
|
+
declare function mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry;
|
|
285
290
|
/**
|
|
286
291
|
* Add a screenshot reference to the current step or story-level.
|
|
287
292
|
* @example story.screenshot({ path: '/screenshots/result.png', alt: 'Final result' })
|
|
288
293
|
*/
|
|
289
|
-
declare function screenshot(options: ScreenshotOptions):
|
|
294
|
+
declare function screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry;
|
|
290
295
|
/**
|
|
291
296
|
* Add tag(s) to the current step or story-level.
|
|
292
297
|
* @example story.tag('admin') or story.tag(['admin', 'security'])
|
|
293
298
|
*/
|
|
294
|
-
declare function tag(name: string | string[]):
|
|
299
|
+
declare function tag(name: string | string[], children?: DocEntry[]): DocEntry;
|
|
295
300
|
/**
|
|
296
301
|
* Add a custom documentation entry for use with custom renderers.
|
|
297
302
|
* @example story.custom({ type: 'myType', data: { foo: 'bar' } })
|
|
298
303
|
*/
|
|
299
|
-
declare function custom(options: CustomOptions):
|
|
304
|
+
declare function custom(options: CustomOptions, children?: DocEntry[]): DocEntry;
|
|
300
305
|
/**
|
|
301
306
|
* Attach a file or inline content to the current step or test case.
|
|
302
307
|
* @example story.attach({ name: 'screenshot', mediaType: 'image/png', path: '/tmp/screenshot.png' })
|
|
@@ -339,6 +344,24 @@ declare function fn<T>(keyword: StepKeyword, text: string, body: () => T): T;
|
|
|
339
344
|
* ```
|
|
340
345
|
*/
|
|
341
346
|
declare function storyExpect<T>(text: string, body: () => T): T;
|
|
347
|
+
/**
|
|
348
|
+
* Attach OTel spans to the current test so the StoryReporter renders them
|
|
349
|
+
* as a trace waterfall in HTML reports.
|
|
350
|
+
*
|
|
351
|
+
* Accepts any array of objects with at least `spanId` and `name` fields.
|
|
352
|
+
* Structurally compatible with autotel's `SerializedSpan` and the
|
|
353
|
+
* `OtelSpan` type from executable-stories-formatters.
|
|
354
|
+
*
|
|
355
|
+
* @example
|
|
356
|
+
* ```ts
|
|
357
|
+
* import { serializeSpan } from 'autotel/test-span-collector';
|
|
358
|
+
*
|
|
359
|
+
* // After running code that creates spans:
|
|
360
|
+
* const spans = exporter.getFinishedSpans().map(serializeSpan);
|
|
361
|
+
* story.attachSpans(spans);
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
declare function attachSpans(spans: ReadonlyArray<Record<string, unknown>>): void;
|
|
342
365
|
/**
|
|
343
366
|
* The main story API object.
|
|
344
367
|
*
|
|
@@ -369,54 +392,67 @@ declare const story: {
|
|
|
369
392
|
init: typeof init;
|
|
370
393
|
given: {
|
|
371
394
|
(text: string, docs?: StoryDocs): void;
|
|
395
|
+
(text: string, children: DocEntry[]): void;
|
|
372
396
|
<T>(text: string, body: () => T): T;
|
|
373
397
|
};
|
|
374
398
|
when: {
|
|
375
399
|
(text: string, docs?: StoryDocs): void;
|
|
400
|
+
(text: string, children: DocEntry[]): void;
|
|
376
401
|
<T>(text: string, body: () => T): T;
|
|
377
402
|
};
|
|
378
403
|
then: {
|
|
379
404
|
(text: string, docs?: StoryDocs): void;
|
|
405
|
+
(text: string, children: DocEntry[]): void;
|
|
380
406
|
<T>(text: string, body: () => T): T;
|
|
381
407
|
};
|
|
382
408
|
and: {
|
|
383
409
|
(text: string, docs?: StoryDocs): void;
|
|
410
|
+
(text: string, children: DocEntry[]): void;
|
|
384
411
|
<T>(text: string, body: () => T): T;
|
|
385
412
|
};
|
|
386
413
|
but: {
|
|
387
414
|
(text: string, docs?: StoryDocs): void;
|
|
415
|
+
(text: string, children: DocEntry[]): void;
|
|
388
416
|
<T>(text: string, body: () => T): T;
|
|
389
417
|
};
|
|
390
418
|
arrange: {
|
|
391
419
|
(text: string, docs?: StoryDocs): void;
|
|
420
|
+
(text: string, children: DocEntry[]): void;
|
|
392
421
|
<T>(text: string, body: () => T): T;
|
|
393
422
|
};
|
|
394
423
|
act: {
|
|
395
424
|
(text: string, docs?: StoryDocs): void;
|
|
425
|
+
(text: string, children: DocEntry[]): void;
|
|
396
426
|
<T>(text: string, body: () => T): T;
|
|
397
427
|
};
|
|
398
428
|
assert: {
|
|
399
429
|
(text: string, docs?: StoryDocs): void;
|
|
430
|
+
(text: string, children: DocEntry[]): void;
|
|
400
431
|
<T>(text: string, body: () => T): T;
|
|
401
432
|
};
|
|
402
433
|
setup: {
|
|
403
434
|
(text: string, docs?: StoryDocs): void;
|
|
435
|
+
(text: string, children: DocEntry[]): void;
|
|
404
436
|
<T>(text: string, body: () => T): T;
|
|
405
437
|
};
|
|
406
438
|
context: {
|
|
407
439
|
(text: string, docs?: StoryDocs): void;
|
|
440
|
+
(text: string, children: DocEntry[]): void;
|
|
408
441
|
<T>(text: string, body: () => T): T;
|
|
409
442
|
};
|
|
410
443
|
execute: {
|
|
411
444
|
(text: string, docs?: StoryDocs): void;
|
|
445
|
+
(text: string, children: DocEntry[]): void;
|
|
412
446
|
<T>(text: string, body: () => T): T;
|
|
413
447
|
};
|
|
414
448
|
action: {
|
|
415
449
|
(text: string, docs?: StoryDocs): void;
|
|
450
|
+
(text: string, children: DocEntry[]): void;
|
|
416
451
|
<T>(text: string, body: () => T): T;
|
|
417
452
|
};
|
|
418
453
|
verify: {
|
|
419
454
|
(text: string, docs?: StoryDocs): void;
|
|
455
|
+
(text: string, children: DocEntry[]): void;
|
|
420
456
|
<T>(text: string, body: () => T): T;
|
|
421
457
|
};
|
|
422
458
|
note: typeof note;
|
|
@@ -431,6 +467,7 @@ declare const story: {
|
|
|
431
467
|
tag: typeof tag;
|
|
432
468
|
custom: typeof custom;
|
|
433
469
|
attach: typeof attach;
|
|
470
|
+
attachSpans: typeof attachSpans;
|
|
434
471
|
fn: typeof fn;
|
|
435
472
|
expect: typeof storyExpect;
|
|
436
473
|
startTimer: typeof startTimer;
|
|
@@ -485,4 +522,4 @@ declare class StoryReporter {
|
|
|
485
522
|
constructor();
|
|
486
523
|
}
|
|
487
524
|
|
|
488
|
-
export { type Story, type StoryDocs, type StoryOptions, StoryReporter, type VitestSuite, type VitestTask, story };
|
|
525
|
+
export { type Story, type StoryDocs, type StoryOptions, StoryReporter, type TicketInput, type VitestSuite, type VitestTask, story };
|
package/dist/index.js
CHANGED
|
@@ -41,7 +41,8 @@ function extractSuitePath(task) {
|
|
|
41
41
|
}
|
|
42
42
|
function normalizeTickets(ticket) {
|
|
43
43
|
if (!ticket) return void 0;
|
|
44
|
-
|
|
44
|
+
const arr = Array.isArray(ticket) ? ticket : [ticket];
|
|
45
|
+
return arr.map((t) => typeof t === "string" ? { id: t } : t);
|
|
45
46
|
}
|
|
46
47
|
function convertStoryDocsToEntries(docs) {
|
|
47
48
|
const entries = [];
|
|
@@ -156,7 +157,7 @@ function init(task, options) {
|
|
|
156
157
|
if (options?.tags?.length) span.setAttribute("story.tags", options.tags);
|
|
157
158
|
if (options?.ticket) {
|
|
158
159
|
const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];
|
|
159
|
-
span.setAttribute("story.tickets", tickets);
|
|
160
|
+
span.setAttribute("story.tickets", tickets.map((t) => typeof t === "string" ? t : t.id));
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
} catch {
|
|
@@ -177,17 +178,37 @@ function createStepMarker(keyword) {
|
|
|
177
178
|
function stepMarker(text, docsOrBody) {
|
|
178
179
|
const ctx = getContext();
|
|
179
180
|
const isCallback = typeof docsOrBody === "function";
|
|
181
|
+
const isChildrenArray = Array.isArray(docsOrBody);
|
|
180
182
|
const resolvedKeyword = (keyword === "Given" || keyword === "When" || keyword === "Then") && ctx.meta.steps.some((s) => s.keyword === keyword) ? "And" : keyword;
|
|
183
|
+
let stepDocs = [];
|
|
184
|
+
if (!isCallback && !isChildrenArray && docsOrBody) {
|
|
185
|
+
stepDocs = convertStoryDocsToEntries(docsOrBody);
|
|
186
|
+
}
|
|
181
187
|
const step = {
|
|
182
188
|
id: `step-${ctx.stepCounter++}`,
|
|
183
189
|
keyword: resolvedKeyword,
|
|
184
190
|
text,
|
|
185
|
-
docs:
|
|
191
|
+
docs: stepDocs,
|
|
186
192
|
...isCallback ? { wrapped: true } : {}
|
|
187
193
|
};
|
|
188
194
|
ctx.meta.steps.push(step);
|
|
189
195
|
ctx.currentStep = step;
|
|
190
196
|
syncMetaToTask();
|
|
197
|
+
if (isChildrenArray) {
|
|
198
|
+
const children = docsOrBody;
|
|
199
|
+
if (children.length > 0) {
|
|
200
|
+
const childSet = new Set(children);
|
|
201
|
+
ctx.meta.docs = (ctx.meta.docs ?? []).filter((d) => !childSet.has(d));
|
|
202
|
+
for (const prevStep of ctx.meta.steps) {
|
|
203
|
+
if (prevStep !== step && prevStep.docs) {
|
|
204
|
+
prevStep.docs = prevStep.docs.filter((d) => !childSet.has(d));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
step.docs = [...step.docs ?? [], ...children];
|
|
208
|
+
}
|
|
209
|
+
syncMetaToTask();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
191
212
|
if (!isCallback) return;
|
|
192
213
|
const body = docsOrBody;
|
|
193
214
|
const start = performance.now();
|
|
@@ -218,19 +239,20 @@ function createStepMarker(keyword) {
|
|
|
218
239
|
}
|
|
219
240
|
return stepMarker;
|
|
220
241
|
}
|
|
221
|
-
function note(text) {
|
|
222
|
-
|
|
223
|
-
const entry = { kind: "note", text, phase: "runtime" };
|
|
224
|
-
if (ctx.currentStep) {
|
|
225
|
-
ctx.currentStep.docs ??= [];
|
|
226
|
-
ctx.currentStep.docs.push(entry);
|
|
227
|
-
} else {
|
|
228
|
-
ctx.meta.docs ??= [];
|
|
229
|
-
ctx.meta.docs.push(entry);
|
|
230
|
-
}
|
|
242
|
+
function note(text, children) {
|
|
243
|
+
return attachDoc({ kind: "note", text, phase: "runtime" }, children);
|
|
231
244
|
}
|
|
232
|
-
function attachDoc(entry) {
|
|
245
|
+
function attachDoc(entry, children) {
|
|
233
246
|
const ctx = getContext();
|
|
247
|
+
if (children && children.length > 0) {
|
|
248
|
+
entry.children = children;
|
|
249
|
+
const childSet = new Set(children);
|
|
250
|
+
const filterDocs = (docs) => docs.filter((d) => !childSet.has(d));
|
|
251
|
+
ctx.meta.docs = filterDocs(ctx.meta.docs ?? []);
|
|
252
|
+
for (const step of ctx.meta.steps) {
|
|
253
|
+
if (step.docs) step.docs = filterDocs(step.docs);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
234
256
|
if (ctx.currentStep) {
|
|
235
257
|
ctx.currentStep.docs ??= [];
|
|
236
258
|
ctx.currentStep.docs.push(entry);
|
|
@@ -239,86 +261,87 @@ function attachDoc(entry) {
|
|
|
239
261
|
ctx.meta.docs.push(entry);
|
|
240
262
|
}
|
|
241
263
|
syncMetaToTask();
|
|
264
|
+
return entry;
|
|
242
265
|
}
|
|
243
|
-
function kv(options) {
|
|
244
|
-
attachDoc({
|
|
266
|
+
function kv(options, children) {
|
|
267
|
+
return attachDoc({
|
|
245
268
|
kind: "kv",
|
|
246
269
|
label: options.label,
|
|
247
270
|
value: options.value,
|
|
248
271
|
phase: "runtime"
|
|
249
|
-
});
|
|
272
|
+
}, children);
|
|
250
273
|
}
|
|
251
|
-
function json(options) {
|
|
274
|
+
function json(options, children) {
|
|
252
275
|
const content = JSON.stringify(options.value, null, 2);
|
|
253
|
-
attachDoc({
|
|
276
|
+
return attachDoc({
|
|
254
277
|
kind: "code",
|
|
255
278
|
label: options.label,
|
|
256
279
|
content,
|
|
257
280
|
lang: "json",
|
|
258
281
|
phase: "runtime"
|
|
259
|
-
});
|
|
282
|
+
}, children);
|
|
260
283
|
}
|
|
261
|
-
function code(options) {
|
|
262
|
-
attachDoc({
|
|
284
|
+
function code(options, children) {
|
|
285
|
+
return attachDoc({
|
|
263
286
|
kind: "code",
|
|
264
287
|
label: options.label,
|
|
265
288
|
content: options.content,
|
|
266
289
|
lang: options.lang,
|
|
267
290
|
phase: "runtime"
|
|
268
|
-
});
|
|
291
|
+
}, children);
|
|
269
292
|
}
|
|
270
|
-
function table(options) {
|
|
271
|
-
attachDoc({
|
|
293
|
+
function table(options, children) {
|
|
294
|
+
return attachDoc({
|
|
272
295
|
kind: "table",
|
|
273
296
|
label: options.label,
|
|
274
297
|
columns: options.columns,
|
|
275
298
|
rows: options.rows,
|
|
276
299
|
phase: "runtime"
|
|
277
|
-
});
|
|
300
|
+
}, children);
|
|
278
301
|
}
|
|
279
|
-
function link(options) {
|
|
280
|
-
attachDoc({
|
|
302
|
+
function link(options, children) {
|
|
303
|
+
return attachDoc({
|
|
281
304
|
kind: "link",
|
|
282
305
|
label: options.label,
|
|
283
306
|
url: options.url,
|
|
284
307
|
phase: "runtime"
|
|
285
|
-
});
|
|
308
|
+
}, children);
|
|
286
309
|
}
|
|
287
|
-
function section(options) {
|
|
288
|
-
attachDoc({
|
|
310
|
+
function section(options, children) {
|
|
311
|
+
return attachDoc({
|
|
289
312
|
kind: "section",
|
|
290
313
|
title: options.title,
|
|
291
314
|
markdown: options.markdown,
|
|
292
315
|
phase: "runtime"
|
|
293
|
-
});
|
|
316
|
+
}, children);
|
|
294
317
|
}
|
|
295
|
-
function mermaid(options) {
|
|
296
|
-
attachDoc({
|
|
318
|
+
function mermaid(options, children) {
|
|
319
|
+
return attachDoc({
|
|
297
320
|
kind: "mermaid",
|
|
298
321
|
code: options.code,
|
|
299
322
|
title: options.title,
|
|
300
323
|
phase: "runtime"
|
|
301
|
-
});
|
|
324
|
+
}, children);
|
|
302
325
|
}
|
|
303
|
-
function screenshot(options) {
|
|
304
|
-
attachDoc({
|
|
326
|
+
function screenshot(options, children) {
|
|
327
|
+
return attachDoc({
|
|
305
328
|
kind: "screenshot",
|
|
306
329
|
path: options.path,
|
|
307
330
|
alt: options.alt,
|
|
308
331
|
phase: "runtime"
|
|
309
|
-
});
|
|
332
|
+
}, children);
|
|
310
333
|
}
|
|
311
|
-
function tag(name) {
|
|
334
|
+
function tag(name, children) {
|
|
312
335
|
const names = Array.isArray(name) ? name : [name];
|
|
313
|
-
attachDoc({ kind: "tag", names, phase: "runtime" });
|
|
336
|
+
return attachDoc({ kind: "tag", names, phase: "runtime" }, children);
|
|
314
337
|
}
|
|
315
|
-
function custom(options) {
|
|
316
|
-
attachDoc({
|
|
338
|
+
function custom(options, children) {
|
|
339
|
+
return attachDoc({
|
|
317
340
|
kind: "custom",
|
|
318
341
|
type: options.type,
|
|
319
342
|
data: options.data,
|
|
320
343
|
phase: "runtime"
|
|
321
|
-
});
|
|
344
|
+
}, children);
|
|
322
345
|
}
|
|
323
346
|
function attach(options) {
|
|
324
347
|
const ctx = getContext();
|
|
@@ -404,6 +427,12 @@ function fn(keyword, text, body) {
|
|
|
404
427
|
function storyExpect(text, body) {
|
|
405
428
|
return fn("Then", text, body);
|
|
406
429
|
}
|
|
430
|
+
function attachSpans(spans) {
|
|
431
|
+
const ctx = getContext();
|
|
432
|
+
if (ctx.taskMeta) {
|
|
433
|
+
ctx.taskMeta.otelSpans = spans;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
407
436
|
var story = {
|
|
408
437
|
// Core
|
|
409
438
|
init,
|
|
@@ -437,6 +466,8 @@ var story = {
|
|
|
437
466
|
custom,
|
|
438
467
|
// Attachments
|
|
439
468
|
attach,
|
|
469
|
+
// OTel span attachment
|
|
470
|
+
attachSpans,
|
|
440
471
|
// Step wrappers
|
|
441
472
|
fn,
|
|
442
473
|
expect: storyExpect,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/story-api.ts","../src/types.ts","../src/index.ts"],"sourcesContent":["/**\n * story.* API for executable-stories-vitest.\n *\n * Uses native Vitest describe/it/test with opt-in documentation:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\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 { createRequire } from 'node:module';\nimport {\n tryGetActiveOtelContext,\n resolveTraceUrl,\n} from 'executable-stories-formatters';\nimport type {\n DocEntry,\n StepKeyword,\n StoryDocs,\n StoryMeta,\n StoryOptions,\n StoryStep,\n VitestSuite,\n} from './types';\n\n// ============================================================================\n// Task Interface (compatible with Vitest's actual task type)\n// ============================================================================\n\n/**\n * Minimal task interface compatible with Vitest's Test type.\n * The meta property accepts any object type to be compatible with Vitest's TaskMeta.\n */\ninterface TaskLike {\n name: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n meta: any;\n suite?: VitestSuite;\n file?: { name?: string };\n}\n\n// ============================================================================\n// Story Context\n// ============================================================================\n\n/** Attachment options for story.attach() */\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 */\ninterface ScopedAttachment extends AttachmentOptions {\n stepIndex?: number;\n stepId?: string;\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 /** Reference to task.meta for updates */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n taskMeta: any;\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/** 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(task) must be called first. Use: it('name', ({ task }) => { story.init(task); ... });\",\n );\n }\n return activeContext;\n}\n\n/** Re-attach current meta to task.meta.story so reporter sees steps and docs (e.g. story.note). */\nfunction syncMetaToTask(): void {\n if (activeContext?.taskMeta) {\n activeContext.taskMeta.story = activeContext.meta;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Check if a name looks like a file path (to filter out from suite paths).\n */\nfunction looksLikeFilePath(name: string): boolean {\n if (name.includes('/') || name.includes('\\\\')) return true;\n if (name.includes('.spec.') || name.includes('.test.')) return true;\n if (/\\.(spec|test)\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n if (/\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n return false;\n}\n\n/**\n * Extract the suite path (parent describe names) from a Vitest task object.\n */\nfunction extractSuitePath(task: TaskLike): string[] | undefined {\n const path: string[] = [];\n const fileName = task.file?.name;\n let current: VitestSuite | undefined = task.suite;\n\n while (current) {\n const name = current.name;\n if (\n name &&\n name.trim() !== '' &&\n name !== '<root>' &&\n name !== fileName &&\n !looksLikeFilePath(name)\n ) {\n path.unshift(name);\n }\n current = current.suite;\n }\n\n return path.length > 0 ? path : undefined;\n}\n\n/**\n * Normalize ticket option to array format.\n */\nfunction normalizeTickets(\n ticket: string | string[] | undefined,\n): string[] | undefined {\n if (!ticket) return undefined;\n return Array.isArray(ticket) ? ticket : [ticket];\n}\n\n/**\n * Convert StoryDocs inline options to DocEntry array.\n * Matches the standalone DocApi method signatures.\n */\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n // note(text)\n if (docs.note) {\n entries.push({ kind: 'note', text: docs.note, phase: 'runtime' });\n }\n\n // tag(name | names)\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\n // kv(label, value) - multiple pairs via Record\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\n // code(label, content, lang?)\n if (docs.code) {\n entries.push({\n kind: 'code',\n label: docs.code.label,\n content: docs.code.content,\n lang: docs.code.lang,\n phase: 'runtime',\n });\n }\n\n // json(label, value)\n if (docs.json) {\n entries.push({\n kind: 'code',\n label: docs.json.label,\n content: JSON.stringify(docs.json.value, null, 2),\n lang: 'json',\n phase: 'runtime',\n });\n }\n\n // table(label, columns, rows)\n if (docs.table) {\n entries.push({\n kind: 'table',\n label: docs.table.label,\n columns: docs.table.columns,\n rows: docs.table.rows,\n phase: 'runtime',\n });\n }\n\n // link(label, url)\n if (docs.link) {\n entries.push({\n kind: 'link',\n label: docs.link.label,\n url: docs.link.url,\n phase: 'runtime',\n });\n }\n\n // section(title, markdown)\n if (docs.section) {\n entries.push({\n kind: 'section',\n title: docs.section.title,\n markdown: docs.section.markdown,\n phase: 'runtime',\n });\n }\n\n // mermaid(code, title?)\n if (docs.mermaid) {\n entries.push({\n kind: 'mermaid',\n code: docs.mermaid.code,\n title: docs.mermaid.title,\n phase: 'runtime',\n });\n }\n\n // screenshot(path, alt?)\n if (docs.screenshot) {\n entries.push({\n kind: 'screenshot',\n path: docs.screenshot.path,\n alt: docs.screenshot.alt,\n phase: 'runtime',\n });\n }\n\n // custom(type, data)\n if (docs.custom) {\n entries.push({\n kind: 'custom',\n type: docs.custom.type,\n data: docs.custom.data,\n phase: 'runtime',\n });\n }\n\n return entries;\n}\n\n// ============================================================================\n// story.init()\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 task - The Vitest task object from ({ task }) => { ... }\n * @param options - Optional story configuration (tags, ticket, meta)\n *\n * @example\n * ```ts\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n * // ... rest of test\n * });\n *\n * // With options:\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\nfunction init(task: TaskLike, options?: StoryOptions): void {\n const meta: StoryMeta = {\n scenario: task.name,\n steps: [],\n suitePath: extractSuitePath(task),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // OTel bridge: detect active span, flow data bidirectionally\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId in structured meta\n meta.meta = { ...meta.meta, otel: { traceId: otelCtx.traceId, spanId: otelCtx.spanId } };\n\n // OTel -> Story: inject human-readable doc entries\n meta.docs = meta.docs ?? [];\n meta.docs.push({ kind: 'kv', label: 'Trace ID', value: otelCtx.traceId, phase: 'runtime' });\n\n const template = options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const url = resolveTraceUrl(template, otelCtx.traceId);\n if (url) {\n meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\n }\n\n // Story -> OTel: enrich active span with story attributes\n try {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const reqUrl = import.meta.url\n ?? (typeof __filename !== 'undefined' ? `file://${__filename}` : undefined);\n const req = createRequire(reqUrl!);\n const api = req('@opentelemetry/api');\n const span = api.trace?.getActiveSpan?.();\n if (span) {\n span.setAttribute('story.scenario', task.name);\n if (options?.tags?.length) span.setAttribute('story.tags', options.tags);\n if (options?.ticket) {\n const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];\n span.setAttribute('story.tickets', tickets);\n }\n }\n } catch { /* OTel not available */ }\n }\n\n // Attach to task.meta so reporter can find it\n task.meta.story = meta;\n\n // Set active context\n activeContext = {\n meta,\n currentStep: null,\n taskMeta: task.meta,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n };\n}\n\n// ============================================================================\n// Step Markers\n// ============================================================================\n\n/**\n * Create a step marker function for a given keyword.\n */\nfunction createStepMarker(keyword: StepKeyword) {\n function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker<T>(text: string, body: () => T): T;\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | (() => T)): T | void {\n const ctx = getContext();\n const isCallback = typeof docsOrBody === 'function';\n\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: (!isCallback && docsOrBody) ? convertStoryDocsToEntries(docsOrBody) : [],\n ...(isCallback ? { wrapped: true } : {}),\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncMetaToTask();\n\n if (!isCallback) return;\n\n const body = docsOrBody as () => T;\n const start = performance.now();\n\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; syncMetaToTask(); return val; },\n (err) => { step.durationMs = performance.now() - start; syncMetaToTask(); throw err; },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n }\n }\n return stepMarker;\n}\n\n// ============================================================================\n// Doc Methods (Standalone)\n// ============================================================================\n\n/**\n * Add a free-text note to the current step or story-level if before any step.\n */\nfunction note(text: string): void {\n const ctx = getContext();\n const entry: DocEntry = { kind: 'note', text, phase: 'runtime' };\n\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// Doc Method Types (shared between standalone and inline)\n// ============================================================================\n\n/** Options for kv() - key-value pair */\ninterface KvOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for json() - JSON code block */\ninterface JsonOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for code() - code block with optional language */\ninterface CodeOptions {\n label: string;\n content: string;\n lang?: string;\n}\n\n/** Options for table() - markdown table */\ninterface TableOptions {\n label: string;\n columns: string[];\n rows: string[][];\n}\n\n/** Options for link() - hyperlink */\ninterface LinkOptions {\n label: string;\n url: string;\n}\n\n/** Options for section() - titled markdown section */\ninterface SectionOptions {\n title: string;\n markdown: string;\n}\n\n/** Options for mermaid() - Mermaid diagram */\ninterface MermaidOptions {\n code: string;\n title?: string;\n}\n\n/** Options for screenshot() - screenshot reference */\ninterface ScreenshotOptions {\n path: string;\n alt?: string;\n}\n\n/** Options for custom() - custom doc entry */\ninterface CustomOptions {\n type: string;\n data: unknown;\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 syncMetaToTask();\n}\n\n// ============================================================================\n// Doc Methods (Standalone) - same shape as inline docs\n// ============================================================================\n\n/**\n * Add a key-value pair to the current step or story-level.\n * @example story.kv({ label: 'Payment ID', value: 'pay_123' })\n */\nfunction kv(options: KvOptions): void {\n attachDoc({\n kind: 'kv',\n label: options.label,\n value: options.value,\n phase: 'runtime',\n });\n}\n\n/**\n * Add a JSON code block to the current step or story-level.\n * @example story.json({ label: 'Order', value: { id: 123 } })\n */\nfunction json(options: JsonOptions): void {\n const content = JSON.stringify(options.value, null, 2);\n attachDoc({\n kind: 'code',\n label: options.label,\n content,\n lang: 'json',\n phase: 'runtime',\n });\n}\n\n/**\n * Add a code block with optional language to the current step or story-level.\n * @example story.code({ label: 'Config', content: 'port: 3000', lang: 'yaml' })\n */\nfunction code(options: CodeOptions): void {\n attachDoc({\n kind: 'code',\n label: options.label,\n content: options.content,\n lang: options.lang,\n phase: 'runtime',\n });\n}\n\n/**\n * Add a markdown table to the current step or story-level.\n * @example story.table({ label: 'Users', columns: ['Name', 'Role'], rows: [['Alice', 'Admin']] })\n */\nfunction table(options: TableOptions): void {\n attachDoc({\n kind: 'table',\n label: options.label,\n columns: options.columns,\n rows: options.rows,\n phase: 'runtime',\n });\n}\n\n/**\n * Add a hyperlink to the current step or story-level.\n * @example story.link({ label: 'API Docs', url: 'https://docs.example.com' })\n */\nfunction link(options: LinkOptions): void {\n attachDoc({\n kind: 'link',\n label: options.label,\n url: options.url,\n phase: 'runtime',\n });\n}\n\n/**\n * Add a titled section with markdown content to the current step or story-level.\n * @example story.section({ title: 'Details', markdown: 'This is **important**' })\n */\nfunction section(options: SectionOptions): void {\n attachDoc({\n kind: 'section',\n title: options.title,\n markdown: options.markdown,\n phase: 'runtime',\n });\n}\n\n/**\n * Add a Mermaid diagram to the current step or story-level.\n * @example story.mermaid({ code: 'graph LR; A-->B', title: 'Flow' })\n */\nfunction mermaid(options: MermaidOptions): void {\n attachDoc({\n kind: 'mermaid',\n code: options.code,\n title: options.title,\n phase: 'runtime',\n });\n}\n\n/**\n * Add a screenshot reference to the current step or story-level.\n * @example story.screenshot({ path: '/screenshots/result.png', alt: 'Final result' })\n */\nfunction screenshot(options: ScreenshotOptions): void {\n attachDoc({\n kind: 'screenshot',\n path: options.path,\n alt: options.alt,\n phase: 'runtime',\n });\n}\n\n/**\n * Add tag(s) to the current step or story-level.\n * @example story.tag('admin') or story.tag(['admin', 'security'])\n */\nfunction tag(name: string | string[]): void {\n const names = Array.isArray(name) ? name : [name];\n attachDoc({ kind: 'tag', names, phase: 'runtime' });\n}\n\n/**\n * Add a custom documentation entry for use with custom renderers.\n * @example story.custom({ type: 'myType', data: { foo: 'bar' } })\n */\nfunction custom(options: CustomOptions): void {\n attachDoc({\n kind: 'custom',\n type: options.type,\n data: options.data,\n phase: 'runtime',\n });\n}\n\n// ============================================================================\n// Attachments\n// ============================================================================\n\n/**\n * Attach a file or inline content to the current step or test case.\n * @example story.attach({ name: 'screenshot', mediaType: 'image/png', path: '/tmp/screenshot.png' })\n */\nfunction 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 // Store attachments on task.meta so reporter can read them\n if (ctx.taskMeta) {\n ctx.taskMeta.storyAttachments = ctx.attachments;\n }\n}\n\n// ============================================================================\n// Step Timing\n// ============================================================================\n\n/**\n * Start a timer for the current step. Returns a token to pass to endTimer().\n */\nfunction 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/**\n * End a timer and record duration on the step that was active when startTimer() was called.\n */\nfunction 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 syncMetaToTask();\n }\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 const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: [],\n wrapped: true,\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncMetaToTask();\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 syncMetaToTask();\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n },\n ) as T;\n }\n\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\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 * await story.expect('async check', async () => { ... });\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.\n *\n * Use with native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\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 // Core\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,\n kv,\n json,\n code,\n table,\n link,\n section,\n mermaid,\n screenshot,\n tag,\n custom,\n\n // Attachments\n attach,\n\n // Step wrappers\n fn,\n expect: storyExpect,\n\n // Step timing\n startTimer,\n endTimer,\n};\n\nexport type Story = typeof story;\n","/**\n * Type definitions for executable-stories-vitest.\n *\n * Shared story types (StepKeyword, DocEntry, StoryStep, StoryMeta, etc.)\n * are imported from executable-stories-formatters — the single source of truth.\n * This module re-exports them and adds Vitest-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// Vitest-specific Types\n// ============================================================================\n\n/**\n * Inline documentation options for step markers.\n * Pass to story.given(), story.when(), story.then() as second argument.\n *\n * @example\n * ```ts\n * story.given('valid credentials', {\n * json: { label: 'Credentials', value: { email: 'test@example.com', password: '***' } },\n * note: 'Password is masked for security'\n * });\n * ```\n */\nexport interface StoryDocs {\n /** Add a free-text note */\n note?: string;\n /** Add tag(s) for categorization */\n tag?: string | string[];\n /** Add key-value pairs */\n kv?: Record<string, unknown>;\n /** Add a code block with label and optional language */\n code?: { label: string; content: string; lang?: string };\n /** Add a JSON data block with label */\n json?: { label: string; value: unknown };\n /** Add a markdown table with label */\n table?: { label: string; columns: string[]; rows: string[][] };\n /** Add a hyperlink */\n link?: { label: string; url: string };\n /** Add a titled section with markdown content */\n section?: { title: string; markdown: string };\n /** Add a Mermaid diagram with optional title */\n mermaid?: { code: string; title?: string };\n /** Add a screenshot reference */\n screenshot?: { path: string; alt?: string };\n /** Add a custom documentation entry */\n custom?: { type: string; data: unknown };\n}\n\n/**\n * Options for configuring a story via story.init().\n *\n * @example\n * ```ts\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\nexport interface StoryOptions {\n /** Tags for filtering and categorizing stories */\n tags?: string[];\n /** Ticket/issue reference(s) for requirements traceability */\n ticket?: string | string[];\n /** Arbitrary user-defined metadata */\n meta?: Record<string, unknown>;\n /** URL template for OTel trace links. Uses {traceId} placeholder. Also settable via OTEL_TRACE_URL_TEMPLATE env var. */\n traceUrlTemplate?: string;\n}\n\n// ============================================================================\n// Vitest Task Type (minimal interface)\n// ============================================================================\n\n/** Minimal Vitest suite interface for suite path extraction */\nexport interface VitestSuite {\n /** Suite name */\n name?: string;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n}\n\n/**\n * Minimal Vitest task interface for story.init().\n * This is the { task } from it('name', ({ task }) => { ... }).\n *\n * Uses generic type parameter to be compatible with Vitest's actual TaskMeta type.\n */\nexport interface VitestTask<TMeta = Record<string, unknown>> {\n /** The test/task name */\n name: string;\n /** Task metadata object where we store story data */\n meta: TMeta;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n /** The test file (optional) */\n file?: { name?: string };\n}\n","/**\n * executable-stories-vitest: Native Vitest story/given/when/then with Markdown doc generation.\n *\n * Uses native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\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 *\n * In vitest.config, import StoryReporter from \"executable-stories-vitest/reporter\":\n *\n * @example\n * ```ts\n * import { defineConfig } from \"vitest/config\";\n * import { StoryReporter } from \"executable-stories-vitest/reporter\";\n *\n * export default defineConfig({\n * test: {\n * reporters: [\"default\", new StoryReporter()],\n * },\n * });\n * ```\n */\n\n// Core API\nexport { story, type Story } from './story-api';\n\n// Types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StepMode,\n DocPhase,\n StoryDocs,\n StoryOptions,\n VitestTask,\n VitestSuite,\n} from './types';\n\nexport { STORY_META_KEY } from './types';\n\n// Reporter types (actual reporter is in /reporter subpath)\nexport type {\n StoryReporterOptions,\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from './reporter';\n\nconst STORY_REPORTER_GUARD_MSG =\n 'Do not import StoryReporter from \"executable-stories-vitest\". In vitest.config, import it from \"executable-stories-vitest/reporter\".';\n\n/** @internal Guard: throws if used. Import StoryReporter from \"executable-stories-vitest/reporter\" in vitest.config. */\nexport class StoryReporter {\n static __isGuard = true;\n constructor() {\n throw new Error(STORY_REPORTER_GUARD_MSG);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/story-api.ts","../src/types.ts","../src/index.ts"],"sourcesContent":["/**\n * story.* API for executable-stories-vitest.\n *\n * Uses native Vitest describe/it/test with opt-in documentation:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\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 { createRequire } from 'node:module';\nimport {\n tryGetActiveOtelContext,\n resolveTraceUrl,\n} from 'executable-stories-formatters';\nimport type {\n DocEntry,\n NormalizedTicket,\n StepKeyword,\n StoryDocs,\n StoryMeta,\n StoryOptions,\n StoryStep,\n TicketInput,\n VitestSuite,\n} from './types';\n\n// ============================================================================\n// Task Interface (compatible with Vitest's actual task type)\n// ============================================================================\n\n/**\n * Minimal task interface compatible with Vitest's Test type.\n * The meta property accepts any object type to be compatible with Vitest's TaskMeta.\n */\ninterface TaskLike {\n name: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n meta: any;\n suite?: VitestSuite;\n file?: { name?: string };\n}\n\n// ============================================================================\n// Story Context\n// ============================================================================\n\n/** Attachment options for story.attach() */\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 */\ninterface ScopedAttachment extends AttachmentOptions {\n stepIndex?: number;\n stepId?: string;\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 /** Reference to task.meta for updates */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n taskMeta: any;\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/** 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(task) must be called first. Use: it('name', ({ task }) => { story.init(task); ... });\",\n );\n }\n return activeContext;\n}\n\n/** Re-attach current meta to task.meta.story so reporter sees steps and docs (e.g. story.note). */\nfunction syncMetaToTask(): void {\n if (activeContext?.taskMeta) {\n activeContext.taskMeta.story = activeContext.meta;\n }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Check if a name looks like a file path (to filter out from suite paths).\n */\nfunction looksLikeFilePath(name: string): boolean {\n if (name.includes('/') || name.includes('\\\\')) return true;\n if (name.includes('.spec.') || name.includes('.test.')) return true;\n if (/\\.(spec|test)\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n if (/\\.(ts|js|mjs|cjs)$/.test(name)) return true;\n return false;\n}\n\n/**\n * Extract the suite path (parent describe names) from a Vitest task object.\n */\nfunction extractSuitePath(task: TaskLike): string[] | undefined {\n const path: string[] = [];\n const fileName = task.file?.name;\n let current: VitestSuite | undefined = task.suite;\n\n while (current) {\n const name = current.name;\n if (\n name &&\n name.trim() !== '' &&\n name !== '<root>' &&\n name !== fileName &&\n !looksLikeFilePath(name)\n ) {\n path.unshift(name);\n }\n current = current.suite;\n }\n\n return path.length > 0 ? path : undefined;\n}\n\n/**\n * Normalize ticket option to array of NormalizedTicket objects.\n */\nfunction normalizeTickets(\n ticket: TicketInput | TicketInput[] | undefined,\n): NormalizedTicket[] | undefined {\n if (!ticket) return undefined;\n const arr = Array.isArray(ticket) ? ticket : [ticket];\n return arr.map((t) => (typeof t === 'string' ? { id: t } : t));\n}\n\n/**\n * Convert StoryDocs inline options to DocEntry array.\n * Matches the standalone DocApi method signatures.\n */\nfunction convertStoryDocsToEntries(docs: StoryDocs): DocEntry[] {\n const entries: DocEntry[] = [];\n\n // note(text)\n if (docs.note) {\n entries.push({ kind: 'note', text: docs.note, phase: 'runtime' });\n }\n\n // tag(name | names)\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\n // kv(label, value) - multiple pairs via Record\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\n // code(label, content, lang?)\n if (docs.code) {\n entries.push({\n kind: 'code',\n label: docs.code.label,\n content: docs.code.content,\n lang: docs.code.lang,\n phase: 'runtime',\n });\n }\n\n // json(label, value)\n if (docs.json) {\n entries.push({\n kind: 'code',\n label: docs.json.label,\n content: JSON.stringify(docs.json.value, null, 2),\n lang: 'json',\n phase: 'runtime',\n });\n }\n\n // table(label, columns, rows)\n if (docs.table) {\n entries.push({\n kind: 'table',\n label: docs.table.label,\n columns: docs.table.columns,\n rows: docs.table.rows,\n phase: 'runtime',\n });\n }\n\n // link(label, url)\n if (docs.link) {\n entries.push({\n kind: 'link',\n label: docs.link.label,\n url: docs.link.url,\n phase: 'runtime',\n });\n }\n\n // section(title, markdown)\n if (docs.section) {\n entries.push({\n kind: 'section',\n title: docs.section.title,\n markdown: docs.section.markdown,\n phase: 'runtime',\n });\n }\n\n // mermaid(code, title?)\n if (docs.mermaid) {\n entries.push({\n kind: 'mermaid',\n code: docs.mermaid.code,\n title: docs.mermaid.title,\n phase: 'runtime',\n });\n }\n\n // screenshot(path, alt?)\n if (docs.screenshot) {\n entries.push({\n kind: 'screenshot',\n path: docs.screenshot.path,\n alt: docs.screenshot.alt,\n phase: 'runtime',\n });\n }\n\n // custom(type, data)\n if (docs.custom) {\n entries.push({\n kind: 'custom',\n type: docs.custom.type,\n data: docs.custom.data,\n phase: 'runtime',\n });\n }\n\n return entries;\n}\n\n// ============================================================================\n// story.init()\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 task - The Vitest task object from ({ task }) => { ... }\n * @param options - Optional story configuration (tags, ticket, meta)\n *\n * @example\n * ```ts\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\n * // ... rest of test\n * });\n *\n * // With options:\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\nfunction init(task: TaskLike, options?: StoryOptions): void {\n const meta: StoryMeta = {\n scenario: task.name,\n steps: [],\n suitePath: extractSuitePath(task),\n tags: options?.tags,\n tickets: normalizeTickets(options?.ticket),\n meta: options?.meta,\n sourceOrder: sourceOrderCounter++,\n };\n\n // OTel bridge: detect active span, flow data bidirectionally\n const otelCtx = tryGetActiveOtelContext();\n if (otelCtx) {\n // OTel -> Story: capture traceId in structured meta\n meta.meta = { ...meta.meta, otel: { traceId: otelCtx.traceId, spanId: otelCtx.spanId } };\n\n // OTel -> Story: inject human-readable doc entries\n meta.docs = meta.docs ?? [];\n meta.docs.push({ kind: 'kv', label: 'Trace ID', value: otelCtx.traceId, phase: 'runtime' });\n\n const template = options?.traceUrlTemplate ?? process.env.OTEL_TRACE_URL_TEMPLATE;\n const url = resolveTraceUrl(template, otelCtx.traceId);\n if (url) {\n meta.docs.push({ kind: 'link', label: 'View Trace', url, phase: 'runtime' });\n }\n\n // Story -> OTel: enrich active span with story attributes\n try {\n const reqUrl = import.meta.url\n ?? (typeof __filename !== 'undefined' ? `file://${__filename}` : undefined);\n const req = createRequire(reqUrl!);\n const api = req('@opentelemetry/api');\n const span = api.trace?.getActiveSpan?.();\n if (span) {\n span.setAttribute('story.scenario', task.name);\n if (options?.tags?.length) span.setAttribute('story.tags', options.tags);\n if (options?.ticket) {\n const tickets = Array.isArray(options.ticket) ? options.ticket : [options.ticket];\n span.setAttribute('story.tickets', tickets.map((t) => typeof t === 'string' ? t : t.id));\n }\n }\n } catch { /* OTel not available */ }\n }\n\n // Attach to task.meta so reporter can find it\n task.meta.story = meta;\n\n // Set active context\n activeContext = {\n meta,\n currentStep: null,\n taskMeta: task.meta,\n stepCounter: 0,\n attachments: [],\n activeTimers: new Map(),\n timerCounter: 0,\n };\n}\n\n// ============================================================================\n// Step Markers\n// ============================================================================\n\n/**\n * Create a step marker function for a given keyword.\n */\nfunction createStepMarker(keyword: StepKeyword) {\n function stepMarker(text: string, docs?: StoryDocs): void;\n function stepMarker(text: string, children: DocEntry[]): void;\n function stepMarker<T>(text: string, body: () => T): T;\n function stepMarker<T>(text: string, docsOrBody?: StoryDocs | DocEntry[] | (() => T)): T | void {\n const ctx = getContext();\n const isCallback = typeof docsOrBody === 'function';\n const isChildrenArray = Array.isArray(docsOrBody);\n\n const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n let stepDocs: DocEntry[] = [];\n if (!isCallback && !isChildrenArray && docsOrBody) {\n stepDocs = convertStoryDocsToEntries(docsOrBody as StoryDocs);\n }\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: stepDocs,\n ...(isCallback ? { wrapped: true } : {}),\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncMetaToTask();\n\n // Handle DocEntry[] children: attach as step docs and deduplicate from story-level\n if (isChildrenArray) {\n const children = docsOrBody as DocEntry[];\n if (children.length > 0) {\n const childSet = new Set<DocEntry>(children);\n // Deduplicate from story-level docs\n ctx.meta.docs = (ctx.meta.docs ?? []).filter((d) => !childSet.has(d));\n // Deduplicate from step docs of earlier steps\n for (const prevStep of ctx.meta.steps) {\n if (prevStep !== step && prevStep.docs) {\n prevStep.docs = prevStep.docs.filter((d) => !childSet.has(d));\n }\n }\n step.docs = [...(step.docs ?? []), ...children];\n }\n syncMetaToTask();\n return;\n }\n\n if (!isCallback) return;\n\n const body = docsOrBody as () => T;\n const start = performance.now();\n\n try {\n const result = body();\n if (result instanceof Promise) {\n return result.then(\n (val) => { step.durationMs = performance.now() - start; syncMetaToTask(); return val; },\n (err) => { step.durationMs = performance.now() - start; syncMetaToTask(); throw err; },\n ) as T;\n }\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n }\n }\n return stepMarker;\n}\n\n// ============================================================================\n// Doc Methods (Standalone)\n// ============================================================================\n\n/**\n * Add a free-text note to the current step or story-level if before any step.\n */\nfunction note(text: string, children?: DocEntry[]): DocEntry {\n return attachDoc({ kind: 'note', text, phase: 'runtime' }, children);\n}\n\n// ============================================================================\n// Doc Method Types (shared between standalone and inline)\n// ============================================================================\n\n/** Options for kv() - key-value pair */\ninterface KvOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for json() - JSON code block */\ninterface JsonOptions {\n label: string;\n value: unknown;\n}\n\n/** Options for code() - code block with optional language */\ninterface CodeOptions {\n label: string;\n content: string;\n lang?: string;\n}\n\n/** Options for table() - markdown table */\ninterface TableOptions {\n label: string;\n columns: string[];\n rows: string[][];\n}\n\n/** Options for link() - hyperlink */\ninterface LinkOptions {\n label: string;\n url: string;\n}\n\n/** Options for section() - titled markdown section */\ninterface SectionOptions {\n title: string;\n markdown: string;\n}\n\n/** Options for mermaid() - Mermaid diagram */\ninterface MermaidOptions {\n code: string;\n title?: string;\n}\n\n/** Options for screenshot() - screenshot reference */\ninterface ScreenshotOptions {\n path: string;\n alt?: string;\n}\n\n/** Options for custom() - custom doc entry */\ninterface CustomOptions {\n type: string;\n data: unknown;\n}\n\n// ============================================================================\n// Helper to attach doc entry to current step or story-level\n// ============================================================================\n\nfunction attachDoc(entry: DocEntry, children?: DocEntry[]): DocEntry {\n const ctx = getContext();\n if (children && children.length > 0) {\n entry.children = children;\n const childSet = new Set<DocEntry>(children);\n const filterDocs = (docs: DocEntry[]) => docs.filter((d) => !childSet.has(d));\n // Remove children from ALL containers (story-level + every step)\n ctx.meta.docs = filterDocs(ctx.meta.docs ?? []);\n for (const step of ctx.meta.steps) {\n if (step.docs) step.docs = filterDocs(step.docs);\n }\n }\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 syncMetaToTask();\n return entry;\n}\n\n// ============================================================================\n// Doc Methods (Standalone) - same shape as inline docs\n// ============================================================================\n\n/**\n * Add a key-value pair to the current step or story-level.\n * @example story.kv({ label: 'Payment ID', value: 'pay_123' })\n */\nfunction kv(options: KvOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'kv',\n label: options.label,\n value: options.value,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a JSON code block to the current step or story-level.\n * @example story.json({ label: 'Order', value: { id: 123 } })\n */\nfunction json(options: JsonOptions, children?: DocEntry[]): DocEntry {\n const content = JSON.stringify(options.value, null, 2);\n return attachDoc({\n kind: 'code',\n label: options.label,\n content,\n lang: 'json',\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a code block with optional language to the current step or story-level.\n * @example story.code({ label: 'Config', content: 'port: 3000', lang: 'yaml' })\n */\nfunction code(options: CodeOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'code',\n label: options.label,\n content: options.content,\n lang: options.lang,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a markdown table to the current step or story-level.\n * @example story.table({ label: 'Users', columns: ['Name', 'Role'], rows: [['Alice', 'Admin']] })\n */\nfunction table(options: TableOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'table',\n label: options.label,\n columns: options.columns,\n rows: options.rows,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a hyperlink to the current step or story-level.\n * @example story.link({ label: 'API Docs', url: 'https://docs.example.com' })\n */\nfunction link(options: LinkOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'link',\n label: options.label,\n url: options.url,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a titled section with markdown content to the current step or story-level.\n * @example story.section({ title: 'Details', markdown: 'This is **important**' })\n */\nfunction section(options: SectionOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'section',\n title: options.title,\n markdown: options.markdown,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a Mermaid diagram to the current step or story-level.\n * @example story.mermaid({ code: 'graph LR; A-->B', title: 'Flow' })\n */\nfunction mermaid(options: MermaidOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'mermaid',\n code: options.code,\n title: options.title,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add a screenshot reference to the current step or story-level.\n * @example story.screenshot({ path: '/screenshots/result.png', alt: 'Final result' })\n */\nfunction screenshot(options: ScreenshotOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'screenshot',\n path: options.path,\n alt: options.alt,\n phase: 'runtime',\n }, children);\n}\n\n/**\n * Add tag(s) to the current step or story-level.\n * @example story.tag('admin') or story.tag(['admin', 'security'])\n */\nfunction tag(name: string | string[], children?: DocEntry[]): DocEntry {\n const names = Array.isArray(name) ? name : [name];\n return attachDoc({ kind: 'tag', names, phase: 'runtime' }, children);\n}\n\n/**\n * Add a custom documentation entry for use with custom renderers.\n * @example story.custom({ type: 'myType', data: { foo: 'bar' } })\n */\nfunction custom(options: CustomOptions, children?: DocEntry[]): DocEntry {\n return attachDoc({\n kind: 'custom',\n type: options.type,\n data: options.data,\n phase: 'runtime',\n }, children);\n}\n\n// ============================================================================\n// Attachments\n// ============================================================================\n\n/**\n * Attach a file or inline content to the current step or test case.\n * @example story.attach({ name: 'screenshot', mediaType: 'image/png', path: '/tmp/screenshot.png' })\n */\nfunction 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 // Store attachments on task.meta so reporter can read them\n if (ctx.taskMeta) {\n ctx.taskMeta.storyAttachments = ctx.attachments;\n }\n}\n\n// ============================================================================\n// Step Timing\n// ============================================================================\n\n/**\n * Start a timer for the current step. Returns a token to pass to endTimer().\n */\nfunction 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/**\n * End a timer and record duration on the step that was active when startTimer() was called.\n */\nfunction 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 syncMetaToTask();\n }\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 const resolvedKeyword: StepKeyword =\n (keyword === 'Given' || keyword === 'When' || keyword === 'Then') &&\n ctx.meta.steps.some((s) => s.keyword === keyword)\n ? 'And'\n : keyword;\n\n const step: StoryStep = {\n id: `step-${ctx.stepCounter++}`,\n keyword: resolvedKeyword,\n text,\n docs: [],\n wrapped: true,\n };\n\n ctx.meta.steps.push(step);\n ctx.currentStep = step;\n syncMetaToTask();\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 syncMetaToTask();\n return val;\n },\n (err) => {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n throw err;\n },\n ) as T;\n }\n\n step.durationMs = performance.now() - start;\n syncMetaToTask();\n return result;\n } catch (err) {\n step.durationMs = performance.now() - start;\n syncMetaToTask();\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 * await story.expect('async check', async () => { ... });\n * ```\n */\nfunction storyExpect<T>(text: string, body: () => T): T {\n return fn('Then', text, body);\n}\n\n// ============================================================================\n// Span attachment: story.attachSpans()\n// ============================================================================\n\n/**\n * Attach OTel spans to the current test so the StoryReporter renders them\n * as a trace waterfall in HTML reports.\n *\n * Accepts any array of objects with at least `spanId` and `name` fields.\n * Structurally compatible with autotel's `SerializedSpan` and the\n * `OtelSpan` type from executable-stories-formatters.\n *\n * @example\n * ```ts\n * import { serializeSpan } from 'autotel/test-span-collector';\n *\n * // After running code that creates spans:\n * const spans = exporter.getFinishedSpans().map(serializeSpan);\n * story.attachSpans(spans);\n * ```\n */\nfunction attachSpans(spans: ReadonlyArray<Record<string, unknown>>): void {\n const ctx = getContext();\n if (ctx.taskMeta) {\n ctx.taskMeta.otelSpans = spans;\n }\n}\n\n// ============================================================================\n// Export story object\n// ============================================================================\n\n/**\n * The main story API object.\n *\n * Use with native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\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 // Core\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,\n kv,\n json,\n code,\n table,\n link,\n section,\n mermaid,\n screenshot,\n tag,\n custom,\n\n // Attachments\n attach,\n\n // OTel span attachment\n attachSpans,\n\n // Step wrappers\n fn,\n expect: storyExpect,\n\n // Step timing\n startTimer,\n endTimer,\n};\n\nexport type Story = typeof story;\n","/**\n * Type definitions for executable-stories-vitest.\n *\n * Shared story types (StepKeyword, DocEntry, StoryStep, StoryMeta, etc.)\n * are imported from executable-stories-formatters — the single source of truth.\n * This module re-exports them and adds Vitest-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 NormalizedTicket,\n} from 'executable-stories-formatters';\n\nexport { STORY_META_KEY } from 'executable-stories-formatters';\n\n// ============================================================================\n// Ticket Input Types\n// ============================================================================\n\n/** A ticket reference: either a plain string ID or an object with id and optional url */\nexport type TicketInput = string | { id: string; url?: string };\n\n// ============================================================================\n// Vitest-specific Types\n// ============================================================================\n\n/**\n * Inline documentation options for step markers.\n * Pass to story.given(), story.when(), story.then() as second argument.\n *\n * @example\n * ```ts\n * story.given('valid credentials', {\n * json: { label: 'Credentials', value: { email: 'test@example.com', password: '***' } },\n * note: 'Password is masked for security'\n * });\n * ```\n */\nexport interface StoryDocs {\n /** Add a free-text note */\n note?: string;\n /** Add tag(s) for categorization */\n tag?: string | string[];\n /** Add key-value pairs */\n kv?: Record<string, unknown>;\n /** Add a code block with label and optional language */\n code?: { label: string; content: string; lang?: string };\n /** Add a JSON data block with label */\n json?: { label: string; value: unknown };\n /** Add a markdown table with label */\n table?: { label: string; columns: string[]; rows: string[][] };\n /** Add a hyperlink */\n link?: { label: string; url: string };\n /** Add a titled section with markdown content */\n section?: { title: string; markdown: string };\n /** Add a Mermaid diagram with optional title */\n mermaid?: { code: string; title?: string };\n /** Add a screenshot reference */\n screenshot?: { path: string; alt?: string };\n /** Add a custom documentation entry */\n custom?: { type: string; data: unknown };\n}\n\n/**\n * Options for configuring a story via story.init().\n *\n * @example\n * ```ts\n * it('admin deletes user', ({ task }) => {\n * story.init(task, {\n * tags: ['admin', 'destructive'],\n * ticket: 'JIRA-456'\n * });\n * });\n * ```\n */\nexport interface StoryOptions {\n /** Tags for filtering and categorizing stories */\n tags?: string[];\n /** Ticket/issue reference(s) for requirements traceability */\n ticket?: TicketInput | TicketInput[];\n /** Arbitrary user-defined metadata */\n meta?: Record<string, unknown>;\n /** URL template for OTel trace links. Uses {traceId} placeholder. Also settable via OTEL_TRACE_URL_TEMPLATE env var. */\n traceUrlTemplate?: string;\n}\n\n// ============================================================================\n// Vitest Task Type (minimal interface)\n// ============================================================================\n\n/** Minimal Vitest suite interface for suite path extraction */\nexport interface VitestSuite {\n /** Suite name */\n name?: string;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n}\n\n/**\n * Minimal Vitest task interface for story.init().\n * This is the { task } from it('name', ({ task }) => { ... }).\n *\n * Uses generic type parameter to be compatible with Vitest's actual TaskMeta type.\n */\nexport interface VitestTask<TMeta = Record<string, unknown>> {\n /** The test/task name */\n name: string;\n /** Task metadata object where we store story data */\n meta: TMeta;\n /** Parent suite (optional) */\n suite?: VitestSuite;\n /** The test file (optional) */\n file?: { name?: string };\n}\n","/**\n * executable-stories-vitest: Native Vitest story/given/when/then with Markdown doc generation.\n *\n * Uses native Vitest describe/it/test for full IDE support:\n *\n * @example\n * ```ts\n * import { describe, it, expect } from 'vitest';\n * import { story } from 'executable-stories-vitest';\n *\n * describe('Calculator', () => {\n * it('adds two numbers', ({ task }) => {\n * story.init(task);\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 *\n * In vitest.config, import StoryReporter from \"executable-stories-vitest/reporter\":\n *\n * @example\n * ```ts\n * import { defineConfig } from \"vitest/config\";\n * import { StoryReporter } from \"executable-stories-vitest/reporter\";\n *\n * export default defineConfig({\n * test: {\n * reporters: [\"default\", new StoryReporter()],\n * },\n * });\n * ```\n */\n\n// Core API\nexport { story, type Story } from './story-api';\n\n// Types for consumers\nexport type {\n StoryMeta,\n StoryStep,\n DocEntry,\n StepKeyword,\n StepMode,\n DocPhase,\n NormalizedTicket,\n TicketInput,\n StoryDocs,\n StoryOptions,\n VitestTask,\n VitestSuite,\n} from './types';\n\nexport { STORY_META_KEY } from './types';\n\n// Reporter types (actual reporter is in /reporter subpath)\nexport type {\n StoryReporterOptions,\n OutputFormat,\n OutputMode,\n ColocatedStyle,\n OutputRule,\n FormatterOptions,\n} from './reporter';\n\nconst STORY_REPORTER_GUARD_MSG =\n 'Do not import StoryReporter from \"executable-stories-vitest\". In vitest.config, import it from \"executable-stories-vitest/reporter\".';\n\n/** @internal Guard: throws if used. Import StoryReporter from \"executable-stories-vitest/reporter\" in vitest.config. */\nexport class StoryReporter {\n static __isGuard = true;\n constructor() {\n throw new Error(STORY_REPORTER_GUARD_MSG);\n }\n}\n"],"mappings":"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","names":[]}
|