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

vitest-story

v0.0.3

Published

A lightweight, fast, and modern BDD framework built directly on top of Vitest.

Readme

vitest-story

A lightweight, fast, and modern BDD framework built directly on top of Vitest.

The Story Behind vitest-story

We built vitest-story out of frustration. While we appreciated the Behavior Driven Development (BDD) methodology, the existing tooling in the JavaScript ecosystem felt heavy and fragmented. Cucumber JS, while the standard, often introduced significant overhead, slow startup times, and a disconnect between the feature files and the code.

We wanted a solution that:

  1. Ran at the speed of modern development. We didn't want to wait for a slow test runner.
  2. Reduced context switching. We wanted our stories to live close to our code, not isolated in separate text files that drift out of sync.
  3. Leveraged the tools we already use. We didn't want a separate CLI or a complex configuration just to run BDD tests.

We chose Vitest as our bedrock because it is blazing fast, has excellent TypeScript support out of the box, and integrates seamlessly with the modern web ecosystem (Vite, Vue, React, etc.). vitest-story is a thin, powerful layer that brings Gherkin-style syntax directly into your Vitest suites.

Features

  • Inline Stories: Write your Gherkin scenarios directly in your TypeScript/JavaScript test files using the story template literal. No more context switching between .feature files and step definitions.
  • Native Vitest Integration: Runs as standard Vitest tests. You get all the benefits of Vitest: watch mode, smart filtering, parallel execution, and instant feedback.
  • Type-Safe Steps: Define steps (Given, When, Then) with full TypeScript support.
  • Flexible Organization: Colocate steps with tests or organize them in a dedicated directory—it's up to you.
  • Lifecycle Hooks: Full support for beforeFeature, afterFeature, beforeScenario, and afterScenario hooks.
  • Zero Boilerplate: Minimal configuration required. Install, import, and write your first story.

Comparison with Cucumber JS

| Feature | Cucumber JS | vitest-story | | :------------------- | :----------------------------- | :---------------------------------- | | Execution Engine | Custom CLI / Runner | Vitest | | Speed | Slower startup, overhead | Blazing fast (powered by Vite) | | Syntax | .feature files (Gherkin) | Template literals in .ts/.js | | Step Definitions | Separate files, regex matching | Imported functions, string matching | | TypeScript | Requires setup/compilation | Native support | | Ecosystem | Large, but fragmented | Integrated with Vite/Vitest |

Benefits

  • Speed: Tests run instantly.
  • Simplicity: No complex glue code. Just imports.
  • Developer Experience: IntelliSense, type checking, and debugging work out of the box in your editor.
  • Maintainability: Keep your requirements (stories) and verification (tests) in sync effortlessly.

Getting Started

Installation

npm install -D vitest vitest-story
# or
pnpm add -D vitest vitest-story
# or
yarn add -D vitest vitest-story

Writing Your First Story

  1. Define your steps. You can do this in a separate file or right next to your test.
// steps.ts
import { Given, When, Then } from "vitest-story";
import { expect } from "vitest";

let accountBalance = 0;

Given("my account balance is {int}", (amount: number) => {
  accountBalance = amount;
});

When("I withdraw {int}", (amount: number) => {
  accountBalance -= amount;
});

Then("my account balance should be {int}", (amount: number) => {
  expect(accountBalance).toBe(amount);
});
  1. Write your story. Create a test file (e.g., bank.test.ts).
// bank.test.ts
import { story } from "vitest-story";
import "./steps"; // Import your steps

story`
  Feature: Bank Account Operations

  Scenario: Successful Withdrawal
    Given my account balance is 100
    When I withdraw 20
    Then my account balance should be 80

  Scenario: Multiple Withdrawals
    Given my account balance is 500
    When I withdraw 50
    And I withdraw 50
    Then my account balance should be 400
`;
  1. Run it.
npx vitest

Example: Shopping Cart

Here is a more complete example showing how vitest-story handles data and multiple steps.

import { story } from "vitest-story";
import { expect } from "vitest";
import { Given, When, Then } from "vitest-story";

// Simple cart implementation for the example
const cart = {
  items: [] as string[],
  add(item: string) {
    this.items.push(item);
  },
  contains(item: string) {
    return this.items.includes(item);
  },
  size() {
    return this.items.length;
  },
};

Given("the shopping cart is empty", () => {
  cart.items = [];
});

When("I add {string} to the cart", (item: string) => {
  cart.add(item);
});

Then("the cart should contain {int} item(s)", (count: number) => {
  expect(cart.size()).toBe(count);
});

Then("the cart should contain {string}", (item: string) => {
  expect(cart.contains(item)).toBe(true);
});

story`
  Feature: Shopping Cart

  Scenario: Adding items
    Given the shopping cart is empty
    When I add "Apple" to the cart
    And I add "Banana" to the cart
    Then the cart should contain 2 items
    And the cart should contain "Apple"
`;

Tags

Tags allow you to organize and filter your scenarios. You can use tags to run subsets of your tests or skip specific scenarios.

Basic Tag Usage

Tags are prefixed with @ and can be placed before Feature or Scenario keywords:

import { story } from "vitest-story";

story`
  @fast
  @calculator
  Feature: Calculator Operations

  Background:
    Given the calculator is reset

  @smoke
  Scenario: Addition
    When I add 5
    Then the result should be 5

  @slow
  Scenario: Complex calculation
    When I perform complex operations
    Then the result should be correct
`;

Skipping Scenarios

Use the @skip tag to skip scenarios:

story`
  Feature: Calculator

  @skip
  Scenario: This test is temporarily disabled
    Given I have an incomplete feature
    Then it should not run
`;

Filtering Tests by Tags

You can filter tests by tags using the VITEST_STORY_TAGS environment variable:

# Run only scenarios tagged with @smoke
VITEST_STORY_TAGS=smoke npx vitest

# Run scenarios with either @smoke or @fast tags
VITEST_STORY_TAGS=smoke,fast npx vitest

Alternatively, configure tags programmatically:

import { configureVitestStory } from "vitest-story";

configureVitestStory({
  tags: ["smoke", "fast"],
});

You can also use Vitest projects to configure different tag combinations in your vitest.config.ts:

import { defineConfig } from "vitest/config";
import { vitestStoryPlugin } from "vitest-story";

export default defineConfig({
  plugins: [
    vitestStoryPlugin({
      stepsPaths: ["./steps"],
      storyPaths: ["./stories"],
    }),
  ],
  test: {
    include: ["**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}", "**/*.story"],
    // Define multiple projects with different tag filters
    projects: [
      {
        extends: true,
        test: {
          name: "smoke-tests",
          env: {
            VITEST_STORY_TAGS: "smoke",
          },
        },
      },
      {
        extends: true,
        test: {
          name: "fast-tests",
          env: {
            VITEST_STORY_TAGS: "smoke,fast",
          },
        },
      },
    ],
  },
});

Run specific project:

# Run smoke tests only
npx vitest --project=smoke-tests

# Run fast tests
npx vitest --project=fast-tests

# Run all projects
npx vitest

Tag Inheritance

Scenarios inherit tags from their feature. If a feature is tagged with @fast, all scenarios in that feature are considered to have the @fast tag.

story`
  @fast
  Feature: Quick Tests

  Scenario: Test 1
    # This scenario inherits @fast from the feature
    Given I start
`;

Tag Priority

The @skip tag always takes priority. Even if a scenario matches your tag filter, it will be skipped if it has the @skip tag.

story`
  Feature: Tests

  @smoke @skip
  Scenario: Skipped smoke test
    # This will be skipped despite having @smoke tag
    Given I start
`;