npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@abendigo/vitest-living-docs

v0.3.0

Published

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.

Downloads

49

Readme

@abendigo/vitest-living-docs

A BDD-style test helper for Vitest that structures tests as given / when / then scenarios and generates living documentation from the results.

Tests read like specifications. The living docs output makes that structure visible to anyone — not just the people who wrote the tests.

Installation

npm install --save-dev @abendigo/vitest-living-docs

Requires vitest >= 2.0.0 as a peer dependency.

Usage

Tests are written using three functions: given, when, and then.

  • given — sets up the world (database state, users, fixtures). Uses named factory functions so the setup is reusable and readable.
  • when — performs the action being tested. Uses an inline arrow function so the call is visible at the test site.
  • then — asserts the outcome. Uses named assertion factories so labels match exactly what is being checked.
import { given, when } from '@abendigo/vitest-living-docs'

// Setup factories — named, reusable
const withDatabase = async (fixture) => {
  const db = await createTestDb()
  return { ...fixture, db }
}

const withUser = (key, { email }) => async (fixture) => {
  const user = await createUser(fixture.db, email, 'password')
  return { ...fixture, users: { ...fixture.users, [key]: user } }
}

// Assertion factories — named, specific
const returnsTrip = (title) => (_fixture, results) => {
  const trip = results[0]
  if (trip.title !== title) throw new Error(`Expected '${title}', got '${trip.title}'`)
}

const tripIsOpen = (_fixture, results) => {
  const trip = results[0]
  if (trip.status !== 'open') throw new Error(`Expected status 'open', got '${trip.status}'`)
}

// The test
given(
  ['an owner exists', [withDatabase, withUser('owner', { email: '[email protected]' })]],

  when(
    ['they create a trip', ({ db, users }) => createTrip(db, { owner_id: users.owner.id, title: 'Sunset Cruise' })],
  ).then(
    ['the trip title is Sunset Cruise', returnsTrip('Sunset Cruise')],
    ['the trip status is open',         tripIsOpen],
  ),
)

This produces a single Vitest test named:

an owner exists / they create a trip / the trip title is Sunset Cruise
an owner exists / they create a trip / the trip status is open

Multiple scenarios from one setup

A single given block can contain multiple when / then scenarios. Setup runs once per scenario — they don't share state.

given(
  ['an owner exists', [withDatabase, withUser('owner', { email: '[email protected]' })]],

  when(
    ['they create a trip with one date', ({ db, users }) => createTrip(db, {
      owner_id: users.owner.id,
      title: 'Sunset Cruise',
      dates: [{ departure_date: '2026-07-05' }]
    })],
  ).then(
    ['the date is auto-confirmed', dateIsConfirmed],
  ),

  when(
    ['they create a trip with multiple dates', ({ db, users }) => createTrip(db, {
      owner_id: users.owner.id,
      title: 'Raft-Up Weekend',
      dates: [
        { departure_date: '2026-07-05' },
        { departure_date: '2026-07-12' },
      ]
    })],
  ).then(
    ['no date is confirmed yet', noDateIsConfirmed],
  ),
)

Multiple setup steps

Pass an array of setup functions as the second element of a given tuple to chain them:

given(
  ['a trip exists with two proposed dates', [withDatabase, withUser('owner', { email: '[email protected]' }), withTrip('trip', 'owner'), withTripDate('a'), withTripDate('b')]],

  when(
    ['the owner confirms date a', ({ db, trips, dates }) => setTripDateConfirmed(db, trips.trip.id, dates.a.id, true)],
  ).then(
    ['date a is confirmed', dateAIsConfirmed],
    ['date b is not confirmed', dateBIsNotConfirmed],
  ),
)

Features

Group related scenarios under a named feature using feature():

import { feature, given, when } from '@abendigo/vitest-living-docs'

feature('Trip creation', [
  'Owners can create trips with optional dates.',
  'A single date is auto-confirmed; multiple dates go to a vote.',
], () => {
  given(
    ['an owner exists', [withDatabase, withUser('owner', { email: '[email protected]' })]],
    // ... scenarios
  )
})

Generating living docs

Run Vitest with JSON output, then generate the HTML report:

# Run tests and write results to test-results.json
npx vitest run --reporter=json --outputFile=test-results.json

# Generate the HTML report
npx vitest-living-docs [output-path]
# Default output: static/dev/tests/index.html

Or add a script to package.json:

{
  "scripts": {
    "test:report": "vitest run --reporter=json --outputFile=test-results.json && vitest-living-docs"
  }
}

Living docs output

The 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.

┌─ Trips ──────────────────────────────────────────────────────────┐
│                                                                   │
│  given   an owner exists                                          │
│  ├─ when   they create a trip with one date                       │
│  │    then  ✓ the date is auto-confirmed                          │
│  │                                                                │
│  ├─ when   they create a trip with multiple dates                 │
│  │    then  ✓ no date is confirmed yet                            │
│  │                                                                │
│  given   a trip exists with two proposed dates                    │
│  ├─ when   the owner confirms date a                              │
│       then  ✓ date a is confirmed                                 │
│             ✓ date b is not confirmed                             │
│                                                                   │
└───────────────────────────────────────────────────────────────────┘

Failing tests surface at the top of the report with a summary and inline failure messages.

See a live example: frustrated.blog/vitest-living-docs

ESLint plugin

An optional ESLint plugin enforces consistent style in given / when / then calls:

// eslint.config.js
import { recommended } from '@abendigo/vitest-living-docs/eslint'

export default [
  recommended,
]

Rules:

| Rule | Default | Description | |---|---|---| | vitest-bdd/no-inline-given | error | Setup functions in given() must be named references or factory calls, not inline arrow functions | | vitest-bdd/no-inline-then | error | Assertion functions in .then() must be named references or factory calls | | vitest-bdd/require-inline-when | error | Action functions in when() must be inline arrow functions so the call is visible at the test site |

Use relaxed instead of recommended to downgrade all rules to warnings.