{"_id":"@abendigo/vitest-living-docs","_rev":"3-5a404144c71ba4776a5d4a5bf8ee7fbb","name":"@abendigo/vitest-living-docs","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@abendigo/vitest-living-docs","version":"0.1.0","_id":"@abendigo/vitest-living-docs@0.1.0","maintainers":[{"name":"m13d","email":"mark@oosterveld.org"}],"bin":{"vitest-living-docs":"scripts/generate-test-docs.mjs"},"dist":{"shasum":"d7e0021b8d458b360b6d5595819d62f4b8e8e770","tarball":"https://registry.npmjs.org/@abendigo/vitest-living-docs/-/vitest-living-docs-0.1.0.tgz","fileCount":10,"integrity":"sha512-i0E3vmzP4KMk4MNldcAv6pTHb/gKUne39d/8bxq8isWEGuK6ClpXIDEwwUYJa1WHRxh/b2q/jmDPnRSiABa3Jg==","signatures":[{"sig":"MEYCIQC0vstchp758Ue5nY5EkHqYMXaSqM77yJauX0IhGFbBOgIhAIY8KYgcXFR0mZhuCILbGoS06z28wOjU/z5bIUM+v9ei","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36486},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"},"./eslint":{"types":"./dist/eslint.d.ts","import":"./dist/eslint.js","source":"./src/eslint.ts"}},"gitHead":"8833e6220c0e88571f3fbfcd94812e91d8e95469","scripts":{"dev":"tsup --watch","build":"tsup"},"_npmUser":{"name":"m13d","email":"mark@oosterveld.org"},"_npmVersion":"11.11.0","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","eslint":"^10.2.0","vitest":"^4.0.0","typescript":"^5.0.0"},"peerDependencies":{"vitest":">=2.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vitest-living-docs_0.1.0_1775499899361_0.4608359446646324","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@abendigo/vitest-living-docs","version":"0.2.0","_id":"@abendigo/vitest-living-docs@0.2.0","maintainers":[{"name":"m13d","email":"mark@oosterveld.org"}],"bin":{"vitest-living-docs":"scripts/generate-test-docs.mjs"},"dist":{"shasum":"4fcdfe02496eb90dd48e1353783ab9c750c7b0a3","tarball":"https://registry.npmjs.org/@abendigo/vitest-living-docs/-/vitest-living-docs-0.2.0.tgz","fileCount":10,"integrity":"sha512-hY0xx24E86BTl8gF89hB0+k/x48N83H4v+QUkDrgByxOF+9QjHaf1YriOT4KM2vNSSWYt/El9vj5P1jO00/DIQ==","signatures":[{"sig":"MEUCIFbnr+eX0b8u18a5FVUZj7nE/wM9cY8wdsTJPmWovUqNAiEAu2WE4HkI7cf7oKGwqB7erM7gvNJRjzOu8hpnA8kxfic=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":36690},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","source":"./src/index.ts"},"./eslint":{"types":"./dist/eslint.d.ts","import":"./dist/eslint.js","source":"./src/eslint.ts"}},"gitHead":"9a676ed8c1b299f011c8704025f00e279f089b4d","scripts":{"dev":"tsup --watch","build":"tsup"},"_npmUser":{"name":"m13d","email":"mark@oosterveld.org"},"_npmVersion":"11.11.0","directories":{},"_nodeVersion":"24.14.1","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","eslint":"^10.2.0","vitest":"^4.0.0","typescript":"^5.0.0"},"peerDependencies":{"vitest":">=2.0.0"},"_npmOperationalInternal":{"tmp":"tmp/vitest-living-docs_0.2.0_1775509972736_0.04524279919267826","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@abendigo/vitest-living-docs","version":"0.3.0","type":"module","exports":{".":{"source":"./src/index.ts","import":"./dist/index.js","types":"./dist/index.d.ts"},"./eslint":{"source":"./src/eslint.ts","import":"./dist/eslint.js","types":"./dist/eslint.d.ts"}},"main":"./dist/index.js","types":"./dist/index.d.ts","bin":{"vitest-living-docs":"scripts/generate-test-docs.mjs"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:report":"vitest run && vitest-living-docs living-docs/index.html"},"peerDependencies":{"vitest":">=2.0.0"},"devDependencies":{"@d2t/vitest-ctrf-json-reporter":"^1.3.0","eslint":"^10.2.0","tsup":"^8.0.0","typescript":"^5.0.0","vitest":"^4.0.0"},"gitHead":"0dbb79797fac427b2ecb3c6b03cf41faef059b93","_id":"@abendigo/vitest-living-docs@0.3.0","description":"A BDD-style test helper for [Vitest](https://vitest.dev/) that structures tests as `given / when / then` scenarios and generates living documentation from the results.","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-gS8v4v2bzKaOaM4dDBw7K17/mDkVw9kgWOz1MlVBSZB26X8zw0DYc6qBTBqA7OVw0l3BGAi31Ooc1S/pEIA7Ww==","shasum":"9c13ec4f2665e05aa6762a9290dace9992c2b472","tarball":"https://registry.npmjs.org/@abendigo/vitest-living-docs/-/vitest-living-docs-0.3.0.tgz","fileCount":15,"unpackedSize":49472,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHGd1nBJzZosJk7CIiTNJ1u39zeFqk3douuQf+99xxelAiA2R47q2IhyEsPHOjHMGL1ZgHUxssXl4xLBt3JQDbW8hQ=="}]},"_npmUser":{"name":"m13d","email":"mark@oosterveld.org"},"directories":{},"maintainers":[{"name":"m13d","email":"mark@oosterveld.org"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vitest-living-docs_0.3.0_1776292587306_0.29049456899956017"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-06T18:24:59.208Z","modified":"2026-04-15T22:36:27.582Z","0.1.0":"2026-04-06T18:24:59.524Z","0.2.0":"2026-04-06T21:12:52.880Z","0.3.0":"2026-04-15T22:36:27.444Z"},"maintainers":[{"name":"m13d","email":"mark@oosterveld.org"}],"readme":"# @abendigo/vitest-living-docs\n\nA BDD-style test helper for [Vitest](https://vitest.dev/) that structures tests as `given / when / then` scenarios and generates living documentation from the results.\n\nTests read like specifications. The living docs output makes that structure visible to anyone — not just the people who wrote the tests.\n\n## Installation\n\n```sh\nnpm install --save-dev @abendigo/vitest-living-docs\n```\n\nRequires `vitest >= 2.0.0` as a peer dependency.\n\n## Usage\n\nTests are written using three functions: `given`, `when`, and `then`.\n\n- **`given`** — sets up the world (database state, users, fixtures). Uses named factory functions so the setup is reusable and readable.\n- **`when`** — performs the action being tested. Uses an inline arrow function so the call is visible at the test site.\n- **`then`** — asserts the outcome. Uses named assertion factories so labels match exactly what is being checked.\n\n```ts\nimport { given, when } from '@abendigo/vitest-living-docs'\n\n// Setup factories — named, reusable\nconst withDatabase = async (fixture) => {\n  const db = await createTestDb()\n  return { ...fixture, db }\n}\n\nconst withUser = (key, { email }) => async (fixture) => {\n  const user = await createUser(fixture.db, email, 'password')\n  return { ...fixture, users: { ...fixture.users, [key]: user } }\n}\n\n// Assertion factories — named, specific\nconst returnsTrip = (title) => (_fixture, results) => {\n  const trip = results[0]\n  if (trip.title !== title) throw new Error(`Expected '${title}', got '${trip.title}'`)\n}\n\nconst tripIsOpen = (_fixture, results) => {\n  const trip = results[0]\n  if (trip.status !== 'open') throw new Error(`Expected status 'open', got '${trip.status}'`)\n}\n\n// The test\ngiven(\n  ['an owner exists', [withDatabase, withUser('owner', { email: 'owner@example.com' })]],\n\n  when(\n    ['they create a trip', ({ db, users }) => createTrip(db, { owner_id: users.owner.id, title: 'Sunset Cruise' })],\n  ).then(\n    ['the trip title is Sunset Cruise', returnsTrip('Sunset Cruise')],\n    ['the trip status is open',         tripIsOpen],\n  ),\n)\n```\n\nThis produces a single Vitest test named:\n\n```\nan owner exists / they create a trip / the trip title is Sunset Cruise\nan owner exists / they create a trip / the trip status is open\n```\n\n### Multiple scenarios from one setup\n\nA single `given` block can contain multiple `when / then` scenarios. Setup runs once per scenario — they don't share state.\n\n```ts\ngiven(\n  ['an owner exists', [withDatabase, withUser('owner', { email: 'owner@example.com' })]],\n\n  when(\n    ['they create a trip with one date', ({ db, users }) => createTrip(db, {\n      owner_id: users.owner.id,\n      title: 'Sunset Cruise',\n      dates: [{ departure_date: '2026-07-05' }]\n    })],\n  ).then(\n    ['the date is auto-confirmed', dateIsConfirmed],\n  ),\n\n  when(\n    ['they create a trip with multiple dates', ({ db, users }) => createTrip(db, {\n      owner_id: users.owner.id,\n      title: 'Raft-Up Weekend',\n      dates: [\n        { departure_date: '2026-07-05' },\n        { departure_date: '2026-07-12' },\n      ]\n    })],\n  ).then(\n    ['no date is confirmed yet', noDateIsConfirmed],\n  ),\n)\n```\n\n### Multiple setup steps\n\nPass an array of setup functions as the second element of a `given` tuple to chain them:\n\n```ts\ngiven(\n  ['a trip exists with two proposed dates', [withDatabase, withUser('owner', { email: 'owner@example.com' }), withTrip('trip', 'owner'), withTripDate('a'), withTripDate('b')]],\n\n  when(\n    ['the owner confirms date a', ({ db, trips, dates }) => setTripDateConfirmed(db, trips.trip.id, dates.a.id, true)],\n  ).then(\n    ['date a is confirmed', dateAIsConfirmed],\n    ['date b is not confirmed', dateBIsNotConfirmed],\n  ),\n)\n```\n\n### Features\n\nGroup related scenarios under a named feature using `feature()`:\n\n```ts\nimport { feature, given, when } from '@abendigo/vitest-living-docs'\n\nfeature('Trip creation', [\n  'Owners can create trips with optional dates.',\n  'A single date is auto-confirmed; multiple dates go to a vote.',\n], () => {\n  given(\n    ['an owner exists', [withDatabase, withUser('owner', { email: 'owner@example.com' })]],\n    // ... scenarios\n  )\n})\n```\n\n## Generating living docs\n\nRun Vitest with JSON output, then generate the HTML report:\n\n```sh\n# Run tests and write results to test-results.json\nnpx vitest run --reporter=json --outputFile=test-results.json\n\n# Generate the HTML report\nnpx vitest-living-docs [output-path]\n# Default output: static/dev/tests/index.html\n```\n\nOr add a script to `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"test:report\": \"vitest run --reporter=json --outputFile=test-results.json && vitest-living-docs\"\n  }\n}\n```\n\n## Living docs output\n\nThe generated HTML shows each test suite as a collapsible panel, with scenarios grouped by their `given` context and nested `when / then` rows. The structure is sticky-scrollable so the context stays visible as you read down a long suite.\n\n```\n┌─ Trips ──────────────────────────────────────────────────────────┐\n│                                                                   │\n│  given   an owner exists                                          │\n│  ├─ when   they create a trip with one date                       │\n│  │    then  ✓ the date is auto-confirmed                          │\n│  │                                                                │\n│  ├─ when   they create a trip with multiple dates                 │\n│  │    then  ✓ no date is confirmed yet                            │\n│  │                                                                │\n│  given   a trip exists with two proposed dates                    │\n│  ├─ when   the owner confirms date a                              │\n│       then  ✓ date a is confirmed                                 │\n│             ✓ date b is not confirmed                             │\n│                                                                   │\n└───────────────────────────────────────────────────────────────────┘\n```\n\nFailing tests surface at the top of the report with a summary and inline failure messages.\n\nSee a live example: [frustrated.blog/vitest-living-docs](https://frustrated.blog/vitest-living-docs/)\n\n## ESLint plugin\n\nAn optional ESLint plugin enforces consistent style in `given / when / then` calls:\n\n```js\n// eslint.config.js\nimport { recommended } from '@abendigo/vitest-living-docs/eslint'\n\nexport default [\n  recommended,\n]\n```\n\nRules:\n\n| Rule | Default | Description |\n|---|---|---|\n| `vitest-bdd/no-inline-given` | error | Setup functions in `given()` must be named references or factory calls, not inline arrow functions |\n| `vitest-bdd/no-inline-then` | error | Assertion functions in `.then()` must be named references or factory calls |\n| `vitest-bdd/require-inline-when` | error | Action functions in `when()` must be inline arrow functions so the call is visible at the test site |\n\nUse `relaxed` instead of `recommended` to downgrade all rules to warnings.\n","readmeFilename":"README.md","description":"A BDD-style test helper for [Vitest](https://vitest.dev/) that structures tests as `given / when / then` scenarios and generates living documentation from the results."}