Current section
Files
Jump to
Current section
Files
src/dream_test@gherkin@discover.erl
-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", 129).
?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", 163).
?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", 331).
-spec source_from_parse_error(binary()) -> gleam@option:option(binary()).
source_from_parse_error(Error) ->
case gleam@string:split_once(Error, <<": "/utf8>>) of
{ok, {Path, _}} ->
{some, Path};
{error, _} ->
none
end.
-file("src/dream_test/gherkin/discover.gleam", 342).
-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", 338).
-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", 320).
-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,
source_from_parse_error(Error)}.
-file("src/dream_test/gherkin/discover.gleam", 373).
-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", 383).
-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", 364).
-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", 350).
-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", 313).
-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, <<<<Path/binary, ": "/utf8>>/binary, E/binary>>}
end.
-file("src/dream_test/gherkin/discover.gleam", 291).
-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", 287).
-spec load_all_features(list(binary())) -> load_result().
load_all_features(Files) ->
load_all_features_loop(Files, [], []).
-file("src/dream_test/gherkin/discover.gleam", 283).
-spec discover_files(binary()) -> list(binary()).
discover_files(Pattern) ->
dream_test_gherkin_discover_ffi:wildcard(Pattern).
-file("src/dream_test/gherkin/discover.gleam", 200).
?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 => <<?FILEPATH/utf8>>,
module => <<"dream_test/gherkin/discover"/utf8>>,
function => <<"to_suite"/utf8>>,
line => 207})
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", 249).
?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", 275).
?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)).