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\n"
" ✓ handles zero\n"
" subtract\n"
" ✓ subtracts positive numbers\n"
" ✗ handles negative results\n"
" equal\n"
" Message: Should handle negative subtraction\n"
" Expected: -5\n"
" Actual: 5\n"
"\n"
" Summary: 4 run, 1 failed, 3 passed\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", 168).
-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", 202).
-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", 214).
-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", 210).
-spec build_indent(integer()) -> binary().
build_indent(Level) ->
build_indent_recursive(Level, <<""/utf8>>).
-file("src/dream_test/reporter/bdd.gleam", 176).
-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", 221).
-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", 228).
-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", 272).
-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", 279).
-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>>]
);
_ ->
<<""/utf8>>
end.
-file("src/dream_test/reporter/bdd.gleam", 262).
-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", 247).
-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", 239).
-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", 192).
-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)),
Test_line = erlang:list_to_binary(
[Indent, Marker, <<" "/utf8>>, Name, <<"\n"/utf8>>]
),
Failure_text = format_failure_details(Result, Depth),
erlang:list_to_binary([Test_line, Failure_text]).
-file("src/dream_test/reporter/bdd.gleam", 339).
-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", 325).
-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", 321).
-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", 362).
-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", 369).
-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", 346).
-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", 299).
-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,
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),
<<"\n"/utf8>>]
).
-file("src/dream_test/reporter/bdd.gleam", 155).
-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", 137).
-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", 128).
-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", 112).
-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", 78).
?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"
" 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) ->
Formatted_results = format_all_results(Results, [], <<""/utf8>>),
Summary_text = format_summary(Results),
erlang:list_to_binary([Formatted_results, <<"\n"/utf8>>, Summary_text]).
-file("src/dream_test/reporter/bdd.gleam", 108).
?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"
).
-spec report(list(dream_test@types:test_result()), fun((binary()) -> nil)) -> nil.
report(Results, Write) ->
Write(format(Results)).