dream_test/gherkin/steps

Step definition registry for Gherkin scenarios.

Use this module to:

Example

  let steps =
    new()
    |> step("I have {int} items in my cart", step_have_items)
    |> step("I add {int} more items", step_add_items)
    |> step("I should have {int} items total", step_should_have)

Placeholder types

Step patterns use typed placeholders:

Placeholders can include literal prefixes/suffixes. For example, ${float} matches $19.99 but captures 19.99 as a Float.

Types

Context passed to every step handler.

Contains everything a step needs to execute:

  • captures - Values captured from pattern placeholders
  • table - Optional DataTable argument from the step
  • doc_string - Optional DocString argument from the step
  • world - Mutable scenario state
pub type StepContext {
  StepContext(
    captures: List(step_trie.CapturedValue),
    table: option.Option(List(List(String))),
    doc_string: option.Option(String),
    world: world.World,
  )
}

Constructors

  • StepContext(
      captures: List(step_trie.CapturedValue),
      table: option.Option(List(List(String))),
      doc_string: option.Option(String),
      world: world.World,
    )

    Arguments

    captures

    Values captured from pattern placeholders, in order

    table

    Optional DataTable attached to the step

    doc_string

    Optional DocString attached to the step

    world

    Mutable scenario state for sharing data between steps

Type alias for step handler functions.

All step handlers have the same signature: they receive a StepContext and return Result(AssertionResult, String), just like unit test bodies.

This means you can use the exact same assertion style inside steps:

fn step_should_have(context: StepContext) {
  let expected = get_int(context.captures, 0) |> result.unwrap(0)
  get_or(context.world, "cart", 0)
  |> should
  |> be_equal(expected)
  |> or_fail_with("Cart count mismatch")
}
pub type StepHandler =
  fn(StepContext) -> Result(types.AssertionResult, String)

Step registry backed by radix trie.

Stores step definitions and provides fast lookup.

“Fast” here means lookup cost grows with the length of the step text, not with how many step definitions you’ve registered.

pub opaque type StepRegistry

Values

pub fn capture_count(
  captures captures: List(step_trie.CapturedValue),
) -> Int

Get the number of captures.

Returns the total number of values captured from placeholders.

Parameters

  • captures: List of captured values from StepContext

Example

      capture_count(matched.captures)
      |> should
      |> be_equal(1)
      |> or_fail_with("expected exactly one capture")
pub fn find_step(
  registry registry: StepRegistry,
  keyword keyword: types.StepKeyword,
  text text: String,
) -> Result(
  step_trie.StepMatch(
    fn(StepContext) -> Result(types.AssertionResult, String),
  ),
  String,
)

Find a matching step definition.

Searches the registry for a handler matching the given keyword and text. Returns the handler and captured values on success, or an error message.

Performance

Lookup cost scales with the number of tokens in the step text after tokenization (the same tokenization used by the step trie).

Concretely: this is the total number of tokens in the input step text, not the number of unique words.

In practice:

  • Tokens are usually “words” separated by spaces.
  • Quoted strings are treated as a single token.
  • Some punctuation/number boundaries are split so patterns like {int}% or ${float}USD can match predictably.

Examples:

  • "I add 2 items" → 4 tokens (["I", "add", "2", "items"])
  • "the message is \"hello world\"" → 4 tokens (["the", "message", "is", "\"hello world\""])

This does not scale with the number of registered steps: you can register hundreds of steps and lookup still stays proportional to the tokenized input.

Parameters

  • registry: The registry to search
  • keyword: Step keyword (Given, When, Then, And, But)
  • text: Step text to match

Returns

  • Ok(StepMatch): Contains matched handler and captured values
  • Error(String): Error message if no match found

Example

      let registry = new() |> given("I have {int} items", step_pass)

      use matched <- result.try(find_step(registry, Given, "I have 3 items"))

      capture_count(matched.captures)
      |> should
      |> be_equal(1)
      |> or_fail_with("expected exactly one capture")
pub fn get_float(
  captures captures: List(step_trie.CapturedValue),
  index index: Int,
) -> Result(Float, String)

Get a Float capture by index (0-based).

Returns the captured float value at the given index, or an error if the index is out of bounds or the value isn’t a float.

Parameters

  • captures: List of captured values from StepContext
  • index: 0-based index of the capture to retrieve

Example

