executable-stories-vitest 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.
@@ -0,0 +1,447 @@
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';
3
+ export { StoryReporterOptions } from './reporter.js';
4
+ import 'vitest/node';
5
+
6
+ /**
7
+ * Type definitions for executable-stories-vitest.
8
+ *
9
+ * Shared story types (StepKeyword, DocEntry, StoryStep, StoryMeta, etc.)
10
+ * are imported from executable-stories-formatters — the single source of truth.
11
+ * This module re-exports them and adds Vitest-specific types.
12
+ */
13
+
14
+ /**
15
+ * Inline documentation options for step markers.
16
+ * Pass to story.given(), story.when(), story.then() as second argument.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * story.given('valid credentials', {
21
+ * json: { label: 'Credentials', value: { email: 'test@example.com', password: '***' } },
22
+ * note: 'Password is masked for security'
23
+ * });
24
+ * ```
25
+ */
26
+ interface StoryDocs {
27
+ /** Add a free-text note */
28
+ note?: string;
29
+ /** Add tag(s) for categorization */
30
+ tag?: string | string[];
31
+ /** Add key-value pairs */
32
+ kv?: Record<string, unknown>;
33
+ /** Add a code block with label and optional language */
34
+ code?: {
35
+ label: string;
36
+ content: string;
37
+ lang?: string;
38
+ };
39
+ /** Add a JSON data block with label */
40
+ json?: {
41
+ label: string;
42
+ value: unknown;
43
+ };
44
+ /** Add a markdown table with label */
45
+ table?: {
46
+ label: string;
47
+ columns: string[];
48
+ rows: string[][];
49
+ };
50
+ /** Add a hyperlink */
51
+ link?: {
52
+ label: string;
53
+ url: string;
54
+ };
55
+ /** Add a titled section with markdown content */
56
+ section?: {
57
+ title: string;
58
+ markdown: string;
59
+ };
60
+ /** Add a Mermaid diagram with optional title */
61
+ mermaid?: {
62
+ code: string;
63
+ title?: string;
64
+ };
65
+ /** Add a screenshot reference */
66
+ screenshot?: {
67
+ path: string;
68
+ alt?: string;
69
+ };
70
+ /** Add a custom documentation entry */
71
+ custom?: {
72
+ type: string;
73
+ data: unknown;
74
+ };
75
+ }
76
+ /**
77
+ * Options for configuring a story via story.init().
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * it('admin deletes user', ({ task }) => {
82
+ * story.init(task, {
83
+ * tags: ['admin', 'destructive'],
84
+ * ticket: 'JIRA-456'
85
+ * });
86
+ * });
87
+ * ```
88
+ */
89
+ interface StoryOptions {
90
+ /** Tags for filtering and categorizing stories */
91
+ tags?: string[];
92
+ /** Ticket/issue reference(s) for requirements traceability */
93
+ ticket?: string | string[];
94
+ /** Arbitrary user-defined metadata */
95
+ meta?: Record<string, unknown>;
96
+ }
97
+ /** Minimal Vitest suite interface for suite path extraction */
98
+ interface VitestSuite {
99
+ /** Suite name */
100
+ name?: string;
101
+ /** Parent suite (optional) */
102
+ suite?: VitestSuite;
103
+ }
104
+ /**
105
+ * Minimal Vitest task interface for story.init().
106
+ * This is the { task } from it('name', ({ task }) => { ... }).
107
+ *
108
+ * Uses generic type parameter to be compatible with Vitest's actual TaskMeta type.
109
+ */
110
+ interface VitestTask<TMeta = Record<string, unknown>> {
111
+ /** The test/task name */
112
+ name: string;
113
+ /** Task metadata object where we store story data */
114
+ meta: TMeta;
115
+ /** Parent suite (optional) */
116
+ suite?: VitestSuite;
117
+ /** The test file (optional) */
118
+ file?: {
119
+ name?: string;
120
+ };
121
+ }
122
+
123
+ /**
124
+ * story.* API for executable-stories-vitest.
125
+ *
126
+ * Uses native Vitest describe/it/test with opt-in documentation:
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * import { describe, it, expect } from 'vitest';
131
+ * import { story } from 'executable-stories-vitest';
132
+ *
133
+ * describe('Calculator', () => {
134
+ * it('adds two numbers', ({ task }) => {
135
+ * story.init(task);
136
+ *
137
+ * story.given('two numbers 5 and 3');
138
+ * const a = 5;
139
+ * const b = 3;
140
+ *
141
+ * story.when('I add them together');
142
+ * const result = a + b;
143
+ *
144
+ * story.then('the result is 8');
145
+ * expect(result).toBe(8);
146
+ * });
147
+ * });
148
+ * ```
149
+ */
150
+
151
+ /**
152
+ * Minimal task interface compatible with Vitest's Test type.
153
+ * The meta property accepts any object type to be compatible with Vitest's TaskMeta.
154
+ */
155
+ interface TaskLike {
156
+ name: string;
157
+ meta: any;
158
+ suite?: VitestSuite;
159
+ file?: {
160
+ name?: string;
161
+ };
162
+ }
163
+ /** Attachment options for story.attach() */
164
+ interface AttachmentOptions {
165
+ name: string;
166
+ mediaType: string;
167
+ path?: string;
168
+ body?: string;
169
+ encoding?: "BASE64" | "IDENTITY";
170
+ charset?: string;
171
+ fileName?: string;
172
+ }
173
+ /**
174
+ * Initialize a story for the current test.
175
+ * Must be called at the start of each test that wants documentation.
176
+ *
177
+ * @param task - The Vitest task object from ({ task }) => { ... }
178
+ * @param options - Optional story configuration (tags, ticket, meta)
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * it('adds two numbers', ({ task }) => {
183
+ * story.init(task);
184
+ * // ... rest of test
185
+ * });
186
+ *
187
+ * // With options:
188
+ * it('admin deletes user', ({ task }) => {
189
+ * story.init(task, {
190
+ * tags: ['admin', 'destructive'],
191
+ * ticket: 'JIRA-456'
192
+ * });
193
+ * });
194
+ * ```
195
+ */
196
+ declare function init(task: TaskLike, options?: StoryOptions): void;
197
+ /**
198
+ * Add a free-text note to the current step or story-level if before any step.
199
+ */
200
+ declare function note(text: string): void;
201
+ /** Options for kv() - key-value pair */
202
+ interface KvOptions {
203
+ label: string;
204
+ value: unknown;
205
+ }
206
+ /** Options for json() - JSON code block */
207
+ interface JsonOptions {
208
+ label: string;
209
+ value: unknown;
210
+ }
211
+ /** Options for code() - code block with optional language */
212
+ interface CodeOptions {
213
+ label: string;
214
+ content: string;
215
+ lang?: string;
216
+ }
217
+ /** Options for table() - markdown table */
218
+ interface TableOptions {
219
+ label: string;
220
+ columns: string[];
221
+ rows: string[][];
222
+ }
223
+ /** Options for link() - hyperlink */
224
+ interface LinkOptions {
225
+ label: string;
226
+ url: string;
227
+ }
228
+ /** Options for section() - titled markdown section */
229
+ interface SectionOptions {
230
+ title: string;
231
+ markdown: string;
232
+ }
233
+ /** Options for mermaid() - Mermaid diagram */
234
+ interface MermaidOptions {
235
+ code: string;
236
+ title?: string;
237
+ }
238
+ /** Options for screenshot() - screenshot reference */
239
+ interface ScreenshotOptions {
240
+ path: string;
241
+ alt?: string;
242
+ }
243
+ /** Options for custom() - custom doc entry */
244
+ interface CustomOptions {
245
+ type: string;
246
+ data: unknown;
247
+ }
248
+ /**
249
+ * Add a key-value pair to the current step or story-level.
250
+ * @example story.kv({ label: 'Payment ID', value: 'pay_123' })
251
+ */
252
+ declare function kv(options: KvOptions): void;
253
+ /**
254
+ * Add a JSON code block to the current step or story-level.
255
+ * @example story.json({ label: 'Order', value: { id: 123 } })
256
+ */
257
+ declare function json(options: JsonOptions): void;
258
+ /**
259
+ * Add a code block with optional language to the current step or story-level.
260
+ * @example story.code({ label: 'Config', content: 'port: 3000', lang: 'yaml' })
261
+ */
262
+ declare function code(options: CodeOptions): void;
263
+ /**
264
+ * Add a markdown table to the current step or story-level.
265
+ * @example story.table({ label: 'Users', columns: ['Name', 'Role'], rows: [['Alice', 'Admin']] })
266
+ */
267
+ declare function table(options: TableOptions): void;
268
+ /**
269
+ * Add a hyperlink to the current step or story-level.
270
+ * @example story.link({ label: 'API Docs', url: 'https://docs.example.com' })
271
+ */
272
+ declare function link(options: LinkOptions): void;
273
+ /**
274
+ * Add a titled section with markdown content to the current step or story-level.
275
+ * @example story.section({ title: 'Details', markdown: 'This is **important**' })
276
+ */
277
+ declare function section(options: SectionOptions): void;
278
+ /**
279
+ * Add a Mermaid diagram to the current step or story-level.
280
+ * @example story.mermaid({ code: 'graph LR; A-->B', title: 'Flow' })
281
+ */
282
+ declare function mermaid(options: MermaidOptions): void;
283
+ /**
284
+ * Add a screenshot reference to the current step or story-level.
285
+ * @example story.screenshot({ path: '/screenshots/result.png', alt: 'Final result' })
286
+ */
287
+ declare function screenshot(options: ScreenshotOptions): void;
288
+ /**
289
+ * Add tag(s) to the current step or story-level.
290
+ * @example story.tag('admin') or story.tag(['admin', 'security'])
291
+ */
292
+ declare function tag(name: string | string[]): void;
293
+ /**
294
+ * Add a custom documentation entry for use with custom renderers.
295
+ * @example story.custom({ type: 'myType', data: { foo: 'bar' } })
296
+ */
297
+ declare function custom(options: CustomOptions): void;
298
+ /**
299
+ * Attach a file or inline content to the current step or test case.
300
+ * @example story.attach({ name: 'screenshot', mediaType: 'image/png', path: '/tmp/screenshot.png' })
301
+ */
302
+ declare function attach(options: AttachmentOptions): void;
303
+ /**
304
+ * Start a timer for the current step. Returns a token to pass to endTimer().
305
+ */
306
+ declare function startTimer(): number;
307
+ /**
308
+ * End a timer and record duration on the step that was active when startTimer() was called.
309
+ */
310
+ declare function endTimer(token: number): void;
311
+ /**
312
+ * Wrap a function body as a step. Records the step with timing and `wrapped: true`.
313
+ * Supports both sync and async functions. Returns whatever the function returns.
314
+ *
315
+ * @param keyword - The BDD keyword (Given, When, Then, And, But)
316
+ * @param text - Step description
317
+ * @param body - The function to execute
318
+ * @returns The return value of body (or a Promise of it if body is async)
319
+ *
320
+ * @example
321
+ * ```ts
322
+ * const data = story.fn('Given', 'setup data', () => ({ a: 5, b: 3 }));
323
+ * const result = await story.fn('When', 'call API', async () => fetch('/api'));
324
+ * ```
325
+ */
326
+ declare function fn<T>(keyword: StepKeyword, text: string, body: () => T): T;
327
+ /**
328
+ * Wrap an assertion as a Then step. Shorthand for `story.fn('Then', text, body)`.
329
+ *
330
+ * @param text - Step description
331
+ * @param body - The assertion function to execute
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * story.expect('the result is 8', () => { expect(result).toBe(8); });
336
+ * await story.expect('async check', async () => { ... });
337
+ * ```
338
+ */
339
+ declare function storyExpect<T>(text: string, body: () => T): T;
340
+ /**
341
+ * The main story API object.
342
+ *
343
+ * Use with native Vitest describe/it/test for full IDE support:
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * import { describe, it, expect } from 'vitest';
348
+ * import { story } from 'executable-stories-vitest';
349
+ *
350
+ * describe('Calculator', () => {
351
+ * it('adds two numbers', ({ task }) => {
352
+ * story.init(task);
353
+ *
354
+ * story.given('two numbers 5 and 3');
355
+ * const a = 5, b = 3;
356
+ *
357
+ * story.when('I add them together');
358
+ * const result = a + b;
359
+ *
360
+ * story.then('the result is 8');
361
+ * expect(result).toBe(8);
362
+ * });
363
+ * });
364
+ * ```
365
+ */
366
+ declare const story: {
367
+ init: typeof init;
368
+ given: (text: string, docs?: StoryDocs) => void;
369
+ when: (text: string, docs?: StoryDocs) => void;
370
+ then: (text: string, docs?: StoryDocs) => void;
371
+ and: (text: string, docs?: StoryDocs) => void;
372
+ but: (text: string, docs?: StoryDocs) => void;
373
+ arrange: (text: string, docs?: StoryDocs) => void;
374
+ act: (text: string, docs?: StoryDocs) => void;
375
+ assert: (text: string, docs?: StoryDocs) => void;
376
+ setup: (text: string, docs?: StoryDocs) => void;
377
+ context: (text: string, docs?: StoryDocs) => void;
378
+ execute: (text: string, docs?: StoryDocs) => void;
379
+ action: (text: string, docs?: StoryDocs) => void;
380
+ verify: (text: string, docs?: StoryDocs) => void;
381
+ note: typeof note;
382
+ kv: typeof kv;
383
+ json: typeof json;
384
+ code: typeof code;
385
+ table: typeof table;
386
+ link: typeof link;
387
+ section: typeof section;
388
+ mermaid: typeof mermaid;
389
+ screenshot: typeof screenshot;
390
+ tag: typeof tag;
391
+ custom: typeof custom;
392
+ attach: typeof attach;
393
+ fn: typeof fn;
394
+ expect: typeof storyExpect;
395
+ startTimer: typeof startTimer;
396
+ endTimer: typeof endTimer;
397
+ };
398
+ type Story = typeof story;
399
+
400
+ /**
401
+ * executable-stories-vitest: Native Vitest story/given/when/then with Markdown doc generation.
402
+ *
403
+ * Uses native Vitest describe/it/test for full IDE support:
404
+ *
405
+ * @example
406
+ * ```ts
407
+ * import { describe, it, expect } from 'vitest';
408
+ * import { story } from 'executable-stories-vitest';
409
+ *
410
+ * describe('Calculator', () => {
411
+ * it('adds two numbers', ({ task }) => {
412
+ * story.init(task);
413
+ *
414
+ * story.given('two numbers 5 and 3');
415
+ * const a = 5, b = 3;
416
+ *
417
+ * story.when('I add them together');
418
+ * const result = a + b;
419
+ *
420
+ * story.then('the result is 8');
421
+ * expect(result).toBe(8);
422
+ * });
423
+ * });
424
+ * ```
425
+ *
426
+ * In vitest.config, import StoryReporter from "executable-stories-vitest/reporter":
427
+ *
428
+ * @example
429
+ * ```ts
430
+ * import { defineConfig } from "vitest/config";
431
+ * import { StoryReporter } from "executable-stories-vitest/reporter";
432
+ *
433
+ * export default defineConfig({
434
+ * test: {
435
+ * reporters: ["default", new StoryReporter()],
436
+ * },
437
+ * });
438
+ * ```
439
+ */
440
+
441
+ /** @internal Guard: throws if used. Import StoryReporter from "executable-stories-vitest/reporter" in vitest.config. */
442
+ declare class StoryReporter {
443
+ static __isGuard: boolean;
444
+ constructor();
445
+ }
446
+
447
+ export { type Story, type StoryDocs, type StoryOptions, StoryReporter, type VitestSuite, type VitestTask, story };