-module(dream_test@gherkin@discover). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream_test/gherkin/discover.gleam"). -export([features/1, with_registry/2, to_suite/2, load/1, list_files/1]). -export_type([feature_discovery/0, load_result/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( " Discover and load Gherkin `.feature` files.\n" "\n" " Use this module to:\n" " - find `.feature` files via a glob pattern\n" " - parse them (`load`) for inspection, or\n" " - convert them into runnable `TestSuite`s (`to_suite`) when you provide a\n" " step registry (your Given/When/Then handlers).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " import dream_test/gherkin/discover\n" " import dream_test/gherkin/steps.{type StepContext, get_int, step}\n" " import dream_test/gherkin/world.{get_or, put}\n" " import dream_test/matchers.{be_equal, or_fail_with, should, succeed}\n" " import gleam/result\n" "\n" " fn step_server_running(context: StepContext) {\n" " put(context.world, \"server_running\", True)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_empty_cart(context: StepContext) {\n" " put(context.world, \"cart\", 0)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_add_items(context: StepContext) {\n" " let current = get_or(context.world, \"cart\", 0)\n" " let to_add = get_int(context.captures, 0) |> result.unwrap(0)\n" " put(context.world, \"cart\", current + to_add)\n" " Ok(succeed())\n" " }\n" "\n" " fn step_verify_count(context: StepContext) {\n" " let expected = get_int(context.captures, 0) |> result.unwrap(0)\n" " get_or(context.world, \"cart\", 0)\n" " |> should\n" " |> be_equal(expected)\n" " |> or_fail_with(\"Cart count mismatch\")\n" " }\n" "\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" " // Discover and load all .feature files\n" " discover.features(\"test/*.feature\")\n" " |> discover.with_registry(steps)\n" " |> discover.to_suite(\"cart_features\")\n" " }\n" " ```\n" ). -opaque feature_discovery() :: {feature_discovery, binary(), gleam@option:option(dream_test@gherkin@steps:step_registry()), list(dream_test@gherkin@types:feature()), list(binary())}. -type load_result() :: {load_result, list(dream_test@gherkin@types:feature()), list(binary())}. -file("src/dream_test/gherkin/discover.gleam", 128). ?DOC( " Start discovering features matching a glob pattern.\n" "\n" " Use Erlang’s `filelib:wildcard/1` semantics (see `wildcard/1` below).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " discover.features(\"test/*.feature\")\n" " ```\n" "\n" " ## Parameters\n" "\n" " - `pattern`: glob pattern used to find `.feature` files\n" "\n" " ## Returns\n" "\n" " A `FeatureDiscovery` builder you can pipe into `with_registry`, `load`,\n" " `list_files`, or `to_suite`.\n" ). -spec features(binary()) -> feature_discovery(). features(Pattern) -> {feature_discovery, Pattern, none, [], []}. -file("src/dream_test/gherkin/discover.gleam", 162). ?DOC( " Attach a step registry to the discovery.\n" "\n" " The step registry is the set of step definitions (Given/When/Then handlers)\n" " used to execute scenarios. It is required before calling `to_suite`.\n" "\n" " ## Example\n" "\n" " ```gleam\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" " // Discover and load all .feature files\n" " discover.features(\"test/*.feature\")\n" " |> discover.with_registry(steps)\n" " |> discover.to_suite(\"cart_features\")\n" " ```\n" "\n" " ## Parameters\n" "\n" " - `discovery`: a `FeatureDiscovery` created with `features`\n" " - `registry`: step definitions used to execute scenarios\n" "\n" " ## Returns\n" "\n" " The updated discovery.\n" ). -spec with_registry( feature_discovery(), dream_test@gherkin@steps:step_registry() ) -> feature_discovery(). with_registry(Discovery, Registry) -> {feature_discovery, erlang:element(2, Discovery), {some, Registry}, erlang:element(4, Discovery), erlang:element(5, Discovery)}. -file("src/dream_test/gherkin/discover.gleam", 312). -spec parse_feature_file(binary()) -> {ok, dream_test@gherkin@types:feature()} | {error, binary()}. parse_feature_file(Path) -> case dream_test@gherkin@parser:parse_file(Path) of {ok, Feature} -> {ok, Feature}; {error, E} -> {error, <<<>/binary, E/binary>>} end. -file("src/dream_test/gherkin/discover.gleam", 290). -spec load_all_features_loop( list(binary()), list(dream_test@gherkin@types:feature()), list(binary()) ) -> load_result(). load_all_features_loop(Files, Features_rev, Errors_rev) -> case Files of [] -> {load_result, lists:reverse(Features_rev), lists:reverse(Errors_rev)}; [Path | Rest] -> case parse_feature_file(Path) of {ok, Feature} -> load_all_features_loop( Rest, [Feature | Features_rev], Errors_rev ); {error, Error} -> load_all_features_loop( Rest, Features_rev, [Error | Errors_rev] ) end end. -file("src/dream_test/gherkin/discover.gleam", 286). -spec load_all_features(list(binary())) -> load_result(). load_all_features(Files) -> load_all_features_loop(Files, [], []). -file("src/dream_test/gherkin/discover.gleam", 333). -spec parse_error_assertion() -> dream_test@types:assertion_result(). parse_error_assertion() -> {assertion_failed, {assertion_failure, <<"parse"/utf8>>, <<"Failed to parse feature file (see test name for details)"/utf8>>, none}}. -file("src/dream_test/gherkin/discover.gleam", 329). -spec parse_error_test_run(nil) -> {ok, dream_test@types:assertion_result()} | {error, binary()}. parse_error_test_run(_) -> {ok, parse_error_assertion()}. -file("src/dream_test/gherkin/discover.gleam", 319). -spec error_to_node(binary()) -> dream_test@types:node_(nil). error_to_node(Error) -> {test, <<"Parse Error: "/utf8, Error/binary>>, [<<"parse-error"/utf8>>], unit, fun parse_error_test_run/1, none}. -file("src/dream_test/gherkin/discover.gleam", 364). -spec errors_to_nodes(list(binary()), list(dream_test@types:node_(nil))) -> list(dream_test@types:node_(nil)). errors_to_nodes(Errors, Acc_rev) -> case Errors of [] -> lists:reverse(Acc_rev); [Error | Rest] -> errors_to_nodes(Rest, [error_to_node(Error) | Acc_rev]) end. -file("src/dream_test/gherkin/discover.gleam", 374). -spec root_to_group(dream_test@types:root(nil)) -> dream_test@types:node_(nil). root_to_group(Suite) -> {root, _, Tree} = Suite, Tree. -file("src/dream_test/gherkin/discover.gleam", 355). -spec feature_to_group( dream_test@gherkin@types:feature(), dream_test@gherkin@steps:step_registry() ) -> dream_test@types:node_(nil). feature_to_group(Feature, Registry) -> Config = {feature_config, Feature, Registry}, Feature_suite = dream_test@gherkin@feature:to_test_suite(Config), root_to_group(Feature_suite). -file("src/dream_test/gherkin/discover.gleam", 341). -spec features_to_groups( list(dream_test@gherkin@types:feature()), dream_test@gherkin@steps:step_registry(), list(dream_test@types:node_(nil)) ) -> list(dream_test@types:node_(nil)). features_to_groups(Features, Registry, Acc_rev) -> case Features of [] -> lists:reverse(Acc_rev); [Feature | Rest] -> Group = feature_to_group(Feature, Registry), features_to_groups(Rest, Registry, [Group | Acc_rev]) end. -file("src/dream_test/gherkin/discover.gleam", 282). -spec discover_files(binary()) -> list(binary()). discover_files(Pattern) -> dream_test_gherkin_discover_ffi:wildcard(Pattern). -file("src/dream_test/gherkin/discover.gleam", 199). ?DOC( " Build a TestSuite from discovered features.\n" "\n" " Panics if `with_registry()` was not called.\n" "\n" " Parse errors are converted into failing unit tests tagged with\n" " `\"parse-error\"`.\n" "\n" " ## What does this produce?\n" "\n" " The returned suite contains one test per scenario. Each test runs the\n" " scenario’s steps using the provided step registry.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // Discover and load all .feature files\n" " discover.features(\"test/*.feature\")\n" " |> discover.with_registry(steps)\n" " |> discover.to_suite(\"cart_features\")\n" " ```\n" "\n" " ## Parameters\n" "\n" " - `discovery`: a `FeatureDiscovery` with a registry attached via `with_registry`\n" " - `suite_name`: name to show for the top-level suite/group in reports\n" "\n" " ## Returns\n" "\n" " A `TestSuite(Nil)` containing one test per discovered scenario, plus failing\n" " tests for any parse errors (tagged `\"parse-error\"`).\n" ). -spec to_suite(feature_discovery(), binary()) -> dream_test@types:root(nil). to_suite(Discovery, Suite_name) -> Registry = case erlang:element(3, Discovery) of {some, R} -> R; none -> erlang:error(#{gleam_error => panic, message => <<"FeatureDiscovery requires a registry. Call with_registry() first."/utf8>>, file => <>, module => <<"dream_test/gherkin/discover"/utf8>>, function => <<"to_suite"/utf8>>, line => 206}) end, Files = discover_files(erlang:element(2, Discovery)), Load_result = load_all_features(Files), Children = features_to_groups(erlang:element(2, Load_result), Registry, []), Error_nodes = errors_to_nodes(erlang:element(3, Load_result), []), {root, nil, {group, Suite_name, [], lists:append(Children, Error_nodes)}}. -file("src/dream_test/gherkin/discover.gleam", 248). ?DOC( " Load features and return detailed results.\n" "\n" " This does **not** require a step registry because it only discovers files\n" " and parses Gherkin syntax. Step definitions are only needed when you want to\n" " execute scenarios (`to_suite`).\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let result = discover.features(\"test/*.feature\") |> discover.load()\n" "\n" " result.features\n" " |> should\n" " |> have_length(1)\n" " |> or_fail_with(\"expected one parsed feature\")\n" " ```\n" "\n" " ## Parameters\n" "\n" " - `discovery`: a `FeatureDiscovery` created with `features`\n" "\n" " ## Returns\n" "\n" " A `LoadResult` containing parsed features and any parse errors.\n" ). -spec load(feature_discovery()) -> load_result(). load(Discovery) -> Files = discover_files(erlang:element(2, Discovery)), load_all_features(Files). -file("src/dream_test/gherkin/discover.gleam", 274). ?DOC( " Get the list of files matching the discovery pattern.\n" "\n" " This is a pure discovery step; files are not parsed.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " discover.features(\"test/*.feature\")\n" " |> discover.list_files()\n" " |> should\n" " |> contain(\"test/cart.feature\")\n" " |> or_fail_with(\"expected list_files to include test/cart.feature\")\n" " ```\n" "\n" " ## Parameters\n" "\n" " - `discovery`: a `FeatureDiscovery` created with `features`\n" "\n" " ## Returns\n" "\n" " A list of file paths matching the glob pattern.\n" ). -spec list_files(feature_discovery()) -> list(binary()). list_files(Discovery) -> discover_files(erlang:element(2, Discovery)).