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