@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.
@@ -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`