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 🙏

© 2024 – Pkg Stats / Ryan Hefner

racejar

v1.1.1

Published

A testing framework agnostic Gherkin driver

Downloads

566

Readme

racejar

A testing framework agnostic Gherkin driver

racejar is a thin wrapper around @cucumber/* that allows you to write your tests in Gherkin and run them with Vitest, Jest or any other testing framework of your choice.

pnpm add --save-dev racejar

Usage with Vitest

Using racejar with Vitest requires no additional configuration. Just drop it into a new or an existing test file and get going:

// your.test.ts

// Import `Feature` from `racejar/vitest`
import {Feature} from 'racejar/vitest'
// Import your raw `.feature` file
import featureFile from './your.feature?raw'

// Run the feature
Feature({
  featureText: featureFile,
  stepDefinitions: [
    // ...
  ],
  parameterTypes: [
    // ...
  ],
})

Usage with Jest

Using racejar with Jest is similar to Vitest. Just import Feature from racejar/jest instead.

However, Jest can't import raw files out of the box. You'll need a transformer. Luckily, it's easy to write and configure a simple one:

// jest.config.ts

import type {Config} from 'jest'

const config: Config = {
  transform: {
    '\\.feature$': '<rootDir>/feature-file-transformer.js',
  },
}
// feature-file-transformer.js

module.exports = {
  process(content) {
    return {
      code: `module.exports = ${JSON.stringify(content)};`,
    }
  },
}

Usage with <your favourite testing framework>

Feature exported from racejar/vitest and racejar/jest are convenient thin wrappers around the more generic compileFeature.

If you are unhappy with those wrappers or want to use racejar with another framework, then you can compile your feature manually and run your tests using the compiled feature:

import {compileFeature} from 'racejar'

const feature = compileFeature({
  featureText: featureFile,
  stepDefinitions: [
    // ...
  ],
  parameterTypes: [
    // ...
  ],
})

for (const scenario of feature.scenarios) {
  // ...
}

Define Steps and Parameter Types

stepDefinitions can be defined inline or separately. They are defined using Given, When, Then exported from racejar:

import {Given, Then, When} from 'racejar'

const stepDefinitions = [Given(/* ... */), When(/* ... */), Then(/* ... */)]

racejar will error out and inform you if a step definition is missing or if you accidentally defined duplicate definitions.

If you use nonstandard parameter types, then you can define them yourself:

import {createParameterType} from 'racejar'

const parameterTypes = [createParameterType(/* ... */)]

Basic Example

import {Given, Then, When} from 'racejar'
import {Feature} from 'racejar/vitest'
import {expect} from 'vitest'

function greet(name: string) {
  return `Hello, ${name}!`
}

type Context = {
  person: string
  greeting: string
}

Feature({
  featureText: `
    Feature: Greeting
      Scenario: Greeting a person
        Given the person "Herman"
        When greeting the person
        Then the greeting is "Hello, Herman!"`,
  stepDefinitions: [
    Given('the person {string}', (context: Context, person: string) => {
      context.person = person
    }),
    When('greeting the person', (context: Context) => {
      context.greeting = greet(context.person)
    }),
    Then('the greeting is {string}', (context: Context, greeting: string) => {
      expect(context.greeting).toBe(greeting)
    }),
  ],
})

Advanced Example

The following example is taken from the editor package in this repository. For a full example of how to use racejar, head over to /packages/editor/gherkin-tests/. The package uses racejar to run a Playwright E2E test suite powered by Vitest Browser Mode.

This feature file tests that the editor can annotate text and additionally asserts the text selection after an annotation is applied. It uses 7 steps which need to be defined:

// annotations.feature

Feature: Annotations

  Background:
    Given one editor
    And a global keymap

  Scenario: Selection after adding an annotation
    Given the text "foo bar baz"
    When "bar" is selected
    And "link" "l1" is toggled
    Then "bar" has marks "l1"
    And "bar" is selected

Here's a rough idea of how these steps can be defined:

import {Given, Then, When} from 'racejar'

// A `context` object is passed around between steps
// The context can be whatever you want it to be
type Context = {
  locator: Locator
  keyMap: Map<string, string>
}

export const stepDefinitions = [
  Given('one editor', async (context: Context) => {
    render(<Editor />)
    const locator = page.getByTestId('<editor test ID>')
    context.locator = locator
    await vi.waitFor(() => expect.element(locator).toBeInTheDocument())
  }),
  Given('a global keymap', (context: Context) => {
    context.keyMap = new Map()
  }),
  Given('the text {string}', async (context: Context, text: string) => {
    await userEvent.click(context.locator)
    await userEvent.type(context.locator, text)
  }),
  Given('{string} is selected', async (context: Context, text: string) => {
    // Select `text` in the editor
  }),
  When(
    '{annotation} {keys} is toggled',
    async (
      context: Context,
      annotation: 'comment' | 'link',
      keyKeys: Array<string>,
    ) => {
      // Toggle the `annotation` and store the resulting keys on the `context.keyMap`
    },
  ),
  Then(
    '{string} has marks {marks}',
    async (context: Context, text: string, marks: Array<string>) => {
      // Get the actual marks on the `text` and compare them with `marks`
    },
  ),
  Then('{text} is selected', async (context: Context, text: Array<string>) => {
    // Assert that the current editor selection matches `text`
  }),
]

As you can see, the step definitions declare a few custom parameters, {annotation}, {keys} and {text}:

import {createParameterType} from 'racejar'

export const parameterTypes = [
  createParameterType({
    name: 'annotation',
    matcher: /"(comment|link)"/,
  }),
  createParameterType({
    name: 'keys',
    matcher: /"(([a-z]\d)(,([a-z]\d))*)"/,
    type: Array,
    transform: (input) => input.split(','),
  }),
  createParameterType({
    name: 'text',
    matcher: /"([a-z-,#>\\n |\[\]]*)"/,
    type: Array,
    transform: parseGherkinTextParameter,
  }),
]

function parseGherkinTextParameter(text: string) {
  return text
    .replace(/\|/g, ',|,')
    .split(',')
    .map((span) => span.replace(/\\n/g, '\n'))
}

Now, let's run the test using our defined steps and custom parameter types:

// annotations.test.ts

import annotationsFeature from './annotations.feature?raw'
import {parameterTypes} from './parameter-types'
import {stepDefinitions} from './step-definitions'

Feature({
  featureText: annotationsFeature,
  stepDefinitions,
  parameterTypes,
})

Tips

If TypeScript errors out with Cannot find module '.your.feature?raw' or its corresponding type declarations. then you can declare .*feature?raw files as modules:

// global.d.ts

declare module '*.feature?raw' {
  const content: string
  export default content
}

Use prettier-plugin-gherkin to automatically format your .feature files.

// .prettierrc

{
  "plugins": ["prettier-plugin-gherkin"]
}