Current section

Files

Jump to
dream_test src dream_test@reporter@bdd.erl
Raw

src/dream_test@reporter@bdd.erl

-module(dream_test@reporter@bdd).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/reporter/bdd.gleam").
-export([format/1, report/2]).
-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(
" BDD-style test reporter for dream_test.\n"
"\n"
" This reporter formats test results in a hierarchical, spec-like format\n"
" that mirrors your `describe`/`it` structure. It's inspired by RSpec, Jest,\n"
" and Mocha.\n"
"\n"
" ## Example Output\n"
"\n"
" ```text\n"
" Calculator\n"
" add\n"
" ✓ adds positive numbers (1ms)\n"
" ✓ handles zero\n"
" subtract\n"
" ✓ subtracts positive numbers\n"
" ✗ handles negative results (2ms)\n"
" equal\n"
" Message: Should handle negative subtraction\n"
" Expected: -5\n"
" Actual: 5\n"
"\n"
" Summary: 4 run, 1 failed, 3 passed in 3ms\n"
" ```\n"
"\n"
" ## Usage\n"
"\n"
" ```gleam\n"
" import dream_test/unit.{describe, it, to_test_cases}\n"
" import dream_test/runner.{run_all}\n"
" import dream_test/reporter/bdd.{report}\n"
" import gleam/io\n"
"\n"
" pub fn main() {\n"
" tests()\n"
" |> to_test_cases(\"my_test\")\n"
" |> run_all()\n"
" |> report(io.print)\n"
" }\n"
" ```\n"
"\n"
" ## Status Markers\n"
"\n"
" | Status | Marker | Meaning |\n"
" |-------------|--------|--------------------------------|\n"
" | Passed | ✓ | All assertions succeeded |\n"
" | Failed | ✗ | One or more assertions failed |\n"
" | Skipped | - | Test was skipped |\n"
" | Pending | ~ | Test is a placeholder |\n"
" | TimedOut | ! | Test exceeded timeout |\n"
" | SetupFailed | âš  | A setup hook failed |\n"
).
-file("src/dream_test/reporter/bdd.gleam", 96).
-spec partition_by_kind(list(dream_test@types:test_result())) -> {list(dream_test@types:test_result()),
list(dream_test@types:test_result())}.
partition_by_kind(Results) ->
gleam@list:partition(
Results,
fun dream_test@reporter@gherkin:is_gherkin_result/1
).
-file("src/dream_test/reporter/bdd.gleam", 118).
-spec compare_string_lists(list(binary()), list(binary())) -> gleam@order:order().
compare_string_lists(A, B) ->
case {A, B} of
{[], []} ->
eq;
{[], _} ->
lt;
{_, []} ->
gt;
{[Head_a | Rest_a], [Head_b | Rest_b]} ->
case gleam@string:compare(Head_a, Head_b) of
eq ->
compare_string_lists(Rest_a, Rest_b);
Other ->
Other
end
end.
-file("src/dream_test/reporter/bdd.gleam", 114).
-spec compare_by_full_name(
dream_test@types:test_result(),
dream_test@types:test_result()
) -> gleam@order:order().
compare_by_full_name(A, B) ->
compare_string_lists(erlang:element(3, A), erlang:element(3, B)).
-file("src/dream_test/reporter/bdd.gleam", 149).
-spec remove_gherkin_summary(list(binary()), list(binary())) -> list(binary()).
remove_gherkin_summary(Lines, Accumulated) ->
case Lines of
[] ->
lists:reverse(Accumulated);
[Line] ->
case gleam_stdlib:string_starts_with(Line, <<"Summary:"/utf8>>)
orelse (Line =:= <<""/utf8>>) of
true ->
lists:reverse(Accumulated);
false ->
lists:reverse([Line | Accumulated])
end;
[Line@1 | Rest] ->
case gleam_stdlib:string_starts_with(Line@1, <<"Summary:"/utf8>>) of
true ->
remove_gherkin_summary(Rest, Accumulated);
false ->
remove_gherkin_summary(Rest, [Line@1 | Accumulated])
end
end.
-file("src/dream_test/reporter/bdd.gleam", 142).
-spec remove_summary_line(binary()) -> binary().
remove_summary_line(Text) ->
Lines = gleam@string:split(Text, <<"\n"/utf8>>),
Without_summary = remove_gherkin_summary(Lines, []),
gleam@string:join(Without_summary, <<"\n"/utf8>>).
-file("src/dream_test/reporter/bdd.gleam", 131).
-spec format_gherkin_results(list(dream_test@types:test_result())) -> binary().
format_gherkin_results(Results) ->
case Results of
[] ->
<<""/utf8>>;
_ ->
Formatted = dream_test@reporter@gherkin:format(Results),
remove_summary_line(Formatted)
end.
-file("src/dream_test/reporter/bdd.gleam", 271).
-spec extract_describe_segments(list(binary())) -> list(binary()).
extract_describe_segments(Full_name) ->
case lists:reverse(Full_name) of
[] ->
[];
[_] ->
[];
[_ | Rest] ->
lists:reverse(Rest)
end.
-file("src/dream_test/reporter/bdd.gleam", 306).
-spec format_duration(integer()) -> binary().
format_duration(Duration_ms) ->
case Duration_ms of
Ms when Ms =< 0 ->
<<""/utf8>>;
Ms@1 ->
<<<<" ("/utf8, (dream_test@timing:format_duration_ms(Ms@1))/binary>>/binary,
")"/utf8>>
end.
-file("src/dream_test/reporter/bdd.gleam", 314).
-spec calculate_test_depth(list(binary())) -> integer().
calculate_test_depth(Full_name) ->
case Full_name of
[] ->
0;
[_] ->
0;
_ ->
erlang:length(Full_name) - 1
end.
-file("src/dream_test/reporter/bdd.gleam", 326).
-spec build_indent_recursive(integer(), binary()) -> binary().
build_indent_recursive(Level, Accumulated) ->
case Level of
0 ->
Accumulated;
N ->
build_indent_recursive(
N - 1,
erlang:list_to_binary([Accumulated, <<" "/utf8>>])
)
end.
-file("src/dream_test/reporter/bdd.gleam", 322).
-spec build_indent(integer()) -> binary().
build_indent(Level) ->
build_indent_recursive(Level, <<""/utf8>>).
-file("src/dream_test/reporter/bdd.gleam", 279).
-spec format_header_segments(list(binary()), integer(), binary()) -> binary().
format_header_segments(Segments, Depth, Accumulated) ->
case Segments of
[] ->
Accumulated;
[Segment | Rest] ->
Indent = build_indent(Depth),
Header = erlang:list_to_binary([Indent, Segment, <<"\n"/utf8>>]),
Updated = erlang:list_to_binary([Accumulated, Header]),
format_header_segments(Rest, Depth + 1, Updated)
end.
-file("src/dream_test/reporter/bdd.gleam", 333).
-spec extract_test_name(list(binary())) -> binary().
extract_test_name(Full_name) ->
case lists:reverse(Full_name) of
[Last | _] ->
Last;
[] ->
<<""/utf8>>
end.
-file("src/dream_test/reporter/bdd.gleam", 340).
-spec status_marker(dream_test@types:status()) -> binary().
status_marker(Status) ->
case Status of
passed ->
<<"✓"/utf8>>;
failed ->
<<"✗"/utf8>>;
skipped ->
<<"-"/utf8>>;
pending ->
<<"~"/utf8>>;
timed_out ->
<<"!"/utf8>>;
setup_failed ->
<<"âš "/utf8>>
end.
-file("src/dream_test/reporter/bdd.gleam", 384).
-spec format_failure_message(binary(), binary()) -> binary().
format_failure_message(Message, Base_indent) ->
case Message of
<<""/utf8>> ->
<<""/utf8>>;
_ ->
erlang:list_to_binary(
[Base_indent, <<" Message: "/utf8>>, Message, <<"\n"/utf8>>]
)
end.
-file("src/dream_test/reporter/bdd.gleam", 419).
-spec format_snapshot_failure(binary(), binary(), binary(), boolean(), binary()) -> binary().
format_snapshot_failure(
Actual,
Expected,
Snapshot_path,
Is_missing,
Base_indent
) ->
case Is_missing of
true ->
erlang:list_to_binary(
[Base_indent,
<<" Snapshot missing: "/utf8>>,
Snapshot_path,
<<"\n"/utf8>>]
);
false ->
erlang:list_to_binary(
[Base_indent,
<<" Snapshot: "/utf8>>,
Snapshot_path,
<<"\n"/utf8>>,
Base_indent,
<<" Expected: "/utf8>>,
Expected,
<<"\n"/utf8>>,
Base_indent,
<<" Actual: "/utf8>>,
Actual,
<<"\n"/utf8>>]
)
end.
-file("src/dream_test/reporter/bdd.gleam", 391).
-spec format_failure_payload(
gleam@option:option(dream_test@types:failure_payload()),
binary()
) -> binary().
format_failure_payload(Payload, Base_indent) ->
case Payload of
{some, {equality_failure, Actual, Expected}} ->
erlang:list_to_binary(
[Base_indent,
<<" Expected: "/utf8>>,
Expected,
<<"\n"/utf8>>,
Base_indent,
<<" Actual: "/utf8>>,
Actual,
<<"\n"/utf8>>]
);
{some,
{snapshot_failure, Actual@1, Expected@1, Snapshot_path, Is_missing}} ->
format_snapshot_failure(
Actual@1,
Expected@1,
Snapshot_path,
Is_missing,
Base_indent
);
_ ->
<<""/utf8>>
end.
-file("src/dream_test/reporter/bdd.gleam", 374).
-spec format_one_failure(dream_test@types:assertion_failure(), integer()) -> binary().
format_one_failure(Failure, Indent_level) ->
Base_indent = build_indent(Indent_level),
Header = erlang:list_to_binary(
[Base_indent, <<" "/utf8>>, erlang:element(2, Failure), <<"\n"/utf8>>]
),
Message_text = format_failure_message(
erlang:element(3, Failure),
Base_indent
),
Payload_text = format_failure_payload(
erlang:element(4, Failure),
Base_indent
),
erlang:list_to_binary([Header, Message_text, Payload_text]).
-file("src/dream_test/reporter/bdd.gleam", 359).
-spec format_all_failures(
list(dream_test@types:assertion_failure()),
integer(),
binary()
) -> binary().
format_all_failures(Failures, Indent_level, Accumulated) ->
case Failures of
[] ->
Accumulated;
[Failure | Rest] ->
Formatted = format_one_failure(Failure, Indent_level),
Updated = erlang:list_to_binary([Accumulated, Formatted]),
format_all_failures(Rest, Indent_level, Updated)
end.
-file("src/dream_test/reporter/bdd.gleam", 351).
-spec format_failure_details(dream_test@types:test_result(), integer()) -> binary().
format_failure_details(Result, Indent_level) ->
case erlang:element(4, Result) of
failed ->
format_all_failures(
erlang:element(7, Result),
Indent_level,
<<""/utf8>>
);
setup_failed ->
format_all_failures(
erlang:element(7, Result),
Indent_level,
<<""/utf8>>
);
_ ->
<<""/utf8>>
end.
-file("src/dream_test/reporter/bdd.gleam", 295).
-spec format_test_line(dream_test@types:test_result()) -> binary().
format_test_line(Result) ->
Depth = calculate_test_depth(erlang:element(3, Result)),
Indent = build_indent(Depth),
Marker = status_marker(erlang:element(4, Result)),
Name = extract_test_name(erlang:element(3, Result)),
Duration = format_duration(erlang:element(5, Result)),
Test_line = erlang:list_to_binary(
[Indent, Marker, <<" "/utf8>>, Name, Duration, <<"\n"/utf8>>]
),
Failure_text = format_failure_details(Result, Depth),
erlang:list_to_binary([Test_line, Failure_text]).
-file("src/dream_test/reporter/bdd.gleam", 477).
-spec sum_durations(list(dream_test@types:test_result()), integer()) -> integer().
sum_durations(Results, Total) ->
case Results of
[] ->
Total;
[Result | Rest] ->
sum_durations(Rest, Total + erlang:element(5, Result))
end.
-file("src/dream_test/reporter/bdd.gleam", 502).
-spec increment_if_matches(
dream_test@types:status(),
dream_test@types:status(),
integer()
) -> integer().
increment_if_matches(Status, Wanted, Count) ->
case Status =:= Wanted of
true ->
Count + 1;
false ->
Count
end.
-file("src/dream_test/reporter/bdd.gleam", 488).
-spec count_matching_status(
list(dream_test@types:test_result()),
dream_test@types:status(),
integer()
) -> integer().
count_matching_status(Results, Wanted, Count) ->
case Results of
[] ->
Count;
[Result | Rest] ->
Next_count = increment_if_matches(
erlang:element(4, Result),
Wanted,
Count
),
count_matching_status(Rest, Wanted, Next_count)
end.
-file("src/dream_test/reporter/bdd.gleam", 484).
-spec count_by_status(
list(dream_test@types:test_result()),
dream_test@types:status()
) -> integer().
count_by_status(Results, Wanted) ->
count_matching_status(Results, Wanted, 0).
-file("src/dream_test/reporter/bdd.gleam", 525).
-spec format_summary_parts(list(binary())) -> binary().
format_summary_parts(Parts) ->
case Parts of
[] ->
<<""/utf8>>;
_ ->
erlang:list_to_binary(
[<<", "/utf8>>, gleam@string:join(Parts, <<", "/utf8>>)]
)
end.
-file("src/dream_test/reporter/bdd.gleam", 532).
-spec add_summary_part_if_nonzero(list(binary()), integer(), binary()) -> list(binary()).
add_summary_part_if_nonzero(Parts, Count, Label) ->
case Count of
0 ->
Parts;
_ ->
[erlang:list_to_binary([erlang:integer_to_binary(Count), Label]) |
Parts]
end.
-file("src/dream_test/reporter/bdd.gleam", 509).
-spec build_summary_suffix(integer(), integer(), integer(), integer()) -> binary().
build_summary_suffix(Skipped, Pending, Timed_out, Setup_failed) ->
Parts = begin
_pipe = [],
_pipe@1 = add_summary_part_if_nonzero(
_pipe,
Skipped,
<<" skipped"/utf8>>
),
_pipe@2 = add_summary_part_if_nonzero(
_pipe@1,
Pending,
<<" pending"/utf8>>
),
_pipe@3 = add_summary_part_if_nonzero(
_pipe@2,
Timed_out,
<<" timed out"/utf8>>
),
add_summary_part_if_nonzero(
_pipe@3,
Setup_failed,
<<" setup failed"/utf8>>
)
end,
format_summary_parts(Parts).
-file("src/dream_test/reporter/bdd.gleam", 452).
-spec format_summary(list(dream_test@types:test_result())) -> binary().
format_summary(Results) ->
Total = erlang:length(Results),
Failed = count_by_status(Results, failed),
Skipped = count_by_status(Results, skipped),
Pending = count_by_status(Results, pending),
Timed_out = count_by_status(Results, timed_out),
Setup_failed = count_by_status(Results, setup_failed),
Passed = ((((Total - Failed) - Skipped) - Pending) - Timed_out) - Setup_failed,
Total_duration = sum_durations(Results, 0),
erlang:list_to_binary(
[<<"Summary: "/utf8>>,
erlang:integer_to_binary(Total),
<<" run, "/utf8>>,
erlang:integer_to_binary(Failed),
<<" failed, "/utf8>>,
erlang:integer_to_binary(Passed),
<<" passed"/utf8>>,
build_summary_suffix(Skipped, Pending, Timed_out, Setup_failed),
<<" in "/utf8>>,
dream_test@timing:format_duration_ms(Total_duration),
<<"\n"/utf8>>]
).
-file("src/dream_test/reporter/bdd.gleam", 258).
-spec count_common_prefix_check(
binary(),
binary(),
list(binary()),
list(binary()),
integer()
) -> integer().
count_common_prefix_check(Prev_head, Curr_head, Prev_rest, Curr_rest, Depth) ->
case Prev_head =:= Curr_head of
true ->
count_common_prefix(Prev_rest, Curr_rest, Depth + 1);
false ->
Depth
end.
-file("src/dream_test/reporter/bdd.gleam", 240).
-spec count_common_prefix(list(binary()), list(binary()), integer()) -> integer().
count_common_prefix(Previous, Current, Depth) ->
case {Previous, Current} of
{[Prev_head | Prev_rest], [Curr_head | Curr_rest]} ->
count_common_prefix_check(
Prev_head,
Curr_head,
Prev_rest,
Curr_rest,
Depth
);
{_, _} ->
Depth
end.
-file("src/dream_test/reporter/bdd.gleam", 231).
-spec format_one_result(dream_test@types:test_result(), list(binary())) -> binary().
format_one_result(Result, Previous_path) ->
Current_path = extract_describe_segments(erlang:element(3, Result)),
Common_depth = count_common_prefix(Previous_path, Current_path, 0),
New_segments = gleam@list:drop(Current_path, Common_depth),
Headers = format_header_segments(New_segments, Common_depth, <<""/utf8>>),
Test_line = format_test_line(Result),
erlang:list_to_binary([Headers, Test_line]).
-file("src/dream_test/reporter/bdd.gleam", 215).
-spec format_all_results(
list(dream_test@types:test_result()),
list(binary()),
binary()
) -> binary().
format_all_results(Results, Previous_path, Accumulated) ->
case Results of
[] ->
Accumulated;
[Result | Rest] ->
Formatted = format_one_result(Result, Previous_path),
Updated = erlang:list_to_binary([Accumulated, Formatted]),
New_path = extract_describe_segments(erlang:element(3, Result)),
format_all_results(Rest, New_path, Updated)
end.
-file("src/dream_test/reporter/bdd.gleam", 102).
-spec format_unit_results(list(dream_test@types:test_result())) -> binary().
format_unit_results(Results) ->
case Results of
[] ->
<<""/utf8>>;
_ ->
Sorted = gleam@list:sort(Results, fun compare_by_full_name/2),
format_all_results(Sorted, [], <<""/utf8>>)
end.
-file("src/dream_test/reporter/bdd.gleam", 83).
?DOC(
" Format test results as a BDD-style report string.\n"
"\n"
" Returns the complete report including:\n"
" - Hierarchical test results with status markers\n"
" - Failure details with messages and diffs\n"
" - Summary line with counts\n"
"\n"
" Gherkin tests are automatically formatted using the Gherkin reporter style.\n"
"\n"
" Use this when you need the report as a string (e.g., for testing the\n"
" reporter itself or writing to a file).\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" let report_string = format(results)\n"
" file.write(\"test-results.txt\", report_string)\n"
" ```\n"
).
-spec format(list(dream_test@types:test_result())) -> binary().
format(Results) ->
{Gherkin_results, Unit_results} = partition_by_kind(Results),
Unit_text = format_unit_results(Unit_results),
Gherkin_text = format_gherkin_results(Gherkin_results),
Summary_text = format_summary(Results),
erlang:list_to_binary(
[Unit_text, Gherkin_text, <<"\n"/utf8>>, Summary_text]
).
-file("src/dream_test/reporter/bdd.gleam", 207).
?DOC(
" Print test results using a provided writer function.\n"
"\n"
" This is the main entry point for most test runs. The writer function\n"
" receives the formatted report string and can print it, log it, or\n"
" handle it however needed.\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" // Print to stdout\n"
" results |> report(io.print)\n"
"\n"
" // Print each line separately (for flushing)\n"
" results |> report(io.println)\n"
"\n"
" // Custom handling\n"
" results |> report(fn(s) { logger.info(s) })\n"
" ```\n"
"\n"
" ## Parameters\n"
"\n"
" - `results` - List of test results from the runner\n"
" - `write` - Function that handles the formatted output string\n"
"\n"
" ## Returns\n"
"\n"
" Returns the input results unchanged, enabling pipeline composition:\n"
"\n"
" ```gleam\n"
" to_test_cases(\"my_test\", tests())\n"
" |> run_all()\n"
" |> report(io.print)\n"
" |> exit_on_failure()\n"
" ```\n"
).
-spec report(list(dream_test@types:test_result()), fun((binary()) -> nil)) -> list(dream_test@types:test_result()).
report(Results, Write) ->
Write(format(Results)),
Results.