fn step_float(context: StepContext) {
  let value = get_float(context.captures, 0) |> result.unwrap(0.0)
  put(context.world, "float", value)
  Ok(succeed())
}
pub fn get_int(
  captures captures: List(step_trie.CapturedValue),
  index index: Int,
) -> Result(Int, String)

Get an Int capture by index (0-based).

Returns the captured integer value at the given index, or an error if the index is out of bounds or the value isn’t an integer.

Parameters

  • captures: List of captured values from StepContext
  • index: 0-based index of the capture to retrieve

Example

fn step_int(context: StepContext) {
  let value = get_int(context.captures, 0) |> result.unwrap(0)
  put(context.world, "int", value)
  Ok(succeed())
}
pub fn get_string(
  captures captures: List(step_trie.CapturedValue),
  index index: Int,
) -> Result(String, String)

Get a String capture by index (0-based).

Returns the captured string value at the given index. Works for both {string} (quoted) and {word} captures.

Parameters

  • captures: List of captured values from StepContext
  • index: 0-based index of the capture to retrieve

Example

fn step_string(context: StepContext) {
  let value = get_string(context.captures, 0) |> result.unwrap("")
  put(context.world, "string", value)
  Ok(succeed())
}
pub fn get_word(
  captures captures: List(step_trie.CapturedValue),
  index index: Int,
) -> Result(String, String)

Get a word capture by index (0-based).

Returns the captured word value at the given index. Works for both {word} and {} (anonymous) captures.

Parameters

  • captures: List of captured values from StepContext
  • index: 0-based index of the capture to retrieve

Example

fn step_word(context: StepContext) {
  let value = get_word(context.captures, 0) |> result.unwrap("")
  put(context.world, "word", value)
  Ok(succeed())
}
pub fn given(
  registry registry: StepRegistry,
  pattern pattern: String,
  handler handler: fn(StepContext) -> Result(
    types.AssertionResult,
    String,
  ),
) -> StepRegistry

Register a Given step definition.

Given steps describe initial context or preconditions.

Parameters

  • registry: The registry to add to
  • pattern: Step pattern with placeholders
  • handler: Handler function to execute

Returns

A new registry containing the added step.

Example

      let registry = new() |> given("I have {int} items", step_pass)
pub fn new() -> StepRegistry

Create an empty step registry.

Start with this and chain given, when_, then_ calls to add steps.

Example

let steps =
  new()
  |> step("I have {int} items", step_int)
  |> step("the price is ${float}", step_float)
  |> step("the message is {string}", step_string)
  |> step("the user is {word}", step_word)
  |> step("everything works", step_pass)
pub fn step(
  registry registry: StepRegistry,
  pattern pattern: String,
  handler handler: fn(StepContext) -> Result(
    types.AssertionResult,
    String,
  ),
) -> StepRegistry

Register a step that matches any keyword.

Use this for steps that work regardless of Given/When/Then context.

Parameters

  • registry: The registry to add to
  • pattern: Step pattern with placeholders
  • handler: Handler function to execute

Returns

A new registry containing the added step.

Example

let steps =
    new()
    |> step("I have {int} items in my cart", step_have_items)
    |> step("I add {int} more items", step_add_items)
    |> step("I should have {int} items total", step_should_have)
pub fn then_(
  registry registry: StepRegistry,
  pattern pattern: String,
  handler handler: fn(StepContext) -> Result(
    types.AssertionResult,
    String,
  ),
) -> StepRegistry

Register a Then step definition.

Then steps describe expected outcomes or assertions.

Note: Named then_ with trailing underscore because then is reserved.

Parameters

  • registry: The registry to add to
  • pattern: Step pattern with placeholders
  • handler: Handler function to execute

Returns

A new registry containing the added step.

Example

      let registry = new() |> then_("I should have {int} items", step_pass)
pub fn when_(
  registry registry: StepRegistry,
  pattern pattern: String,
  handler handler: fn(StepContext) -> Result(
    types.AssertionResult,
    String,
  ),
) -> StepRegistry

Register a When step definition.

When steps describe an action or event.

Note: Named when_ with trailing underscore because when is reserved.

Parameters

  • registry: The registry to add to
  • pattern: Step pattern with placeholders
  • handler: Handler function to execute

Returns

A new registry containing the added step.

Example

      let registry = new() |> when_("I add {int} items", step_pass)
Search Document