Skip to content

Astro

You can use vitest-cucumber with Astro content collections to generate documentation pages from the same feature files your tests use. This guide uses loadFeatureFromText to parse feature files into Feature objects.

You can use Astro’s built-in file() loader with loadFeatureFromText as a custom parser:

// src/content.config.ts
import { defineCollection } from "astro:content"
import { file } from "astro/loaders"
import { loadFeatureFromText } from "@amiceli/vitest-cucumber"

const features = defineCollection({
    loader: file("../path/to/features/**/*.feature", {
        parser: (content) => {
            const feature = loadFeatureFromText(content)
            return [feature]
        },
    }),
})

export const collections = { features }

If you need more control (e.g. custom IDs, multiple languages, or filtering), you can write a custom loader:

// src/loaders/feature-loader.ts
import type { Loader } from "astro/loaders"
import { readFile } from "node:fs/promises"
import { glob as globby } from "glob"
import { loadFeatureFromText } from "@amiceli/vitest-cucumber"

export function featureLoader(options: {
    pattern: string
    base: string
    language?: string
}): Loader {
    return {
        name: "feature-loader",
        async load({ store, parseData }) {
            const files = await globby(options.pattern, { cwd: options.base })

            for (const file of files) {
                const fullPath = `${options.base}/${file}`
                const content = await readFile(fullPath, "utf-8")
                const feature = loadFeatureFromText(content, {
                    language: options.language,
                })

                const id = file.replace(/\.feature$/, "")
                const data = await parseData({ id, data: feature })
                store.set({ id, data })
            }
        },
    }
}

When your collection is defined, you can query it like any other Astro content collection:

---
// src/pages/features/index.astro
import { getCollection } from "astro:content"

const features = await getCollection("features")
---

<ul>
  {features.map((f) => (
    <li>
      <a href={`/features/${f.id}`}>{f.data.name}</a>
      {f.data.description && <p>{f.data.description}</p>}
    </li>
  ))}
</ul>
---
// src/pages/features/[id].astro
import { getCollection } from "astro:content"

export async function getStaticPaths() {
  const features = await getCollection("features")
  return features.map((f) => ({ params: { id: f.id }, props: { feature: f } }))
}

const { feature } = Astro.props
const { name, description, scenarii, background, rules, tags } = feature.data
---

<h1>{name}</h1>
{description && <p>{description}</p>}
{tags.size > 0 && (
  <div>
    {[...tags].map((tag) => <span>@{tag}</span>)}
  </div>
)}

{background && (
  <section>
    <h2>Background</h2>
    <ul>
      {[...background.steps].map((s) => <li>{s.type} {s.details}</li>)}
    </ul>
  </section>
)}

{[...scenarii].map((scenario) => (
  <section>
    <h2>{scenario.getTitle()}</h2>
    <ul>
      {[...scenario.steps].map((s) => <li>{s.type} {s.details}</li>)}
    </ul>
  </section>
))}