-module(dream_test@gherkin@world). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream_test/gherkin/world.gleam"). -export([new_world/1, cleanup/1, put/3, get/2, get_or/3, has/2, delete/2, scenario_id/1]). -export_type([world/0, ets_table/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Scenario state management for Gherkin tests.\n" "\n" " Each scenario gets its own World instance for storing state between steps.\n" " The World is automatically created before a scenario runs and cleaned up after.\n" "\n" " ## How It Works\n" "\n" " The World uses ETS (Erlang Term Storage) for mutable storage with process\n" " isolation. Each scenario gets a unique table that's cleaned up automatically.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/feature.{feature, given, scenario, then, when}\n" " import dream_test/gherkin/steps.{type StepContext, get_float, step}\n" " import dream_test/gherkin/world.{get_or, put}\n" " import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n" " import dream_test/reporters/bdd\n" " import dream_test/reporters/progress\n" " import dream_test/runner\n" " import gleam/io\n" " import gleam/result\n" "\n" " // NOTE: We annotate `StepContext` because record field access needs a known type.\n" " fn step_have_balance(context: StepContext) {\n" " // {float} captures the numeric value (even with $ prefix)\n" " let balance = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " put(context.world, \"balance\", balance)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_withdraw(context: StepContext) {\n" " let current = get_or(context.world, \"balance\", 0.0)\n" " let amount = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " put(context.world, \"balance\", current -. amount)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_balance_is(context: StepContext) {\n" " let expected = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " get_or(context.world, \"balance\", 0.0)\n" " |> should\n" " |> be_equal(expected)\n" " |> or_fail_with(\"Balance mismatch\")\n" " }\n" "\n" " pub fn register(registry) {\n" " registry\n" " |> step(\"I have a balance of ${float}\", step_have_balance)\n" " |> step(\"I withdraw ${float}\", step_withdraw)\n" " |> step(\"my balance should be ${float}\", step_balance_is)\n" " }\n" "\n" " pub fn tests() {\n" " let steps = steps.new() |> register()\n" "\n" " feature(\"Bank Account\", steps, [\n" " scenario(\"Withdrawal\", [\n" " given(\"I have a balance of $100.00\"),\n" " when(\"I withdraw $30.00\"),\n" " then(\"my balance should be $70.00\"),\n" " ]),\n" " ])\n" " }\n" "\n" " pub fn main() {\n" " runner.new([tests()])\n" " |> runner.progress_reporter(progress.new())\n" " |> runner.results_reporters([bdd.new()])\n" " |> runner.exit_on_failure()\n" " |> runner.run()\n" " }\n" " ```\n" ). -opaque world() :: {world, binary(), ets_table()}. -type ets_table() :: any(). -file("src/dream_test/gherkin/world.gleam", 118). ?DOC( " Create a new World for a scenario.\n" "\n" " Called automatically by the runner before each scenario.\n" " Each World gets a unique ETS table for isolated storage.\n" "\n" " ## Parameters\n" "\n" " - `scenario_id`: Unique identifier for the scenario\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/world\n" "\n" " pub fn main() {\n" " // In normal gherkin runs, the runner creates and cleans up the World for you.\n" " let w = world.new_world(\"example_scenario\")\n" " world.cleanup(w)\n" " }\n" " ```\n" ). -spec new_world(binary()) -> world(). new_world(Scenario_id) -> Table_name = <<<<<<"gherkin_world_"/utf8, Scenario_id/binary>>/binary, "_"/utf8>>/binary, (dream_test_gherkin_world_ffi:unique_id())/binary>>, Table = dream_test_gherkin_world_ffi:create_table(Table_name), {world, Scenario_id, Table}. -file("src/dream_test/gherkin/world.gleam", 145). ?DOC( " Clean up the World after a scenario completes.\n" "\n" " Called automatically by the runner after each scenario.\n" " Deletes the ETS table and frees resources.\n" "\n" " ## Parameters\n" "\n" " - `world`: The World to clean up\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/world\n" "\n" " pub fn main() {\n" " // In normal gherkin runs, the runner creates and cleans up the World for you.\n" " let w = world.new_world(\"example_scenario\")\n" " world.cleanup(w)\n" " }\n" " ```\n" ). -spec cleanup(world()) -> nil. cleanup(World) -> dream_test_gherkin_world_ffi:delete_table(erlang:element(3, World)). -file("src/dream_test/gherkin/world.gleam", 228). ?DOC( " Store a value in the World.\n" "\n" " Stores any value by string key. If the key already exists,\n" " the value is replaced.\n" "\n" " ## Parameters\n" "\n" " - `world`: The World to store in\n" " - `key`: String key for the value\n" " - `value`: Any value to store\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/feature.{feature, given, scenario, then, when}\n" " import dream_test/gherkin/steps.{type StepContext, get_float, step}\n" " import dream_test/gherkin/world.{get_or, put}\n" " import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n" " import dream_test/reporters/bdd\n" " import dream_test/reporters/progress\n" " import dream_test/runner\n" " import gleam/io\n" " import gleam/result\n" "\n" " // NOTE: We annotate `StepContext` because record field access needs a known type.\n" " fn step_have_balance(context: StepContext) {\n" " // {float} captures the numeric value (even with $ prefix)\n" " let balance = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " put(context.world, \"balance\", balance)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_withdraw(context: StepContext) {\n" " let current = get_or(context.world, \"balance\", 0.0)\n" " let amount = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " put(context.world, \"balance\", current -. amount)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_balance_is(context: StepContext) {\n" " let expected = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " get_or(context.world, \"balance\", 0.0)\n" " |> should\n" " |> be_equal(expected)\n" " |> or_fail_with(\"Balance mismatch\")\n" " }\n" "\n" " pub fn register(registry) {\n" " registry\n" " |> step(\"I have a balance of ${float}\", step_have_balance)\n" " |> step(\"I withdraw ${float}\", step_withdraw)\n" " |> step(\"my balance should be ${float}\", step_balance_is)\n" " }\n" "\n" " pub fn tests() {\n" " let steps = steps.new() |> register()\n" "\n" " feature(\"Bank Account\", steps, [\n" " scenario(\"Withdrawal\", [\n" " given(\"I have a balance of $100.00\"),\n" " when(\"I withdraw $30.00\"),\n" " then(\"my balance should be $70.00\"),\n" " ]),\n" " ])\n" " }\n" "\n" " pub fn main() {\n" " runner.new([tests()])\n" " |> runner.progress_reporter(progress.new())\n" " |> runner.results_reporters([bdd.new()])\n" " |> runner.exit_on_failure()\n" " |> runner.run()\n" " }\n" " ```\n" ). -spec put(world(), binary(), any()) -> nil. put(World, Key, Value) -> dream_test_gherkin_world_ffi:insert(erlang:element(3, World), Key, Value). -file("src/dream_test/gherkin/world.gleam", 301). ?DOC( " Retrieve a value from the World.\n" "\n" " Returns `Ok(value)` if the key exists, `Error(message)` if not found.\n" " The caller is responsible for ensuring type consistency.\n" "\n" " ## Parameters\n" "\n" " - `world`: The World to query\n" " - `key`: String key to look up\n" "\n" " ## Returns\n" "\n" " - `Ok(value)`: Key exists, returns the stored value\n" " - `Error(String)`: Key doesn't exist (human-readable message)\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/feature.{feature, given, scenario, then}\n" " import dream_test/gherkin/steps.{type StepContext, step}\n" " import dream_test/gherkin/world.{get, put}\n" " import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n" " import dream_test/reporters/bdd\n" " import dream_test/reporters/progress\n" " import dream_test/runner\n" " import gleam/io\n" "\n" " fn step_store(context: StepContext) {\n" " put(context.world, \"count\", 42)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_count_is_42(context: StepContext) {\n" " case get(context.world, \"count\") {\n" " Ok(count) ->\n" " count\n" " |> should\n" " |> be_equal(42)\n" " |> or_fail_with(\"count mismatch\")\n" " Error(message) -> Error(message)\n" " }\n" " }\n" "\n" " pub fn register(registry) {\n" " registry\n" " |> step(\"count is stored\", step_store)\n" " |> step(\"count should be 42\", step_count_is_42)\n" " }\n" "\n" " pub fn tests() {\n" " let steps = steps.new() |> register()\n" "\n" " feature(\"World: get\", steps, [\n" " scenario(\"Reading a stored value\", [\n" " given(\"count is stored\"),\n" " then(\"count should be 42\"),\n" " ]),\n" " ])\n" " }\n" "\n" " pub fn main() {\n" " runner.new([tests()])\n" " |> runner.progress_reporter(progress.new())\n" " |> runner.results_reporters([bdd.new()])\n" " |> runner.exit_on_failure()\n" " |> runner.run()\n" " }\n" " ```\n" ). -spec get(world(), binary()) -> {ok, any()} | {error, binary()}. get(World, Key) -> case dream_test_gherkin_world_ffi:lookup(erlang:element(3, World), Key) of {some, Value} -> {ok, Value}; none -> {error, <<"World key not found: "/utf8, Key/binary>>} end. -file("src/dream_test/gherkin/world.gleam", 383). ?DOC( " Retrieve a value or return a default.\n" "\n" " Convenience function that returns the stored value if the key exists,\n" " or the provided default if not found.\n" "\n" " ## Parameters\n" "\n" " - `world`: The World to query\n" " - `key`: String key to look up\n" " - `default`: Default value to return if key not found\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/feature.{feature, given, scenario, then, when}\n" " import dream_test/gherkin/steps.{type StepContext, get_float, step}\n" " import dream_test/gherkin/world.{get_or, put}\n" " import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n" " import dream_test/reporters/bdd\n" " import dream_test/reporters/progress\n" " import dream_test/runner\n" " import gleam/io\n" " import gleam/result\n" "\n" " // NOTE: We annotate `StepContext` because record field access needs a known type.\n" " fn step_have_balance(context: StepContext) {\n" " // {float} captures the numeric value (even with $ prefix)\n" " let balance = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " put(context.world, \"balance\", balance)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_withdraw(context: StepContext) {\n" " let current = get_or(context.world, \"balance\", 0.0)\n" " let amount = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " put(context.world, \"balance\", current -. amount)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_balance_is(context: StepContext) {\n" " let expected = get_float(context.captures, 0) |> result.unwrap(0.0)\n" " get_or(context.world, \"balance\", 0.0)\n" " |> should\n" " |> be_equal(expected)\n" " |> or_fail_with(\"Balance mismatch\")\n" " }\n" "\n" " pub fn register(registry) {\n" " registry\n" " |> step(\"I have a balance of ${float}\", step_have_balance)\n" " |> step(\"I withdraw ${float}\", step_withdraw)\n" " |> step(\"my balance should be ${float}\", step_balance_is)\n" " }\n" "\n" " pub fn tests() {\n" " let steps = steps.new() |> register()\n" "\n" " feature(\"Bank Account\", steps, [\n" " scenario(\"Withdrawal\", [\n" " given(\"I have a balance of $100.00\"),\n" " when(\"I withdraw $30.00\"),\n" " then(\"my balance should be $70.00\"),\n" " ]),\n" " ])\n" " }\n" "\n" " pub fn main() {\n" " runner.new([tests()])\n" " |> runner.progress_reporter(progress.new())\n" " |> runner.results_reporters([bdd.new()])\n" " |> runner.exit_on_failure()\n" " |> runner.run()\n" " }\n" " ```\n" ). -spec get_or(world(), binary(), GJJ) -> GJJ. get_or(World, Key, Default) -> case get(World, Key) of {ok, Value} -> Value; {error, _} -> Default end. -file("src/dream_test/gherkin/world.gleam", 456). ?DOC( " Check if a key exists in the World.\n" "\n" " Returns `True` if the key exists, `False` otherwise.\n" "\n" " ## Parameters\n" "\n" " - `world`: The World to query\n" " - `key`: String key to check\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/feature.{feature, given, scenario, then, when}\n" " import dream_test/gherkin/steps.{type StepContext, step}\n" " import dream_test/gherkin/world.{delete, has, put}\n" " import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n" " import dream_test/reporters/bdd\n" " import dream_test/reporters/progress\n" " import dream_test/runner\n" " import gleam/io\n" "\n" " fn step_store(context: StepContext) {\n" " put(context.world, \"temp\", True)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_delete(context: StepContext) {\n" " delete(context.world, \"temp\")\n" " Ok(succeed())\n" " }\n" "\n" " fn step_is_absent(context: StepContext) {\n" " has(context.world, \"temp\")\n" " |> should\n" " |> be_equal(False)\n" " |> or_fail_with(\"expected temp to be absent\")\n" " }\n" "\n" " pub fn register(registry) {\n" " registry\n" " |> step(\"temp is stored\", step_store)\n" " |> step(\"temp is deleted\", step_delete)\n" " |> step(\"temp should be absent\", step_is_absent)\n" " }\n" "\n" " pub fn tests() {\n" " let steps = steps.new() |> register()\n" "\n" " feature(\"World: has + delete\", steps, [\n" " scenario(\"Deleting a key\", [\n" " given(\"temp is stored\"),\n" " when(\"temp is deleted\"),\n" " then(\"temp should be absent\"),\n" " ]),\n" " ])\n" " }\n" "\n" " pub fn main() {\n" " runner.new([tests()])\n" " |> runner.progress_reporter(progress.new())\n" " |> runner.results_reporters([bdd.new()])\n" " |> runner.exit_on_failure()\n" " |> runner.run()\n" " }\n" " ```\n" ). -spec has(world(), binary()) -> boolean(). has(World, Key) -> case dream_test_gherkin_world_ffi:lookup(erlang:element(3, World), Key) of {some, _} -> true; none -> false end. -file("src/dream_test/gherkin/world.gleam", 529). ?DOC( " Delete a key from the World.\n" "\n" " Removes a key-value pair. If the key doesn't exist, this is a no-op.\n" "\n" " ## Parameters\n" "\n" " - `world`: The World to modify\n" " - `key`: String key to delete\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/feature.{feature, given, scenario, then, when}\n" " import dream_test/gherkin/steps.{type StepContext, step}\n" " import dream_test/gherkin/world.{delete, has, put}\n" " import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n" " import dream_test/reporters/bdd\n" " import dream_test/reporters/progress\n" " import dream_test/runner\n" " import gleam/io\n" "\n" " fn step_store(context: StepContext) {\n" " put(context.world, \"temp\", True)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_delete(context: StepContext) {\n" " delete(context.world, \"temp\")\n" " Ok(succeed())\n" " }\n" "\n" " fn step_is_absent(context: StepContext) {\n" " has(context.world, \"temp\")\n" " |> should\n" " |> be_equal(False)\n" " |> or_fail_with(\"expected temp to be absent\")\n" " }\n" "\n" " pub fn register(registry) {\n" " registry\n" " |> step(\"temp is stored\", step_store)\n" " |> step(\"temp is deleted\", step_delete)\n" " |> step(\"temp should be absent\", step_is_absent)\n" " }\n" "\n" " pub fn tests() {\n" " let steps = steps.new() |> register()\n" "\n" " feature(\"World: has + delete\", steps, [\n" " scenario(\"Deleting a key\", [\n" " given(\"temp is stored\"),\n" " when(\"temp is deleted\"),\n" " then(\"temp should be absent\"),\n" " ]),\n" " ])\n" " }\n" "\n" " pub fn main() {\n" " runner.new([tests()])\n" " |> runner.progress_reporter(progress.new())\n" " |> runner.results_reporters([bdd.new()])\n" " |> runner.exit_on_failure()\n" " |> runner.run()\n" " }\n" " ```\n" ). -spec delete(world(), binary()) -> nil. delete(World, Key) -> dream_test_gherkin_world_ffi:delete_key(erlang:element(3, World), Key). -file("src/dream_test/gherkin/world.gleam", 558). ?DOC( " Get the scenario ID for this World.\n" "\n" " Useful for debugging and logging.\n" "\n" " ## Parameters\n" "\n" " - `world`: The World to query\n" "\n" " ## Returns\n" "\n" " The scenario ID string.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/world\n" "\n" " pub fn main() {\n" " let w = world.new_world(\"example_scenario\")\n" " let id = world.scenario_id(w)\n" " world.cleanup(w)\n" " id\n" " }\n" " ```\n" ). -spec scenario_id(world()) -> binary(). scenario_id(World) -> erlang:element(2, World).