Current section
Files
Jump to
Current section
Files
src/dream_test@gherkin@feature.erl
-module(dream_test@gherkin@feature).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/gherkin/feature.gleam").
-export([to_test_suite/1, feature/3, scenario/2, with_tags/2, given/1, 'when'/1, then/1, 'and'/1, but/1, background/1, feature_with_background/4]).
-export_type([feature_config/0, inline_scenario/0, inline_step/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(
" Convert Gherkin features into runnable `TestSuite`s.\n"
"\n"
" This module does two related jobs:\n"
" - **Execute parsed `.feature` files**: turn a parsed `gherkin/types.Feature`\n"
" into a `TestSuite` using a step registry (your Given/When/Then handlers).\n"
" - **Provide an inline DSL**: build features directly in Gleam when you don’t\n"
" want to keep `.feature` files on disk.\n"
"\n"
" ## Example (file-based)\n"
"\n"
" ```gleam\n"
" pub fn tests() {\n"
" // Define step handlers\n"
" let steps =\n"
" steps.new()\n"
" |> step(\"the server is running\", step_server_running)\n"
" |> step(\"the cart is empty\", step_empty_cart)\n"
" |> step(\"I add {int} items\", step_add_items)\n"
" |> step(\"the cart should have {int} items\", step_verify_count)\n"
"\n"
" // Parse the .feature file\n"
" let assert Ok(feature) = parser.parse_file(\"test/cart.feature\")\n"
"\n"
" // Convert to TestSuite and run\n"
" let config = FeatureConfig(feature: feature, step_registry: steps)\n"
" to_test_suite(config)\n"
" }\n"
" ```\n"
"\n"
" ## Example (inline DSL)\n"
"\n"
" ```gleam\n"
" pub fn tests() {\n"
" let steps =\n"
" steps.new()\n"
" |> step(\"the server is running\", step_server_running)\n"
" |> step(\"the cart is empty\", step_empty_cart)\n"
" |> step(\"I add {int} items\", step_add_items)\n"
" |> step(\"the cart should have {int} items\", step_verify_count)\n"
"\n"
" let bg = background([given(\"the server is running\")])\n"
"\n"
" feature_with_background(\"Shopping Cart\", steps, bg, [\n"
" scenario(\"Adding items\", [\n"
" given(\"the cart is empty\"),\n"
" when(\"I add 3 items\"),\n"
" then(\"the cart should have 3 items\"),\n"
" ])\n"
" |> with_tags([\"smoke\"]),\n"
" scenario(\"Adding more items\", [\n"
" given(\"the cart is empty\"),\n"
" when(\"I add 2 items\"),\n"
" and(\"I add 3 items\"),\n"
" then(\"the cart should have 5 items\"),\n"
" ]),\n"
" ])\n"
" }\n"
" ```\n"
).
-type feature_config() :: {feature_config,
dream_test@gherkin@types:feature(),
dream_test@gherkin@steps:step_registry()}.
-type inline_scenario() :: {inline_scenario,
binary(),
list(inline_step()),
list(binary())}.
-type inline_step() :: {inline_step, binary(), binary()}.
-file("src/dream_test/gherkin/feature.gleam", 180).
-spec reverse_append(list(GOM), list(GOM)) -> list(GOM).
reverse_append(Items, Acc) ->
case Items of
[] ->
Acc;
[Item | Rest] ->
reverse_append(Rest, [Item | Acc])
end.
-file("src/dream_test/gherkin/feature.gleam", 485).
-spec extract_doc_string_from_step(dream_test@gherkin@types:step()) -> gleam@option:option(binary()).
extract_doc_string_from_step(Step) ->
case erlang:element(4, Step) of
{some, {doc_string, Content, _}} ->
{some, Content};
_ ->
none
end.
-file("src/dream_test/gherkin/feature.gleam", 476).
-spec extract_table_from_step(dream_test@gherkin@types:step()) -> gleam@option:option(list(list(binary()))).
extract_table_from_step(Step) ->
case erlang:element(4, Step) of
{some, {data_table, Rows}} ->
{some, Rows};
_ ->
none
end.
-file("src/dream_test/gherkin/feature.gleam", 460).
-spec build_step_context(
dream_test@gherkin@step_trie:step_match(fun((dream_test@gherkin@steps:step_context()) -> {ok,
dream_test@types:assertion_result()} |
{error, binary()})),
dream_test@gherkin@types:step(),
dream_test@gherkin@world:world()
) -> dream_test@gherkin@steps:step_context().
build_step_context(Match, Step, The_world) ->
Table = extract_table_from_step(Step),
Doc_string = extract_doc_string_from_step(Step),
{step_context, erlang:element(3, Match), Table, Doc_string, The_world}.
-file("src/dream_test/gherkin/feature.gleam", 426).
-spec execute_step(
dream_test@gherkin@types:step(),
dream_test@gherkin@steps:step_registry(),
dream_test@gherkin@world:world(),
dream_test@gherkin@types:step_keyword()
) -> dream_test@types:assertion_result().
execute_step(Step, Registry, The_world, Previous_keyword) ->
Effective_keyword = dream_test@gherkin@types:resolve_keyword(
erlang:element(2, Step),
Previous_keyword
),
case dream_test@gherkin@steps:find_step(
Registry,
Effective_keyword,
erlang:element(3, Step)
) of
{ok, Match} ->
Context = build_step_context(Match, Step, The_world),
case (erlang:element(2, Match))(Context) of
{ok, Result} ->
Result;
{error, Message} ->
{assertion_failed,
{assertion_failure, <<"step"/utf8>>, Message, none}}
end;
{error, Msg} ->
{assertion_failed, {assertion_failure, <<"step"/utf8>>, Msg, none}}
end.
-file("src/dream_test/gherkin/feature.gleam", 405).
-spec execute_steps(
list(dream_test@gherkin@types:step()),
dream_test@gherkin@steps:step_registry(),
dream_test@gherkin@world:world(),
dream_test@gherkin@types:step_keyword()
) -> dream_test@types:assertion_result().
execute_steps(Steps, Registry, The_world, Previous_keyword) ->
case Steps of
[] ->
assertion_ok;
[Step | Rest] ->
case execute_step(Step, Registry, The_world, Previous_keyword) of
assertion_ok ->
Effective_keyword = dream_test@gherkin@types:resolve_keyword(
erlang:element(2, Step),
Previous_keyword
),
execute_steps(Rest, Registry, The_world, Effective_keyword);
Failure ->
Failure
end
end.
-file("src/dream_test/gherkin/feature.gleam", 388).
-spec execute_scenario(
binary(),
list(dream_test@gherkin@types:step()),
dream_test@gherkin@steps:step_registry()
) -> dream_test@types:assertion_result().
execute_scenario(Scenario_id, Steps, Registry) ->
The_world = dream_test@gherkin@world:new_world(Scenario_id),
Result = execute_steps(Steps, Registry, The_world, given),
dream_test@gherkin@world:cleanup(The_world),
Result.
-file("src/dream_test/gherkin/feature.gleam", 234).
-spec build_full_name(binary(), binary(), gleam@option:option(binary())) -> list(binary()).
build_full_name(Feature_name, Scenario_name, Example_suffix) ->
case Example_suffix of
none ->
[Feature_name, Scenario_name];
{some, Suffix} ->
[Feature_name,
<<<<Scenario_name/binary, " "/utf8>>/binary, Suffix/binary>>]
end.
-file("src/dream_test/gherkin/feature.gleam", 204).
-spec build_scenario_test_node(
dream_test@gherkin@types:feature(),
binary(),
list(binary()),
list(dream_test@gherkin@types:step()),
feature_config(),
gleam@option:option(binary())
) -> dream_test@types:node_(nil).
build_scenario_test_node(
Feature,
Scenario_name,
Scenario_tags,
Steps,
Config,
Example_suffix
) ->
Full_name = build_full_name(
erlang:element(2, Feature),
Scenario_name,
Example_suffix
),
Scenario_id = gleam@string:join(Full_name, <<"::"/utf8>>),
All_tags = lists:append(erlang:element(5, Feature), Scenario_tags),
All_steps = case erlang:element(6, Feature) of
{some, {background, Background_steps}} ->
lists:append(Background_steps, Steps);
none ->
Steps
end,
{test,
Scenario_name,
All_tags,
{gherkin_scenario, Scenario_id},
fun(_) ->
{ok,
execute_scenario(
Scenario_id,
All_steps,
erlang:element(3, Config)
)}
end,
none,
erlang:element(3, Feature)}.
-file("src/dream_test/gherkin/feature.gleam", 370).
-spec substitute_placeholders_loop(list({binary(), binary()}), binary()) -> binary().
substitute_placeholders_loop(Pairs, Acc) ->
case Pairs of
[] ->
Acc;
[{Header, Value} | Rest] ->
Placeholder = <<<<"<"/utf8, Header/binary>>/binary, ">"/utf8>>,
Next = gleam@string:replace(Acc, Placeholder, Value),
substitute_placeholders_loop(Rest, Next)
end.
-file("src/dream_test/gherkin/feature.gleam", 363).
-spec substitute_placeholders(binary(), gleam@dict:dict(binary(), binary())) -> binary().
substitute_placeholders(Text, Substitutions) ->
substitute_placeholders_loop(maps:to_list(Substitutions), Text).
-file("src/dream_test/gherkin/feature.gleam", 351).
-spec substitute_step(
dream_test@gherkin@types:step(),
gleam@dict:dict(binary(), binary())
) -> dream_test@gherkin@types:step().
substitute_step(Step, Substitutions) ->
New_text = substitute_placeholders(erlang:element(3, Step), Substitutions),
{step, erlang:element(2, Step), New_text, erlang:element(4, Step)}.
-file("src/dream_test/gherkin/feature.gleam", 336).
-spec substitute_steps_loop(
list(dream_test@gherkin@types:step()),
gleam@dict:dict(binary(), binary()),
list(dream_test@gherkin@types:step())
) -> list(dream_test@gherkin@types:step()).
substitute_steps_loop(Steps, Substitutions, Acc_rev) ->
case Steps of
[] ->
lists:reverse(Acc_rev);
[Step | Rest] ->
substitute_steps_loop(
Rest,
Substitutions,
[substitute_step(Step, Substitutions) | Acc_rev]
)
end.
-file("src/dream_test/gherkin/feature.gleam", 329).
-spec substitute_steps(
list(dream_test@gherkin@types:step()),
gleam@dict:dict(binary(), binary())
) -> list(dream_test@gherkin@types:step()).
substitute_steps(Steps, Substitutions) ->
substitute_steps_loop(Steps, Substitutions, []).
-file("src/dream_test/gherkin/feature.gleam", 318).
-spec build_substitution_map_loop(
list({binary(), binary()}),
gleam@dict:dict(binary(), binary())
) -> gleam@dict:dict(binary(), binary()).
build_substitution_map_loop(Pairs, Acc) ->
case Pairs of
[] ->
Acc;
[{Header, Value} | Rest] ->
build_substitution_map_loop(
Rest,
gleam@dict:insert(Acc, Header, Value)
)
end.
-file("src/dream_test/gherkin/feature.gleam", 311).
-spec build_substitution_map(list(binary()), list(binary())) -> gleam@dict:dict(binary(), binary()).
build_substitution_map(Headers, Values) ->
build_substitution_map_loop(gleam@list:zip(Headers, Values), maps:new()).
-file("src/dream_test/gherkin/feature.gleam", 268).
-spec expand_scenario_outline_rows_loop(
dream_test@gherkin@types:feature(),
binary(),
list(binary()),
list(dream_test@gherkin@types:step()),
feature_config(),
list(binary()),
list(list(binary())),
integer(),
list(dream_test@types:node_(nil))
) -> list(dream_test@types:node_(nil)).
expand_scenario_outline_rows_loop(
Feature,
Name,
Tags,
Steps,
Config,
Headers,
Rows,
Index,
Acc_rev
) ->
case Rows of
[] ->
lists:reverse(Acc_rev);
[Row | Rest] ->
Substitutions = build_substitution_map(Headers, Row),
Expanded_steps = substitute_steps(Steps, Substitutions),
Suffix = <<<<"(Example "/utf8,
(erlang:integer_to_binary(Index + 1))/binary>>/binary,
")"/utf8>>,
Node = build_scenario_test_node(
Feature,
Name,
Tags,
Expanded_steps,
Config,
{some, Suffix}
),
expand_scenario_outline_rows_loop(
Feature,
Name,
Tags,
Steps,
Config,
Headers,
Rest,
Index + 1,
[Node | Acc_rev]
)
end.
-file("src/dream_test/gherkin/feature.gleam", 245).
-spec expand_scenario_outline(
dream_test@gherkin@types:feature(),
binary(),
list(binary()),
list(dream_test@gherkin@types:step()),
dream_test@gherkin@types:examples_table(),
feature_config()
) -> list(dream_test@types:node_(nil)).
expand_scenario_outline(Feature, Name, Tags, Steps, Examples, Config) ->
Headers = erlang:element(2, Examples),
expand_scenario_outline_rows_loop(
Feature,
Name,
Tags,
Steps,
Config,
Headers,
erlang:element(3, Examples),
0,
[]
).
-file("src/dream_test/gherkin/feature.gleam", 187).
-spec scenario_to_suite_items(
dream_test@gherkin@types:feature(),
dream_test@gherkin@types:scenario(),
feature_config()
) -> list(dream_test@types:node_(nil)).
scenario_to_suite_items(Feature, Scenario, Config) ->
case Scenario of
{scenario, Name, Tags, Steps} ->
Test_node = build_scenario_test_node(
Feature,
Name,
Tags,
Steps,
Config,
none
),
[Test_node];
{scenario_outline, Name@1, Tags@1, Steps@1, Examples} ->
expand_scenario_outline(
Feature,
Name@1,
Tags@1,
Steps@1,
Examples,
Config
)
end.
-file("src/dream_test/gherkin/feature.gleam", 160).
-spec build_suite_items_loop(
dream_test@gherkin@types:feature(),
list(dream_test@gherkin@types:scenario()),
feature_config(),
list(dream_test@types:node_(nil))
) -> list(dream_test@types:node_(nil)).
build_suite_items_loop(Feature, Scenarios, Config, Acc_rev) ->
case Scenarios of
[] ->
lists:reverse(Acc_rev);
[Scenario | Rest] ->
Items = scenario_to_suite_items(Feature, Scenario, Config),
build_suite_items_loop(
Feature,
Rest,
Config,
reverse_append(Items, Acc_rev)
)
end.
-file("src/dream_test/gherkin/feature.gleam", 153).
-spec build_suite_items(dream_test@gherkin@types:feature(), feature_config()) -> list(dream_test@types:node_(nil)).
build_suite_items(Feature, Config) ->
build_suite_items_loop(Feature, erlang:element(7, Feature), Config, []).
-file("src/dream_test/gherkin/feature.gleam", 143).
?DOC(
" Convert a Feature to a TestSuite.\n"
"\n"
" Creates a TestSuite with one test per scenario. Background steps are\n"
" prepended to each scenario. ScenarioOutlines are expanded based on\n"
" their examples table.\n"
"\n"
" ## Parameters\n"
"\n"
" - `config`: FeatureConfig with feature and step registry\n"
"\n"
" ## Returns\n"
"\n"
" A TestSuite that can be run with `runner.new([suite]) |> runner.run()`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let config = FeatureConfig(feature: feature, step_registry: steps)\n"
" to_test_suite(config)\n"
" ```\n"
).
-spec to_test_suite(feature_config()) -> dream_test@types:root(nil).
to_test_suite(Config) ->
Feature = erlang:element(2, Config),
Children = build_suite_items(Feature, Config),
{root,
nil,
{group,
erlang:element(2, Feature),
erlang:element(5, Feature),
Children}}.
-file("src/dream_test/gherkin/feature.gleam", 673).
-spec parse_keyword(binary()) -> dream_test@gherkin@types:step_keyword().
parse_keyword(Keyword_str) ->
case Keyword_str of
<<"Given"/utf8>> ->
given;
<<"When"/utf8>> ->
'when';
<<"Then"/utf8>> ->
then;
<<"And"/utf8>> ->
'and';
<<"But"/utf8>> ->
but;
_ ->
given
end.
-file("src/dream_test/gherkin/feature.gleam", 668).
-spec inline_to_parsed_step(inline_step()) -> dream_test@gherkin@types:step().
inline_to_parsed_step(Inline) ->
Keyword = parse_keyword(erlang:element(2, Inline)),
{step, Keyword, erlang:element(3, Inline), none}.
-file("src/dream_test/gherkin/feature.gleam", 659).
-spec inline_to_parsed_scenario(inline_scenario()) -> dream_test@gherkin@types:scenario().
inline_to_parsed_scenario(Inline) ->
Parsed_steps = gleam@list:map(
erlang:element(3, Inline),
fun inline_to_parsed_step/1
),
{scenario,
erlang:element(2, Inline),
erlang:element(4, Inline),
Parsed_steps}.
-file("src/dream_test/gherkin/feature.gleam", 529).
?DOC(
" Define a feature inline in Gleam code.\n"
"\n"
" Creates a TestSuite from inline scenario definitions without needing\n"
" a `.feature` file.\n"
"\n"
" ## Parameters\n"
"\n"
" - `name`: Feature name\n"
" - `registry`: Step registry with step definitions\n"
" - `scenarios`: List of inline scenarios\n"
"\n"
" ## Returns\n"
"\n"
" A TestSuite that can be run with `runner.new([suite]) |> runner.run()`.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let steps =\n"
" steps.new()\n"
" |> step(\"I have {int} items in my cart\", step_have_items)\n"
" |> step(\"I add {int} more items\", step_add_items)\n"
" |> step(\"I should have {int} items total\", step_should_have)\n"
"\n"
" feature(\"Shopping Cart\", steps, [\n"
" scenario(\"Adding items to cart\", [\n"
" given(\"I have 3 items in my cart\"),\n"
" when(\"I add 2 more items\"),\n"
" then(\"I should have 5 items total\"),\n"
" but(\"I should have 5 items total\"),\n"
" ]),\n"
" ])\n"
" ```\n"
).
-spec feature(
binary(),
dream_test@gherkin@steps:step_registry(),
list(inline_scenario())
) -> dream_test@types:root(nil).
feature(Name, Registry, Scenarios) ->
Parsed_scenarios = gleam@list:map(
Scenarios,
fun inline_to_parsed_scenario/1
),
Parsed_feature = {feature, Name, none, none, [], none, Parsed_scenarios},
Config = {feature_config, Parsed_feature, Registry},
to_test_suite(Config).
-file("src/dream_test/gherkin/feature.gleam", 567).
?DOC(
" Define an inline scenario.\n"
"\n"
" ## Parameters\n"
"\n"
" - `name`: Scenario name\n"
" - `steps`: List of inline steps\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" scenario(\"Adding items to cart\", [\n"
" given(\"I have 3 items in my cart\"),\n"
" when(\"I add 2 more items\"),\n"
" then(\"I should have 5 items total\"),\n"
" but(\"I should have 5 items total\"),\n"
" ]),\n"
" ```\n"
).
-spec scenario(binary(), list(inline_step())) -> inline_scenario().
scenario(Name, Inline_steps) ->
{inline_scenario, Name, Inline_steps, []}.
-file("src/dream_test/gherkin/feature.gleam", 592).
?DOC(
" Add tags to a Gherkin scenario for filtering.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" scenario(\"Adding items\", [\n"
" given(\"the cart is empty\"),\n"
" when(\"I add 3 items\"),\n"
" then(\"the cart should have 3 items\"),\n"
" ])\n"
" |> with_tags([\"smoke\"]),\n"
" ```\n"
"\n"
" ## Note\n"
"\n"
" This function is for Gherkin scenarios. For unit tests (`it`), use\n"
" `dream_test/unit.with_tags` instead.\n"
).
-spec with_tags(inline_scenario(), list(binary())) -> inline_scenario().
with_tags(Inline_scenario, Tags) ->
{inline_scenario,
erlang:element(2, Inline_scenario),
erlang:element(3, Inline_scenario),
Tags}.
-file("src/dream_test/gherkin/feature.gleam", 607).
?DOC(
" Create a Given step for inline DSL.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" given(\"the cart is empty\"),\n"
" ```\n"
).
-spec given(binary()) -> inline_step().
given(Text) ->
{inline_step, <<"Given"/utf8>>, Text}.
-file("src/dream_test/gherkin/feature.gleam", 619).
?DOC(
" Create a When step for inline DSL.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" when(\"I add 3 items\"),\n"
" ```\n"
).
-spec 'when'(binary()) -> inline_step().
'when'(Text) ->
{inline_step, <<"When"/utf8>>, Text}.
-file("src/dream_test/gherkin/feature.gleam", 631).
?DOC(
" Create a Then step for inline DSL.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" then(\"the cart should have 3 items\"),\n"
" ```\n"
).
-spec then(binary()) -> inline_step().
then(Text) ->
{inline_step, <<"Then"/utf8>>, Text}.
-file("src/dream_test/gherkin/feature.gleam", 643).
?DOC(
" Create an And step for inline DSL.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" and(\"I add 3 items\"),\n"
" ```\n"
).
-spec 'and'(binary()) -> inline_step().
'and'(Text) ->
{inline_step, <<"And"/utf8>>, Text}.
-file("src/dream_test/gherkin/feature.gleam", 655).
?DOC(
" Create a But step for inline DSL.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" but(\"I should have 5 items total\"),\n"
" ```\n"
).
-spec but(binary()) -> inline_step().
but(Text) ->
{inline_step, <<"But"/utf8>>, Text}.
-file("src/dream_test/gherkin/feature.gleam", 698).
?DOC(
" Define a background for inline features.\n"
"\n"
" Background steps run before each scenario.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let bg = background([given(\"the server is running\")])\n"
" ```\n"
).
-spec background(list(inline_step())) -> list(dream_test@gherkin@types:step()).
background(Inline_steps) ->
gleam@list:map(Inline_steps, fun inline_to_parsed_step/1).
-file("src/dream_test/gherkin/feature.gleam", 733).
?DOC(
" Define a feature with a background.\n"
"\n"
" ## Parameters\n"
"\n"
" - `name`: Feature name\n"
" - `registry`: Step registry\n"
" - `background_steps`: Steps to run before each scenario\n"
" - `scenarios`: List of inline scenarios\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let bg = background([given(\"the server is running\")])\n"
"\n"
" feature_with_background(\"Shopping Cart\", steps, bg, [\n"
" scenario(\"Adding items\", [\n"
" given(\"the cart is empty\"),\n"
" when(\"I add 3 items\"),\n"
" then(\"the cart should have 3 items\"),\n"
" ])\n"
" |> with_tags([\"smoke\"]),\n"
" scenario(\"Adding more items\", [\n"
" given(\"the cart is empty\"),\n"
" when(\"I add 2 items\"),\n"
" and(\"I add 3 items\"),\n"
" then(\"the cart should have 5 items\"),\n"
" ]),\n"
" ])\n"
" ```\n"
).
-spec feature_with_background(
binary(),
dream_test@gherkin@steps:step_registry(),
list(dream_test@gherkin@types:step()),
list(inline_scenario())
) -> dream_test@types:root(nil).
feature_with_background(Name, Registry, Background_steps, Scenarios) ->
Parsed_scenarios = gleam@list:map(
Scenarios,
fun inline_to_parsed_scenario/1
),
Parsed_feature = {feature,
Name,
none,
none,
[],
{some, {background, Background_steps}},
Parsed_scenarios},
Config = {feature_config, Parsed_feature, Registry},
to_test_suite(Config).