-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, <<<>/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).