Skip to main content

ruleOutline()

ruleOutline runs the same rule multiple times with different data sets — the Specification pattern equivalent of scenarioOutline. The Examples table is embedded in the title, and each row produces a separate test.

import { specification, ruleOutline } from "@swedevtools/livedoc-vitest";

specification("Tax Calculation", () => {
ruleOutline(`Tax rate by income bracket
Examples:
| income | rate |
| 10000 | 0.10 |
| 50000 | 0.22 |
| 100000 | 0.32 |
`, (ctx) => {
const result = calculateTaxRate(ctx.example.income);
expect(result).toBe(ctx.example.rate);
});
});

Reference

ruleOutline(title, fn)

Registers a data-driven rule. The title must contain an Examples: table. The callback runs once per table row.

function ruleOutline(title: string, fn: (ctx: RuleOutlineCtx) => void): void

See more examples below.

Parameters

  • title: string — The rule outline title followed by an Examples: table. Parsed as:

    • First line → rule title
    • @-prefixed lines → tags
    • Other lines before Examples: → description
    • Examples: block → pipe-delimited table (first row = headers, subsequent rows = data)
  • fn: (ctx: RuleOutlineCtx) => void — Callback containing assertions. Runs once per example row. Should not be async.

The ctx Parameter

PropertyTypeDescription
ctx.specificationSpecificationContext{ filename, title, description, tags }
ctx.ruleRuleContext{ title, description, tags, specification }
ctx.exampleRecord<string, any>Current row data keyed by column header name

The Examples Table

Values are auto-coerced:

Input in TableCoerced Type
42number
3.14number
trueboolean
falseboolean
hellostring

Returns

void — Rule outlines are registered as side effects.

Caveats

  • The Examples: keyword is required. Without it, no rows are parsed.
  • Column names become properties on ctx.example. Use valid JavaScript identifiers.
  • Each row runs as a separate Vitest it() call, independently reportable.
  • ctx.rule.values and ctx.rule.params are not populated in ruleOutline — use ctx.example instead.

Modifiers

ruleOutline.skip(title, fn)

Skip this rule outline entirely.

ruleOutline.skip(`Pending feature
Examples:
| input | expected |
| a | b |
`, () => { /* skipped */ });

ruleOutline.only(title, fn)

Run only this rule outline.

ruleOutline.only(`Focus on this
Examples:
| input | expected |
| a | b |
`, () => { /* only this runs */ });

Usage

Basic: Data-driven rule

import { specification, ruleOutline } from "@swedevtools/livedoc-vitest";

specification("Math Operations", () => {
ruleOutline(`Addition
Examples:
| a | b | sum |
| 1 | 2 | 3 |
| 10 | 20 | 30 |
| -5 | 5 | 0 |
`, (ctx) => {
expect(ctx.example.a + ctx.example.b).toBe(ctx.example.sum);
});
});

Multiple example tables

All rows from all tables are merged and executed. Labels are for documentation only.

import { specification, ruleOutline } from "@swedevtools/livedoc-vitest";

specification("Feeding Requirements", () => {
ruleOutline(`Daily energy needs by region

Examples: Australian Cows
| weight | energy | protein |
| 450 | 26500 | 215 |
| 500 | 29500 | 245 |

Examples: New Zealand Cows
| weight | energy | protein |
| 1450 | 46500 | 1215 |
| 1500 | 49500 | 1245 |
`, (ctx) => {
const result = calculateFeedRequirements(ctx.example.weight);
expect(result.energy).toBe(ctx.example.energy);
expect(result.protein).toBe(ctx.example.protein);
});
});

Boundary value testing

import { specification, ruleOutline } from "@swedevtools/livedoc-vitest";

specification("Age Verification", () => {
ruleOutline(`Age verification boundaries
Examples:
| age | allowed |
| 16 | false |
| 17 | false |
| 18 | true |
| 19 | true |
| 21 | true |
`, (ctx) => {
expect(isAllowed(ctx.example.age)).toBe(ctx.example.allowed);
});
});

With tags and description

import { specification, ruleOutline } from "@swedevtools/livedoc-vitest";

specification("Email Validation", () => {
ruleOutline(`Valid emails are accepted
@validation @email
Ensures the email regex handles all standard formats

Examples:
| email | valid |
| test@test.com | true |
| user+tag@gmail.com | true |
| invalid | false |
| @nodomain.com | false |
| user@.com | false |
`, (ctx) => {
const result = isValidEmail(ctx.example.email);
expect(result).toBe(ctx.example.valid);
});
});

Combining rule and ruleOutline

import { specification, rule, ruleOutline } from "@swedevtools/livedoc-vitest";

specification("Discount Engine", () => {
rule("No discount for new customers on first order", () => {
const customer = createCustomer({ isNew: true });
expect(getDiscount(customer, 100)).toBe(0);
});

ruleOutline(`Loyalty discount tiers
Examples:
| orders | spend | discount |
| 10 | 500 | 5 |
| 25 | 2000 | 10 |
| 50 | 5000 | 15 |
`, (ctx) => {
const customer = createCustomer({
totalOrders: ctx.example.orders,
totalSpend: ctx.example.spend,
});
expect(getDiscount(customer, 100)).toBe(ctx.example.discount);
});
});

See Also