executable-stories-vitest 7.0.1 → 7.0.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "executable-stories-vitest",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.2",
|
|
4
4
|
"description": "TS-first story/given/when/then helpers for Vitest with Markdown user-story doc generation.",
|
|
5
5
|
"author": "Jag Reehal <jag@jagreehal.com>",
|
|
6
6
|
"homepage": "https://github.com/jagreehal/executable-stories#readme",
|
|
@@ -32,11 +32,13 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"dist",
|
|
35
|
-
"
|
|
35
|
+
"skills",
|
|
36
|
+
"README.md",
|
|
37
|
+
"bin"
|
|
36
38
|
],
|
|
37
39
|
"peerDependencies": {
|
|
38
40
|
"vitest": ">=4.0.18",
|
|
39
|
-
"executable-stories-formatters": "^0.6.
|
|
41
|
+
"executable-stories-formatters": "^0.6.2"
|
|
40
42
|
},
|
|
41
43
|
"devDependencies": {
|
|
42
44
|
"@opentelemetry/api": "^1.9.0",
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
"typescript": "^5.9.3",
|
|
47
49
|
"vitest": "^4.0.18",
|
|
48
50
|
"eslint-config-executable-stories": "0.2.0",
|
|
49
|
-
"executable-stories-formatters": "0.6.
|
|
51
|
+
"executable-stories-formatters": "0.6.2"
|
|
50
52
|
},
|
|
51
53
|
"keywords": [
|
|
52
54
|
"vitest",
|
|
@@ -64,6 +66,9 @@
|
|
|
64
66
|
"fast-glob": "^3.3.3",
|
|
65
67
|
"picomatch": "^4.0.3"
|
|
66
68
|
},
|
|
69
|
+
"bin": {
|
|
70
|
+
"intent": "./bin/intent.js"
|
|
71
|
+
},
|
|
67
72
|
"scripts": {
|
|
68
73
|
"build": "tsup",
|
|
69
74
|
"type-check": "tsc --noEmit",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vitest-converting-tests
|
|
3
|
+
description: >
|
|
4
|
+
Incrementally adopt executable-stories in Vitest. Add story.init(task) and
|
|
5
|
+
step markers to existing it/test blocks. Use doc.story for framework-native
|
|
6
|
+
tests. File naming .story.test.ts. No rewrite needed — progressive
|
|
7
|
+
enhancement of existing tests.
|
|
8
|
+
type: lifecycle
|
|
9
|
+
library: executable-stories-vitest
|
|
10
|
+
library_version: "7.0.1"
|
|
11
|
+
requires:
|
|
12
|
+
- vitest-story-api
|
|
13
|
+
sources:
|
|
14
|
+
- "jagreehal/executable-stories:apps/docs-site/src/content/docs/guides/converting-vitest.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
This skill builds on vitest-story-api. Read vitest-story-api first.
|
|
18
|
+
|
|
19
|
+
# Converting Existing Vitest Tests
|
|
20
|
+
|
|
21
|
+
## Setup
|
|
22
|
+
|
|
23
|
+
Install the package:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -D executable-stories-vitest executable-stories-formatters
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Core Patterns
|
|
30
|
+
|
|
31
|
+
### Step 1: Rename the file
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
# Before
|
|
35
|
+
test/calculator.test.ts
|
|
36
|
+
|
|
37
|
+
# After
|
|
38
|
+
test/calculator.story.test.ts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The reporter filters for `.story.test.ts` files.
|
|
42
|
+
|
|
43
|
+
### Step 2: Add story.init() and step markers
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
// Before
|
|
47
|
+
import { describe, expect, it } from "vitest";
|
|
48
|
+
|
|
49
|
+
describe("Calculator", () => {
|
|
50
|
+
it("adds two numbers", () => {
|
|
51
|
+
const result = add(2, 3);
|
|
52
|
+
expect(result).toBe(5);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// After
|
|
59
|
+
import { describe, expect, it } from "vitest";
|
|
60
|
+
import { story } from "executable-stories-vitest";
|
|
61
|
+
|
|
62
|
+
describe("Calculator", () => {
|
|
63
|
+
it("adds two numbers", ({ task }) => {
|
|
64
|
+
story.init(task);
|
|
65
|
+
|
|
66
|
+
story.given("two numbers 2 and 3");
|
|
67
|
+
const a = 2, b = 3;
|
|
68
|
+
|
|
69
|
+
story.when("they are added");
|
|
70
|
+
const result = add(a, b);
|
|
71
|
+
|
|
72
|
+
story.then("the result is 5");
|
|
73
|
+
expect(result).toBe(5);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Key change: add `({ task })` to the callback parameter.
|
|
79
|
+
|
|
80
|
+
### Step 3: Minimal story (test name only)
|
|
81
|
+
|
|
82
|
+
If you want a test to appear in docs without step markers:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
it("subtracts two numbers", ({ task }) => {
|
|
86
|
+
story.init(task);
|
|
87
|
+
// Test runs normally, appears in report with test name as scenario title
|
|
88
|
+
expect(subtract(10, 4)).toBe(6);
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Step 4: Add doc entries for richer output
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
it("handles division by zero", ({ task }) => {
|
|
96
|
+
story.init(task, { tags: ["edge-case"] });
|
|
97
|
+
|
|
98
|
+
story.given("a divisor of zero");
|
|
99
|
+
story.note("This tests the error handling path");
|
|
100
|
+
|
|
101
|
+
story.when("division is attempted");
|
|
102
|
+
const fn = () => divide(10, 0);
|
|
103
|
+
|
|
104
|
+
story.then("an error is thrown");
|
|
105
|
+
expect(fn).toThrow("Division by zero");
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Progressive adoption
|
|
110
|
+
|
|
111
|
+
Convert tests one file at a time. Non-story tests continue working normally. The reporter only processes files matching `*.story.test.ts` with `story.init()` calls.
|
|
112
|
+
|
|
113
|
+
## Common Mistakes
|
|
114
|
+
|
|
115
|
+
### HIGH Forgetting ({ task }) in callback
|
|
116
|
+
|
|
117
|
+
Wrong:
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
it("my test", () => {
|
|
121
|
+
story.init(task); // ReferenceError: task is not defined
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Correct:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
it("my test", ({ task }) => {
|
|
129
|
+
story.init(task);
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Vitest passes the test context as the first callback argument. You must destructure `task` from it.
|
|
134
|
+
|
|
135
|
+
Source: packages/executable-stories-vitest/src/story-api.ts
|
|
136
|
+
|
|
137
|
+
### MEDIUM Using wrong file extension
|
|
138
|
+
|
|
139
|
+
Wrong: `calculator.story.spec.ts` (Playwright convention)
|
|
140
|
+
Correct: `calculator.story.test.ts` (Vitest convention)
|
|
141
|
+
|
|
142
|
+
Source: CLAUDE.md — file naming conventions
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vitest-reporter-setup
|
|
3
|
+
description: >
|
|
4
|
+
Configure StoryReporter in vitest.config.ts for executable-stories-vitest.
|
|
5
|
+
Import from executable-stories-vitest/reporter subpath. OutputConfig with
|
|
6
|
+
formats, outputDir, outputName. Output modes: aggregated, colocated
|
|
7
|
+
(mirrored/adjacent). Markdown, HTML, JUnit, Cucumber JSON options.
|
|
8
|
+
GitHub Actions summary. rawRunPath for CLI consumption.
|
|
9
|
+
type: core
|
|
10
|
+
library: executable-stories-vitest
|
|
11
|
+
library_version: "7.0.1"
|
|
12
|
+
sources:
|
|
13
|
+
- "jagreehal/executable-stories:packages/executable-stories-vitest/src/reporter.ts"
|
|
14
|
+
- "jagreehal/executable-stories:apps/docs-site/src/content/docs/vitest/vitest-config.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# executable-stories-vitest — Reporter Setup
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// vitest.config.ts
|
|
23
|
+
import { defineConfig } from "vitest/config";
|
|
24
|
+
import { StoryReporter } from "executable-stories-vitest/reporter";
|
|
25
|
+
|
|
26
|
+
export default defineConfig({
|
|
27
|
+
test: {
|
|
28
|
+
reporters: [
|
|
29
|
+
"default",
|
|
30
|
+
new StoryReporter({
|
|
31
|
+
formats: ["markdown", "html"],
|
|
32
|
+
outputDir: "docs",
|
|
33
|
+
outputName: "user-stories",
|
|
34
|
+
}),
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Peer dependency: `executable-stories-formatters` must be installed.
|
|
41
|
+
|
|
42
|
+
## Core Patterns
|
|
43
|
+
|
|
44
|
+
### Output modes
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Aggregated (default) — one file per format
|
|
48
|
+
new StoryReporter({
|
|
49
|
+
formats: ["markdown"],
|
|
50
|
+
outputDir: "docs",
|
|
51
|
+
outputName: "user-stories",
|
|
52
|
+
output: { mode: "aggregated" },
|
|
53
|
+
})
|
|
54
|
+
// → docs/user-stories.md
|
|
55
|
+
|
|
56
|
+
// Colocated mirrored — one file per source, directory mirrored
|
|
57
|
+
new StoryReporter({
|
|
58
|
+
formats: ["markdown"],
|
|
59
|
+
outputDir: "docs",
|
|
60
|
+
output: { mode: "colocated", colocatedStyle: "mirrored" },
|
|
61
|
+
})
|
|
62
|
+
// test/auth/login.story.test.ts → docs/test/auth/login.story.md
|
|
63
|
+
|
|
64
|
+
// Colocated adjacent — written next to the test file
|
|
65
|
+
new StoryReporter({
|
|
66
|
+
formats: ["markdown"],
|
|
67
|
+
output: { mode: "colocated", colocatedStyle: "adjacent" },
|
|
68
|
+
})
|
|
69
|
+
// test/auth/login.story.test.ts → test/auth/login.story.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Format-specific options
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
new StoryReporter({
|
|
76
|
+
formats: ["markdown", "html", "junit", "cucumber-json"],
|
|
77
|
+
outputDir: "reports",
|
|
78
|
+
markdown: {
|
|
79
|
+
title: "User Stories",
|
|
80
|
+
includeStatusIcons: true,
|
|
81
|
+
includeErrors: true,
|
|
82
|
+
includeMetadata: true,
|
|
83
|
+
sortScenarios: "source",
|
|
84
|
+
ticketUrlTemplate: "https://jira.example.com/browse/{ticket}",
|
|
85
|
+
},
|
|
86
|
+
html: {
|
|
87
|
+
title: "Test Report",
|
|
88
|
+
darkMode: true,
|
|
89
|
+
searchable: true,
|
|
90
|
+
startCollapsed: false,
|
|
91
|
+
embedScreenshots: true,
|
|
92
|
+
},
|
|
93
|
+
junit: {
|
|
94
|
+
suiteName: "My Test Suite",
|
|
95
|
+
includeOutput: true,
|
|
96
|
+
},
|
|
97
|
+
cucumberJson: { pretty: true },
|
|
98
|
+
})
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Pattern-based output rules
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
new StoryReporter({
|
|
105
|
+
formats: ["markdown"],
|
|
106
|
+
output: {
|
|
107
|
+
mode: "aggregated",
|
|
108
|
+
rules: [
|
|
109
|
+
{
|
|
110
|
+
match: "test/api/**",
|
|
111
|
+
mode: "colocated",
|
|
112
|
+
colocatedStyle: "adjacent",
|
|
113
|
+
formats: ["markdown", "html"],
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
match: "test/e2e/**",
|
|
117
|
+
outputDir: "docs/e2e",
|
|
118
|
+
outputName: "e2e-stories",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Raw run output for CLI
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
new StoryReporter({
|
|
129
|
+
formats: ["markdown"],
|
|
130
|
+
rawRunPath: "reports/raw-run.json",
|
|
131
|
+
enableGithubActionsSummary: true,
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Common Mistakes
|
|
136
|
+
|
|
137
|
+
### HIGH Importing StoryReporter from main package entry
|
|
138
|
+
|
|
139
|
+
Wrong:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { StoryReporter } from "executable-stories-vitest";
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Correct:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { StoryReporter } from "executable-stories-vitest/reporter";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The main entry exports a guard class that throws at construction time. The real `StoryReporter` lives at the `/reporter` subpath to keep heavy formatter dependencies out of test code.
|
|
152
|
+
|
|
153
|
+
Source: packages/executable-stories-vitest/src/index.ts
|
|
154
|
+
|
|
155
|
+
### HIGH Passing a string instead of OutputConfig object
|
|
156
|
+
|
|
157
|
+
Wrong:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
new StoryReporter({ output: "docs/user-stories.md" })
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Correct:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
new StoryReporter({
|
|
167
|
+
formats: ["markdown"],
|
|
168
|
+
outputDir: "docs",
|
|
169
|
+
outputName: "user-stories",
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The `output` property expects an `OutputConfig` object with `mode`, `colocatedStyle`, and `rules`. A string is silently treated as an object with all `undefined` fields, falling back to defaults.
|
|
174
|
+
|
|
175
|
+
Source: packages/executable-stories-vitest/src/reporter.ts
|
|
176
|
+
|
|
177
|
+
### MEDIUM Default format is cucumber-json, not markdown
|
|
178
|
+
|
|
179
|
+
Wrong assumption:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Expecting markdown output
|
|
183
|
+
new StoryReporter({ outputDir: "docs" })
|
|
184
|
+
// → docs/test-results.cucumber.json (not .md)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Correct:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
new StoryReporter({
|
|
191
|
+
formats: ["markdown"],
|
|
192
|
+
outputDir: "docs",
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The default format is `["cucumber-json"]`, not `["markdown"]`. Always specify `formats` explicitly.
|
|
197
|
+
|
|
198
|
+
Source: packages/executable-stories-vitest/src/reporter.ts
|
|
199
|
+
|
|
200
|
+
See also: vitest-story-api/SKILL.md — Stories need the reporter to produce output
|
|
201
|
+
See also: formatters-cli/SKILL.md — Reporter produces RawRun that CLI consumes
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: vitest-story-api
|
|
3
|
+
description: >
|
|
4
|
+
Write BDD stories in Vitest using executable-stories-vitest. Callback-only
|
|
5
|
+
API: story.init(task) with ({ task }) destructuring. Steps: given, when,
|
|
6
|
+
then, and, but. Doc entries: json, kv, code, table, link, section, mermaid,
|
|
7
|
+
note, tag, screenshot, custom. Auto-And keyword conversion. No top-level
|
|
8
|
+
then export. Aliases: arrange, act, assert. Inline docs via second argument.
|
|
9
|
+
type: core
|
|
10
|
+
library: executable-stories-vitest
|
|
11
|
+
library_version: "7.0.1"
|
|
12
|
+
sources:
|
|
13
|
+
- "jagreehal/executable-stories:packages/executable-stories-vitest/src/story-api.ts"
|
|
14
|
+
- "jagreehal/executable-stories:apps/docs-site/src/content/docs/vitest/vitest-story-api.md"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# executable-stories-vitest — Story API
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { describe, expect, it } from "vitest";
|
|
23
|
+
import { story } from "executable-stories-vitest";
|
|
24
|
+
|
|
25
|
+
describe("Cart checkout", () => {
|
|
26
|
+
it("applies discount code", ({ task }) => {
|
|
27
|
+
story.init(task, { tags: ["checkout"], ticket: "CART-42" });
|
|
28
|
+
|
|
29
|
+
story.given("a cart with items totaling $100");
|
|
30
|
+
const cart = createCart([{ name: "Shirt", price: 100 }]);
|
|
31
|
+
|
|
32
|
+
story.when("a 20% discount code is applied");
|
|
33
|
+
applyDiscount(cart, "SAVE20");
|
|
34
|
+
|
|
35
|
+
story.then("the total is $80");
|
|
36
|
+
expect(cart.total).toBe(80);
|
|
37
|
+
|
|
38
|
+
story.and("the discount is shown in the summary");
|
|
39
|
+
expect(cart.discounts).toHaveLength(1);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
File naming: `*.story.test.ts` or `*.story.spec.ts`.
|
|
45
|
+
|
|
46
|
+
## Core Patterns
|
|
47
|
+
|
|
48
|
+
### Step markers with Auto-And conversion
|
|
49
|
+
|
|
50
|
+
First call to `given()`, `when()`, or `then()` renders the keyword as-is. Subsequent calls to the same keyword in the same story auto-convert to "And". Explicit `and()` always renders "And". Explicit `but()` always renders "But" and never auto-converts.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
it("blocks suspended user login", ({ task }) => {
|
|
54
|
+
story.init(task);
|
|
55
|
+
|
|
56
|
+
story.given("the user account exists"); // renders "Given"
|
|
57
|
+
story.given("the account is suspended"); // renders "And" (auto-converted)
|
|
58
|
+
story.when("the user submits valid credentials");
|
|
59
|
+
story.then("the user sees an error message");
|
|
60
|
+
story.but("the user is not logged in"); // renders "But" (always)
|
|
61
|
+
story.but("no session is created"); // renders "But" (always)
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Doc entries attached to steps
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
it("processes payment", ({ task }) => {
|
|
69
|
+
story.init(task);
|
|
70
|
+
|
|
71
|
+
story.given("a valid payment request");
|
|
72
|
+
story.json({ label: "Request payload", value: { amount: 50, currency: "USD" } });
|
|
73
|
+
story.kv({ label: "Gateway", value: "stripe" });
|
|
74
|
+
|
|
75
|
+
story.when("the payment is submitted");
|
|
76
|
+
story.code({ label: "Response", content: '{ "status": "ok" }', lang: "json" });
|
|
77
|
+
|
|
78
|
+
story.then("the order is confirmed");
|
|
79
|
+
story.table({
|
|
80
|
+
label: "Order summary",
|
|
81
|
+
columns: ["Item", "Qty", "Price"],
|
|
82
|
+
rows: [["Widget", "2", "$25"]],
|
|
83
|
+
});
|
|
84
|
+
story.link({ label: "API docs", url: "https://docs.example.com/payments" });
|
|
85
|
+
story.note("Payment processed in sandbox mode");
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Inline docs via second argument
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
story.given("valid credentials", {
|
|
93
|
+
json: { label: "Credentials", value: { user: "alice", role: "admin" } },
|
|
94
|
+
note: "Password masked for security",
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Step wrappers with timing
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
it("fetches user profile", ({ task }) => {
|
|
102
|
+
story.init(task);
|
|
103
|
+
|
|
104
|
+
story.given("a registered user");
|
|
105
|
+
const userId = "user-123";
|
|
106
|
+
|
|
107
|
+
const profile = await story.fn("When", "the profile is fetched", async () => {
|
|
108
|
+
return fetchProfile(userId);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await story.expect("the profile contains the correct name", () => {
|
|
112
|
+
expect(profile.name).toBe("Alice");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Common Mistakes
|
|
118
|
+
|
|
119
|
+
### CRITICAL Missing task argument in story.init()
|
|
120
|
+
|
|
121
|
+
Wrong:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
it("my test", () => {
|
|
125
|
+
story.init();
|
|
126
|
+
story.given("something");
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Correct:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
it("my test", ({ task }) => {
|
|
134
|
+
story.init(task);
|
|
135
|
+
story.given("something");
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Without `task`, story metadata is not linked to the test and the reporter cannot capture it. The `task` object comes from Vitest's test callback destructuring.
|
|
140
|
+
|
|
141
|
+
Source: packages/executable-stories-vitest/src/story-api.ts
|
|
142
|
+
|
|
143
|
+
### CRITICAL Importing top-level then from the package
|
|
144
|
+
|
|
145
|
+
Wrong:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { story, then } from "executable-stories-vitest";
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Correct:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { story } from "executable-stories-vitest";
|
|
155
|
+
|
|
156
|
+
it("my test", ({ task }) => {
|
|
157
|
+
story.init(task);
|
|
158
|
+
story.then("result is correct");
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
A top-level `then` export would make the module namespace thenable. Tools using `await import("executable-stories-vitest")` would treat the module as a Promise and invoke `then` unexpectedly. All step functions exist only on the `story` object.
|
|
163
|
+
|
|
164
|
+
Source: CLAUDE.md — "Vitest: do not export top-level then"
|
|
165
|
+
|
|
166
|
+
### HIGH Expecting but() to auto-convert to And
|
|
167
|
+
|
|
168
|
+
Wrong:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
story.then("the user sees a success message");
|
|
172
|
+
story.but("no email is sent"); // Developer expects this to render "And"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Correct:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// but() always renders "But" — this is intentional for negative/contrast intent
|
|
179
|
+
story.then("the user sees a success message");
|
|
180
|
+
story.but("no email is sent"); // Renders "But" (correct for contrast)
|
|
181
|
+
|
|
182
|
+
// Use and() if you want "And"
|
|
183
|
+
story.then("the user sees a success message");
|
|
184
|
+
story.and("no email is sent"); // Renders "And"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`but()` expresses negative intent or contrast and never auto-converts. This matches Gherkin semantics.
|
|
188
|
+
|
|
189
|
+
Source: packages/executable-stories-vitest/src/story-api.ts
|
|
190
|
+
|
|
191
|
+
### HIGH Calling steps before story.init()
|
|
192
|
+
|
|
193
|
+
Wrong:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
it("my test", ({ task }) => {
|
|
197
|
+
story.given("something");
|
|
198
|
+
story.init(task);
|
|
199
|
+
});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Correct:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
it("my test", ({ task }) => {
|
|
206
|
+
story.init(task);
|
|
207
|
+
story.given("something");
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Steps called before `init()` are silently dropped because no story context exists yet.
|
|
212
|
+
|
|
213
|
+
Source: packages/eslint-plugin-executable-stories-vitest/src/rules/require-init-before-steps.ts
|
|
214
|
+
|
|
215
|
+
See also: vitest-reporter-setup/SKILL.md — Stories need a reporter to produce output
|
|
216
|
+
See also: eslint-vitest-rules/SKILL.md — ESLint enforces correct story.init() usage
|