@swedevtools/livedoc-vitest 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/RuleContext-BZhuy-zS.d.cts +206 -0
- package/dist/RuleContext-BZhuy-zS.d.ts +206 -0
- package/dist/globals.cjs +2 -0
- package/dist/globals.d.cts +104 -0
- package/dist/globals.d.ts +104 -0
- package/dist/globals.js +1 -0
- package/dist/index-Blmp569T.d.cts +687 -0
- package/dist/index-CysiWbtk.d.ts +687 -0
- package/dist/index.cjs +10024 -0
- package/dist/index.d.cts +291 -0
- package/dist/index.d.ts +291 -0
- package/dist/index.js +9933 -0
- package/dist/playwright/index.cjs +103 -0
- package/dist/playwright/index.d.cts +129 -0
- package/dist/playwright/index.d.ts +129 -0
- package/dist/playwright/index.js +99 -0
- package/dist/reporter/index.cjs +8676 -0
- package/dist/reporter/index.d.cts +7 -0
- package/dist/reporter/index.d.ts +7 -0
- package/dist/reporter/index.js +8635 -0
- package/dist/setup.cjs +14 -0
- package/dist/setup.d.cts +2 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.js +12 -0
- package/package.json +135 -0
- package/tools/livedoc-setup.mjs +164 -0
- package/tools/skills/SKILL.md +244 -0
- package/tools/skills/VALIDATION.md +29 -0
- package/tools/skills/examples/routing.md +60 -0
- package/tools/skills/resources/bdd-features.md +231 -0
- package/tools/skills/resources/playwright.md +148 -0
- package/tools/skills/resources/reporter-config.md +163 -0
- package/tools/skills/resources/specifications.md +159 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# BDD Features — Full Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for writing BDD/Gherkin-style tests with `@swedevtools/livedoc-vitest`.
|
|
4
|
+
|
|
5
|
+
## Keywords
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
feature → scenario | scenarioOutline | background → given | when | then | and | but
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
All blocks receive a `ctx` parameter with framework metadata.
|
|
12
|
+
|
|
13
|
+
## Import Block
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { feature, scenario, scenarioOutline, background, given, when, Then as then, and, but } from "@swedevtools/livedoc-vitest";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**CRITICAL**: Import `Then` (uppercase) and alias as `then` (lowercase). ESM thenable detection requires the uppercase export name.
|
|
20
|
+
|
|
21
|
+
## Feature
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
feature("Shopping Cart", (ctx) => {
|
|
25
|
+
// ctx.feature → { filename, title, description, tags }
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Scenario
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
scenario("Adding items to cart", (ctx) => {
|
|
33
|
+
// ctx.scenario → { title, description, tags, given?, steps }
|
|
34
|
+
given("...", (ctx) => { /* ctx.step available */ });
|
|
35
|
+
when("...", (ctx) => { /* ctx.step available */ });
|
|
36
|
+
then("...", (ctx) => { /* ctx.step available */ });
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Background
|
|
41
|
+
|
|
42
|
+
Shared setup that runs before every scenario in a feature:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
feature("User Dashboard", () => {
|
|
46
|
+
background("Authenticated user", (ctx) => {
|
|
47
|
+
given("the user is logged in", () => {
|
|
48
|
+
user = await login();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Optional cleanup — runs after each scenario
|
|
52
|
+
ctx.afterBackground(() => {
|
|
53
|
+
user = null;
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
scenario("Viewing the dashboard", () => {
|
|
58
|
+
// 'given' from background runs first
|
|
59
|
+
when("...", () => { });
|
|
60
|
+
then("...", () => { });
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Note**: `background()` requires a title as the first argument.
|
|
66
|
+
|
|
67
|
+
## Descriptions and Tags
|
|
68
|
+
|
|
69
|
+
Lines after the first line in titles provide descriptions and tags:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
feature(`Shopping Cart Checkout
|
|
73
|
+
@checkout @critical
|
|
74
|
+
Business rules for the shopping cart checkout flow.
|
|
75
|
+
Covers GST calculation, shipping tiers, and discount codes.
|
|
76
|
+
`, (ctx) => {
|
|
77
|
+
|
|
78
|
+
scenario(`Free shipping for large orders
|
|
79
|
+
Orders over $100 qualify for free shipping in Australia.
|
|
80
|
+
`, (ctx) => {
|
|
81
|
+
// steps...
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- **First line** = title
|
|
87
|
+
- **Lines starting with `@`** = tags (used for filtering)
|
|
88
|
+
- **Remaining lines** = description (appears in output and reports)
|
|
89
|
+
|
|
90
|
+
**Always add descriptions** to `feature` blocks for context. Optionally to `scenario` blocks.
|
|
91
|
+
|
|
92
|
+
## Value Extraction
|
|
93
|
+
|
|
94
|
+
### Quoted Values — auto-extracted, type-coerced
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
given("user has '100' items and active is 'true'", (ctx) => {
|
|
98
|
+
const [count, isActive] = ctx.step.values;
|
|
99
|
+
// count = 100 (number), isActive = true (boolean)
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Named Parameters — `<name:value>` syntax
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
given("a user with <email:john@test.com> and <age:25>", (ctx) => {
|
|
107
|
+
const email = ctx.step.params.email; // "john@test.com"
|
|
108
|
+
const age = ctx.step.params.age; // 25
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Data Tables
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
given(`users:
|
|
116
|
+
| name | age |
|
|
117
|
+
| Alice | 30 |
|
|
118
|
+
| Bob | 25 |
|
|
119
|
+
`, (ctx) => {
|
|
120
|
+
// ctx.step.table => [{name: "Alice", age: 30}, {name: "Bob", age: 25}]
|
|
121
|
+
// ctx.step.tableAsEntity => (2-col table as single object)
|
|
122
|
+
// ctx.step.tableAsSingleList => (first column as flat array)
|
|
123
|
+
// ctx.step.dataTable => (raw 2D array)
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Doc Strings
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
given(`config:
|
|
131
|
+
"""
|
|
132
|
+
{"debug": true, "level": 5}
|
|
133
|
+
"""
|
|
134
|
+
`, (ctx) => {
|
|
135
|
+
const raw = ctx.step.docString; // '{"debug": true, "level": 5}'
|
|
136
|
+
const parsed = ctx.step.docStringAsEntity; // {debug: true, level: 5}
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## ScenarioOutline with Examples
|
|
141
|
+
|
|
142
|
+
Data-driven scenarios. Each row in the Examples table creates a separate test:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
scenarioOutline(`Validate inputs
|
|
146
|
+
Examples:
|
|
147
|
+
| input | expected |
|
|
148
|
+
| foo | true |
|
|
149
|
+
| bar | false |
|
|
150
|
+
`, (ctx) => {
|
|
151
|
+
when("checking <input>", (ctx) => {
|
|
152
|
+
result = validate(ctx.example.input);
|
|
153
|
+
});
|
|
154
|
+
then("result is <expected>", (ctx) => {
|
|
155
|
+
expect(result).toBe(ctx.example.expected);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Access example data via `ctx.example.<columnName>`. Values are type-coerced.
|
|
161
|
+
|
|
162
|
+
## Step Attachment API
|
|
163
|
+
|
|
164
|
+
Attach files, screenshots, and data to steps. Attachments appear in the LiveDoc Viewer.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// Attach JSON data
|
|
168
|
+
ctx.step.attachJSON(data, "API Response");
|
|
169
|
+
|
|
170
|
+
// Attach a screenshot (base64 PNG)
|
|
171
|
+
ctx.step.attachScreenshot(base64Data, "Login Page");
|
|
172
|
+
|
|
173
|
+
// Attach arbitrary data
|
|
174
|
+
ctx.step.attach(base64Data, { mimeType: "image/png", kind: "image", title: "Chart" });
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Methods:**
|
|
178
|
+
- `attach(base64Data, options?)` — options: `{mimeType, kind: 'image'|'screenshot'|'file', title}`
|
|
179
|
+
- `attachScreenshot(base64Data, title?)` — convenience for PNG screenshots
|
|
180
|
+
- `attachJSON(data, title?)` — convenience for JSON data
|
|
181
|
+
|
|
182
|
+
Read attachments: `ctx.step.attachments` (read-only array)
|
|
183
|
+
|
|
184
|
+
## Async Rules
|
|
185
|
+
|
|
186
|
+
- **Only step callbacks support `async`** (`given`, `when`, `then`, `and`, `but`)
|
|
187
|
+
- `feature`, `scenario`, `scenarioOutline`, `background` must be **synchronous**
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
when("data is fetched", async (ctx) => {
|
|
191
|
+
result = await fetchData(); // ✅ OK
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
scenario("Test", async (ctx) => { /* ❌ NOT ALLOWED */ });
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Context Reference
|
|
198
|
+
|
|
199
|
+
| Property | Type | Description |
|
|
200
|
+
| --- | --- | --- |
|
|
201
|
+
| `ctx.feature` | `FeatureContext` | `{filename, title, description, tags}` |
|
|
202
|
+
| `ctx.scenario` | `ScenarioContext` | `{title, description, tags, given?, steps}` |
|
|
203
|
+
| `ctx.step` | `StepContext` | `{title, type, values, docString, table, ...}` |
|
|
204
|
+
| `ctx.example` | `object` | Current example row (scenarioOutline only) |
|
|
205
|
+
| `ctx.background` | `BackgroundContext` | Background metadata |
|
|
206
|
+
| `ctx.afterBackground(fn)` | function | Register cleanup (background only) |
|
|
207
|
+
|
|
208
|
+
### StepContext Properties
|
|
209
|
+
|
|
210
|
+
| Property | Returns |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| `values` | Coerced quoted values array |
|
|
213
|
+
| `valuesRaw` | Raw string values |
|
|
214
|
+
| `params` | Coerced named values object `<n:v>` |
|
|
215
|
+
| `paramsRaw` | Raw named values string object |
|
|
216
|
+
| `docString` | Raw doc string content |
|
|
217
|
+
| `docStringAsEntity` | Parsed JSON or undefined |
|
|
218
|
+
| `table` | Headers as keys, array of row objects |
|
|
219
|
+
| `tableAsEntity` | 2-col table as single object |
|
|
220
|
+
| `tableAsSingleList` | First column as flat array |
|
|
221
|
+
| `dataTable` | Raw 2D array |
|
|
222
|
+
| `attachments` | Read-only `Attachment[]` |
|
|
223
|
+
|
|
224
|
+
## Validation Checklist
|
|
225
|
+
|
|
226
|
+
- [ ] All test data appears in step title strings (self-documenting)
|
|
227
|
+
- [ ] Descriptions provided on `feature` blocks for context
|
|
228
|
+
- [ ] Values extracted via `ctx.step.values`, `ctx.step.params`, or `ctx.example`
|
|
229
|
+
- [ ] `Then` imported as uppercase, aliased to lowercase `then`
|
|
230
|
+
- [ ] Async only on step callbacks
|
|
231
|
+
- [ ] File name ends in `.Spec.ts`
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Playwright Integration — Full Reference
|
|
2
|
+
|
|
3
|
+
Browser-based testing with `@swedevtools/livedoc-vitest/playwright`.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D playwright # or: pnpm add -D playwright
|
|
9
|
+
npx playwright install chromium # install browser binary
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { useBrowser, screenshot } from "@swedevtools/livedoc-vitest/playwright";
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## useBrowser(options?)
|
|
19
|
+
|
|
20
|
+
Manages browser lifecycle for the current feature file. **Call at module scope** (outside any scenario). Launches the browser in `beforeAll`, closes in `afterAll`.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { feature, scenario, given, when, Then as then } from "@swedevtools/livedoc-vitest";
|
|
24
|
+
import { useBrowser, screenshot } from "@swedevtools/livedoc-vitest/playwright";
|
|
25
|
+
|
|
26
|
+
const { page, context, browser } = useBrowser();
|
|
27
|
+
|
|
28
|
+
feature("Viewer Navigation", () => {
|
|
29
|
+
scenario("Loading the homepage", () => {
|
|
30
|
+
when("navigating to the homepage", async (ctx) => {
|
|
31
|
+
await page().goto("http://localhost:3000");
|
|
32
|
+
await screenshot(page(), ctx);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
then("the page title should be visible", async () => {
|
|
36
|
+
const title = await page().locator("h1").textContent();
|
|
37
|
+
expect(title).toBeTruthy();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Options
|
|
44
|
+
|
|
45
|
+
| Option | Type | Default | Description |
|
|
46
|
+
| --- | --- | --- | --- |
|
|
47
|
+
| `browser` | `'chromium' \| 'firefox' \| 'webkit'` | `'chromium'` | Browser engine |
|
|
48
|
+
| `headless` | `boolean` | `true` | Run headless (set `false` for debugging) |
|
|
49
|
+
| `viewport` | `{width, height}` | `1280×720` | Browser viewport size |
|
|
50
|
+
| `freshContextPerScenario` | `boolean` | `false` | Create a fresh browser context for each scenario |
|
|
51
|
+
|
|
52
|
+
### Return Value
|
|
53
|
+
|
|
54
|
+
`useBrowser()` returns **getter functions**, not direct references:
|
|
55
|
+
|
|
56
|
+
- `page()` — returns the current Playwright Page
|
|
57
|
+
- `context()` — returns the current BrowserContext
|
|
58
|
+
- `browser()` — returns the Browser instance
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const { page } = useBrowser({ headless: false });
|
|
62
|
+
|
|
63
|
+
// ✅ CORRECT: Call page() inside a step
|
|
64
|
+
when("clicking the button", async () => {
|
|
65
|
+
await page().click("button#submit");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ❌ WRONG: page() at module scope — browser not launched yet
|
|
69
|
+
const p = page(); // Will throw or return undefined
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Headed Mode (Debugging)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const { page } = useBrowser({ headless: false });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The browser window stays visible for debugging. Combine with `scenario.only()` to focus on a single test.
|
|
79
|
+
|
|
80
|
+
## screenshot(page, ctx, options?)
|
|
81
|
+
|
|
82
|
+
Captures a screenshot and attaches it to the current step.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
when("viewing the dashboard", async (ctx) => {
|
|
86
|
+
await screenshot(page(), ctx);
|
|
87
|
+
// Auto-named: "viewing-the-dashboard-0.png"
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Parameters
|
|
92
|
+
|
|
93
|
+
- `page` — Playwright Page instance (use `page()` getter)
|
|
94
|
+
- `ctx` — Step context from the step callback
|
|
95
|
+
- `options.name` — Custom screenshot name (optional; auto-generated from step title if omitted)
|
|
96
|
+
- `options.fullPage` — Capture full page vs viewport only (optional)
|
|
97
|
+
|
|
98
|
+
### Custom Named Screenshots
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
when("viewing the dashboard", async (ctx) => {
|
|
102
|
+
await screenshot(page(), ctx, { name: "dashboard-initial-load" });
|
|
103
|
+
// ... interact with page ...
|
|
104
|
+
await screenshot(page(), ctx, { name: "dashboard-after-filter" });
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Global Setup for Dev Server
|
|
109
|
+
|
|
110
|
+
When testing against a local dev server, use Vitest's `globalSetup` to start it:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// vitest.config.ts
|
|
114
|
+
export default defineConfig({
|
|
115
|
+
test: {
|
|
116
|
+
globalSetup: './global-setup.ts',
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// global-setup.ts
|
|
121
|
+
export async function setup() {
|
|
122
|
+
// Start your dev server, wait for it to be ready
|
|
123
|
+
}
|
|
124
|
+
export async function teardown() {
|
|
125
|
+
// Stop the server
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## CI Configuration
|
|
130
|
+
|
|
131
|
+
```yaml
|
|
132
|
+
# .github/workflows/test.yml
|
|
133
|
+
- name: Install Playwright
|
|
134
|
+
run: npx playwright install --with-deps chromium
|
|
135
|
+
- name: Run tests
|
|
136
|
+
run: npx vitest run
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Troubleshooting
|
|
140
|
+
|
|
141
|
+
| Problem | Cause | Solution |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| `Cannot find module 'playwright'` | Not installed | `npm install -D playwright` |
|
|
144
|
+
| `Browser not found` | Binaries not installed | `npx playwright install chromium` |
|
|
145
|
+
| `page() returns undefined` | Called at module scope | Call `page()` inside step callbacks only |
|
|
146
|
+
| `useBrowser is not a function` | Wrong import path | Use `@swedevtools/livedoc-vitest/playwright` |
|
|
147
|
+
| Tests timeout | Slow network/server | Increase vitest timeout, ensure server is running |
|
|
148
|
+
| Screenshots are blank | Page not loaded | Add `await page().waitForLoadState()` before screenshot |
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Reporter Configuration — Full Reference
|
|
2
|
+
|
|
3
|
+
Configure LiveDoc reporters for console output, real-time viewer streaming, JSON export, and static HTML reports.
|
|
4
|
+
|
|
5
|
+
## Available Reporters
|
|
6
|
+
|
|
7
|
+
| Reporter | Purpose | Import |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `LiveDocSpecReporter` | Console output + auto-discover viewer | `@swedevtools/livedoc-vitest/reporter` |
|
|
10
|
+
| `LiveDocViewerReporter` | Stream to viewer only (no console) | `@swedevtools/livedoc-vitest/reporter` |
|
|
11
|
+
| `JsonReporter` | Write JSON file for static export | `@swedevtools/livedoc-vitest/reporter` |
|
|
12
|
+
| `SilentReporter` | Suppress all output | `@swedevtools/livedoc-vitest/reporter` |
|
|
13
|
+
|
|
14
|
+
## LiveDocSpecReporter
|
|
15
|
+
|
|
16
|
+
The primary reporter. Produces structured Gherkin-style console output and auto-discovers a running LiveDoc Viewer server.
|
|
17
|
+
|
|
18
|
+
### Simplest Config
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { defineConfig } from "vitest/config";
|
|
22
|
+
|
|
23
|
+
export default defineConfig({
|
|
24
|
+
test: {
|
|
25
|
+
reporters: [
|
|
26
|
+
["@swedevtools/livedoc-vitest/reporter", { detailLevel: "spec+summary+headers" }],
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Detail Levels
|
|
33
|
+
|
|
34
|
+
Combinable with `+`:
|
|
35
|
+
|
|
36
|
+
| Level | Output |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `spec` | Full step-by-step output |
|
|
39
|
+
| `summary` | Pass/fail/skip counts |
|
|
40
|
+
| `headers` | Feature and scenario titles |
|
|
41
|
+
| `list` | One-line-per-test list |
|
|
42
|
+
| `silent` | No output |
|
|
43
|
+
|
|
44
|
+
Examples: `"spec+summary+headers"`, `"list+headers"`, `"summary"`
|
|
45
|
+
|
|
46
|
+
### Explicit Publish Config
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { LiveDocSpecReporter } from "@swedevtools/livedoc-vitest/reporter";
|
|
50
|
+
|
|
51
|
+
export default defineConfig({
|
|
52
|
+
test: {
|
|
53
|
+
reporters: [
|
|
54
|
+
new LiveDocSpecReporter({
|
|
55
|
+
detailLevel: "spec+summary+headers",
|
|
56
|
+
publish: {
|
|
57
|
+
enabled: true,
|
|
58
|
+
server: "http://localhost:3000",
|
|
59
|
+
project: "my-project",
|
|
60
|
+
environment: "local",
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Auto-Discovery Priority
|
|
69
|
+
|
|
70
|
+
The reporter automatically finds the viewer server:
|
|
71
|
+
|
|
72
|
+
1. **Environment variables**: `LIVEDOC_SERVER_URL` or `LIVEDOC_PUBLISH_SERVER`
|
|
73
|
+
2. **Explicit config**: `publish.server` in reporter options
|
|
74
|
+
3. **Discovery**: `discoverServer()` fallback from `@swedevtools/livedoc-server`
|
|
75
|
+
|
|
76
|
+
### Additional Options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Description |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `detailLevel` | string | Output detail level (see above) |
|
|
81
|
+
| `output` | string | Write output to file |
|
|
82
|
+
| `removeHeaderText` | string | Strip text from headers (monorepo prefix) |
|
|
83
|
+
| `colors` | boolean | Enable/disable ANSI colors |
|
|
84
|
+
| `postReporters` | `IPostReporter[]` | Chain additional reporters after this one |
|
|
85
|
+
|
|
86
|
+
## LiveDocViewerReporter
|
|
87
|
+
|
|
88
|
+
Streams results to the LiveDoc Viewer in real-time without console output. Use when you want viewer integration only.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { LiveDocViewerReporter } from "@swedevtools/livedoc-vitest/reporter";
|
|
92
|
+
|
|
93
|
+
export default defineConfig({
|
|
94
|
+
test: {
|
|
95
|
+
reporters: [
|
|
96
|
+
new LiveDocViewerReporter({
|
|
97
|
+
server: "http://localhost:3000",
|
|
98
|
+
project: "my-project",
|
|
99
|
+
environment: "local",
|
|
100
|
+
}),
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## JsonReporter
|
|
107
|
+
|
|
108
|
+
Writes test results to a JSON file. Used for CI/CD static report generation.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { JsonReporter } from "@swedevtools/livedoc-vitest/reporter";
|
|
112
|
+
|
|
113
|
+
export default defineConfig({
|
|
114
|
+
test: {
|
|
115
|
+
reporters: [
|
|
116
|
+
new JsonReporter({ outputFile: "test-results.json" }),
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Static HTML Export
|
|
123
|
+
|
|
124
|
+
Generate a self-contained HTML report from a JSON results file:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx livedoc-viewer export -i test-results.json -o report.html
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### CI/CD Pipeline
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
steps:
|
|
134
|
+
- name: Run tests with JSON output
|
|
135
|
+
run: npx vitest run --config vitest.config.json.ts
|
|
136
|
+
|
|
137
|
+
- name: Generate HTML report
|
|
138
|
+
run: npx livedoc-viewer export -i test-results.json -o report.html
|
|
139
|
+
|
|
140
|
+
- name: Upload report
|
|
141
|
+
uses: actions/upload-artifact@v4
|
|
142
|
+
with:
|
|
143
|
+
name: test-report
|
|
144
|
+
path: report.html
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Environment-Driven Config
|
|
148
|
+
|
|
149
|
+
Use environment variables for flexible CI configurations:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"scripts": {
|
|
154
|
+
"test:spec": "cross-env LIVEDOC_DETAIL_LEVEL=spec+headers vitest run",
|
|
155
|
+
"test:list": "cross-env LIVEDOC_DETAIL_LEVEL=list+headers vitest run",
|
|
156
|
+
"test:summary": "cross-env LIVEDOC_DETAIL_LEVEL=summary+headers vitest run"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Backward Compatibility
|
|
162
|
+
|
|
163
|
+
`LiveDocServerReporter` is a deprecated re-export of `LiveDocSpecReporter`. Old configs still work.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Specifications — Full Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for writing MSpec-style specification/rule tests with `@swedevtools/livedoc-vitest`.
|
|
4
|
+
|
|
5
|
+
## Keywords
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
specification → rule | ruleOutline
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Import Block
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { specification, rule, ruleOutline } from "@swedevtools/livedoc-vitest";
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Specification
|
|
18
|
+
|
|
19
|
+
Top-level container grouping related rules:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
specification("Calculator Operations", (ctx) => {
|
|
23
|
+
// ctx.specification → { title, description, tags }
|
|
24
|
+
});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Rule
|
|
28
|
+
|
|
29
|
+
Individual test cases with direct assertions:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
specification("Calculator Operations", () => {
|
|
33
|
+
rule("Adding '5' and '3' returns '8'", (ctx) => {
|
|
34
|
+
const [a, b, expected] = ctx.rule.values; // [5, 3, 8]
|
|
35
|
+
expect(a + b).toBe(expected);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Value Extraction
|
|
41
|
+
|
|
42
|
+
### Quoted Values — auto-extracted, type-coerced
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
rule("Adding '5' and '3' returns '8'", (ctx) => {
|
|
46
|
+
const [a, b, expected] = ctx.rule.values;
|
|
47
|
+
// a = 5 (number), b = 3, expected = 8
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Named Parameters — `<name:value>` syntax
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
rule("Subtracting <b:3> from <a:10> returns <expected:7>", (ctx) => {
|
|
55
|
+
const a = ctx.rule.params.a; // 10
|
|
56
|
+
const b = ctx.rule.params.b; // 3
|
|
57
|
+
expect(a - b).toBe(ctx.rule.params.expected); // 7
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## RuleOutline with Examples
|
|
62
|
+
|
|
63
|
+
Data-driven rules. Each row in the Examples table creates a separate test:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
ruleOutline(`Discount calculations
|
|
67
|
+
Examples:
|
|
68
|
+
| price | discount | expected |
|
|
69
|
+
| 100 | 10 | 90 |
|
|
70
|
+
| 200 | 25 | 150 |
|
|
71
|
+
`, (ctx) => {
|
|
72
|
+
const result = ctx.example.price - (ctx.example.price * ctx.example.discount / 100);
|
|
73
|
+
expect(result).toBe(ctx.example.expected);
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Combining Title Values and Example Data
|
|
78
|
+
|
|
79
|
+
RuleOutline supports both title values and example table data:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
ruleOutline(`Discount of '10' percent applies to orders over '100' dollars
|
|
83
|
+
Examples:
|
|
84
|
+
| orderTotal | expectedDiscount |
|
|
85
|
+
| 150 | 15 |
|
|
86
|
+
| 200 | 20 |
|
|
87
|
+
`, (ctx) => {
|
|
88
|
+
const [discountPct, threshold] = ctx.rule.values; // From title: [10, 100]
|
|
89
|
+
const discount = ctx.example.orderTotal * (discountPct / 100); // From table
|
|
90
|
+
expect(discount).toBe(ctx.example.expectedDiscount);
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Named Params in RuleOutline
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
ruleOutline(`Applying <operation:multiply> with factor <factor:3>
|
|
98
|
+
Examples:
|
|
99
|
+
| input | expected |
|
|
100
|
+
| 5 | 15 |
|
|
101
|
+
| 10 | 30 |
|
|
102
|
+
`, (ctx) => {
|
|
103
|
+
expect(ctx.rule.params.operation).toBe("multiply");
|
|
104
|
+
expect(ctx.example.input * ctx.rule.params.factor).toBe(ctx.example.expected);
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Descriptions and Tags
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
specification(`Email Validation
|
|
112
|
+
@validation
|
|
113
|
+
Rules for validating email addresses across formats.
|
|
114
|
+
`, (ctx) => {
|
|
115
|
+
// rules...
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- **First line** = title
|
|
120
|
+
- **Lines starting with `@`** = tags
|
|
121
|
+
- **Remaining lines** = description
|
|
122
|
+
|
|
123
|
+
## Async Rules
|
|
124
|
+
|
|
125
|
+
`rule` callbacks support `async`. `specification` callbacks must be **synchronous**.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
rule("Fetching user returns valid data", async (ctx) => {
|
|
129
|
+
const user = await fetchUser(1);
|
|
130
|
+
expect(user.name).toBeTruthy(); // ✅ OK
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
specification("Test", async (ctx) => { /* ❌ NOT ALLOWED */ });
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Context Reference
|
|
137
|
+
|
|
138
|
+
| Property | Type | Description |
|
|
139
|
+
| --- | --- | --- |
|
|
140
|
+
| `ctx.specification` | `SpecificationContext` | `{title, description, tags}` |
|
|
141
|
+
| `ctx.rule` | `RuleContext` | `{title, description, tags, specification, values, valuesRaw, params, paramsRaw}` |
|
|
142
|
+
| `ctx.example` | `object` | Current example row (ruleOutline only) |
|
|
143
|
+
|
|
144
|
+
### RuleContext Properties
|
|
145
|
+
|
|
146
|
+
| Property | Returns |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `values` | Coerced quoted values array |
|
|
149
|
+
| `valuesRaw` | Raw string values |
|
|
150
|
+
| `params` | Coerced named values object `<n:v>` |
|
|
151
|
+
| `paramsRaw` | Raw named values string object |
|
|
152
|
+
|
|
153
|
+
## Validation Checklist
|
|
154
|
+
|
|
155
|
+
- [ ] All test data appears in rule title strings (self-documenting)
|
|
156
|
+
- [ ] Descriptions provided on `specification` blocks
|
|
157
|
+
- [ ] Values extracted via `ctx.rule.values`, `ctx.rule.params`, or `ctx.example`
|
|
158
|
+
- [ ] Async only on `rule` callbacks, not `specification`
|
|
159
|
+
- [ ] File name ends in `.Spec.ts`
|