-module(dream_test@reporter@json). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/dream_test/reporter/json.gleam"). -export([format/1, format_pretty/1, report/2, report_pretty/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( " JSON test reporter for dream_test.\n" "\n" " This reporter outputs test results as JSON for CI/CD integration,\n" " test aggregation, and tooling.\n" "\n" " ## Example Output\n" "\n" " ```json\n" " {\n" " \"version\": \"1.0\",\n" " \"timestamp_ms\": 1733151045123,\n" " \"duration_ms\": 315,\n" " \"system\": {\n" " \"os\": \"darwin\",\n" " \"otp_version\": \"27\",\n" " \"gleam_version\": \"0.67.0\"\n" " },\n" " \"summary\": {\n" " \"total\": 3,\n" " \"passed\": 2,\n" " \"failed\": 1,\n" " \"skipped\": 0,\n" " \"pending\": 0,\n" " \"timed_out\": 0,\n" " \"setup_failed\": 0\n" " },\n" " \"tests\": [\n" " {\n" " \"name\": \"adds numbers\",\n" " \"full_name\": [\"Calculator\", \"add\", \"adds numbers\"],\n" " \"status\": \"passed\",\n" " \"duration_ms\": 2,\n" " \"tags\": [],\n" " \"kind\": \"unit\",\n" " \"failures\": []\n" " }\n" " ]\n" " }\n" " ```\n" "\n" " ## Usage\n" "\n" " ```gleam\n" " import dream_test/reporter/json\n" " import gleam/io\n" "\n" " pub fn main() {\n" " to_test_cases(\"my_test\", tests())\n" " |> run_all()\n" " |> json.report(io.print)\n" " |> exit_on_failure()\n" " }\n" " ```\n" "\n" " ## Combining with BDD Reporter\n" "\n" " ```gleam\n" " results\n" " |> bdd.report(io.print) // Human-readable to stdout\n" " |> json.report(write_to_file) // JSON to file\n" " |> exit_on_failure()\n" " ```\n" ). -file("src/dream_test/reporter/json.gleam", 233). -spec build_payload_fields( gleam@option:option(dream_test@types:failure_payload()) ) -> list({binary(), gleam@json:json()}). build_payload_fields(Payload) -> case Payload of none -> []; {some, {equality_failure, Actual, Expected}} -> [{<<"expected"/utf8>>, gleam@json:string(Expected)}, {<<"actual"/utf8>>, gleam@json:string(Actual)}]; {some, {boolean_failure, Actual@1, Expected@1}} -> [{<<"expected"/utf8>>, gleam@json:bool(Expected@1)}, {<<"actual"/utf8>>, gleam@json:bool(Actual@1)}]; {some, {option_failure, Actual@2, Expected_some}} -> [{<<"expected_some"/utf8>>, gleam@json:bool(Expected_some)}, {<<"actual"/utf8>>, gleam@json:string(Actual@2)}]; {some, {result_failure, Actual@3, Expected_ok}} -> [{<<"expected_ok"/utf8>>, gleam@json:bool(Expected_ok)}, {<<"actual"/utf8>>, gleam@json:string(Actual@3)}]; {some, {collection_failure, Actual@4, Expected@2, Operation}} -> [{<<"expected"/utf8>>, gleam@json:string(Expected@2)}, {<<"actual"/utf8>>, gleam@json:string(Actual@4)}, {<<"operation"/utf8>>, gleam@json:string(Operation)}]; {some, {comparison_failure, Actual@5, Expected@3, Operator}} -> [{<<"expected"/utf8>>, gleam@json:string(Expected@3)}, {<<"actual"/utf8>>, gleam@json:string(Actual@5)}, {<<"comparison_operator"/utf8>>, gleam@json:string(Operator)}]; {some, {string_match_failure, Actual@6, Pattern, Operation@1}} -> [{<<"pattern"/utf8>>, gleam@json:string(Pattern)}, {<<"actual"/utf8>>, gleam@json:string(Actual@6)}, {<<"operation"/utf8>>, gleam@json:string(Operation@1)}]; {some, {custom_matcher_failure, Actual@7, Description}} -> [{<<"actual"/utf8>>, gleam@json:string(Actual@7)}, {<<"description"/utf8>>, gleam@json:string(Description)}] end. -file("src/dream_test/reporter/json.gleam", 223). -spec build_failure_json(dream_test@types:assertion_failure()) -> gleam@json:json(). build_failure_json(Failure) -> Base_fields = [{<<"operator"/utf8>>, gleam@json:string(erlang:element(2, Failure))}, {<<"message"/utf8>>, gleam@json:string(erlang:element(3, Failure))}], Payload_fields = build_payload_fields(erlang:element(4, Failure)), gleam@json:object(lists:append(Base_fields, Payload_fields)). -file("src/dream_test/reporter/json.gleam", 280). -spec status_to_string(dream_test@types:status()) -> binary(). status_to_string(Status) -> case Status of passed -> <<"passed"/utf8>>; failed -> <<"failed"/utf8>>; skipped -> <<"skipped"/utf8>>; pending -> <<"pending"/utf8>>; timed_out -> <<"timed_out"/utf8>>; setup_failed -> <<"setup_failed"/utf8>> end. -file("src/dream_test/reporter/json.gleam", 291). -spec kind_to_string(dream_test@types:test_kind()) -> binary(). kind_to_string(Kind) -> case Kind of unit -> <<"unit"/utf8>>; integration -> <<"integration"/utf8>>; {gherkin_scenario, Id} -> <<"gherkin:"/utf8, Id/binary>> end. -file("src/dream_test/reporter/json.gleam", 211). -spec build_test_json(dream_test@types:test_result()) -> gleam@json:json(). build_test_json(Result) -> gleam@json:object( [{<<"name"/utf8>>, gleam@json:string(erlang:element(2, Result))}, {<<"full_name"/utf8>>, gleam@json:array( erlang:element(3, Result), fun gleam@json:string/1 )}, {<<"status"/utf8>>, gleam@json:string(status_to_string(erlang:element(4, Result)))}, {<<"duration_ms"/utf8>>, gleam@json:int(erlang:element(5, Result))}, {<<"tags"/utf8>>, gleam@json:array( erlang:element(6, Result), fun gleam@json:string/1 )}, {<<"kind"/utf8>>, gleam@json:string(kind_to_string(erlang:element(8, Result)))}, {<<"failures"/utf8>>, gleam@json:array( erlang:element(7, Result), fun build_failure_json/1 )}] ). -file("src/dream_test/reporter/json.gleam", 299). -spec count_by_status( list(dream_test@types:test_result()), dream_test@types:status() ) -> integer(). count_by_status(Results, Wanted) -> gleam@list:fold( Results, 0, fun(Count, Result) -> case erlang:element(4, Result) =:= Wanted of true -> Count + 1; false -> Count end end ). -file("src/dream_test/reporter/json.gleam", 191). -spec build_summary_json(list(dream_test@types:test_result())) -> gleam@json:json(). build_summary_json(Results) -> Total = erlang:length(Results), Passed = count_by_status(Results, passed), 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), gleam@json:object( [{<<"total"/utf8>>, gleam@json:int(Total)}, {<<"passed"/utf8>>, gleam@json:int(Passed)}, {<<"failed"/utf8>>, gleam@json:int(Failed)}, {<<"skipped"/utf8>>, gleam@json:int(Skipped)}, {<<"pending"/utf8>>, gleam@json:int(Pending)}, {<<"timed_out"/utf8>>, gleam@json:int(Timed_out)}, {<<"setup_failed"/utf8>>, gleam@json:int(Setup_failed)}] ). -file("src/dream_test/reporter/json.gleam", 308). -spec calculate_total_duration(list(dream_test@types:test_result())) -> integer(). calculate_total_duration(Results) -> gleam@list:fold( Results, 0, fun(Total, Result) -> Total + erlang:element(5, Result) end ). -file("src/dream_test/reporter/json.gleam", 377). -spec spaces(integer()) -> binary(). spaces(Count) -> gleam@string:repeat(<<" "/utf8>>, Count). -file("src/dream_test/reporter/json.gleam", 320). -spec prettify_chars(list(binary()), integer(), boolean(), binary()) -> binary(). prettify_chars(Chars, Indent, In_string, Acc) -> case Chars of [] -> Acc; [<<"\\"/utf8>>, Escaped | Rest] when In_string -> prettify_chars( Rest, Indent, true, <<<>/binary, Escaped/binary>> ); [<<"\""/utf8>> | Rest@1] -> prettify_chars( Rest@1, Indent, not In_string, <> ); [Char | Rest@2] when In_string -> prettify_chars(Rest@2, Indent, true, <>); [<<"{"/utf8>> | Rest@3] -> New_indent = Indent + 2, prettify_chars( Rest@3, New_indent, false, <<<>/binary, (spaces(New_indent))/binary>> ); [<<"}"/utf8>> | Rest@4] -> New_indent@1 = Indent - 2, prettify_chars( Rest@4, New_indent@1, false, <<<<<>/binary, (spaces(New_indent@1))/binary>>/binary, "}"/utf8>> ); [<<"["/utf8>> | Rest@5] -> New_indent@2 = Indent + 2, prettify_chars( Rest@5, New_indent@2, false, <<<>/binary, (spaces(New_indent@2))/binary>> ); [<<"]"/utf8>> | Rest@6] -> New_indent@3 = Indent - 2, prettify_chars( Rest@6, New_indent@3, false, <<<<<>/binary, (spaces(New_indent@3))/binary>>/binary, "]"/utf8>> ); [<<","/utf8>> | Rest@7] -> prettify_chars( Rest@7, Indent, false, <<<>/binary, (spaces(Indent))/binary>> ); [<<":"/utf8>> | Rest@8] -> prettify_chars(Rest@8, Indent, false, <>); [<<" "/utf8>> | Rest@9] -> prettify_chars(Rest@9, Indent, false, Acc); [Char@1 | Rest@10] -> prettify_chars( Rest@10, Indent, false, <> ) end. -file("src/dream_test/reporter/json.gleam", 316). -spec prettify_json(binary()) -> binary(). prettify_json(Input) -> prettify_chars(gleam@string:to_graphemes(Input), 0, false, <<""/utf8>>). -file("src/dream_test/reporter/json.gleam", 183). -spec build_system_json() -> gleam@json:json(). build_system_json() -> gleam@json:object( [{<<"os"/utf8>>, gleam@json:string(dream_test_reporter_json_ffi:get_os())}, {<<"otp_version"/utf8>>, gleam@json:string( dream_test_reporter_json_ffi:get_otp_version() )}, {<<"gleam_version"/utf8>>, gleam@json:string( dream_test_reporter_json_ffi:get_gleam_version() )}] ). -file("src/dream_test/reporter/json.gleam", 170). -spec build_report_json(list(dream_test@types:test_result())) -> gleam@json:json(). build_report_json(Results) -> Total_duration = calculate_total_duration(Results), gleam@json:object( [{<<"version"/utf8>>, gleam@json:string(<<"1.0"/utf8>>)}, {<<"timestamp_ms"/utf8>>, gleam@json:int(dream_test_reporter_json_ffi:get_timestamp_ms())}, {<<"duration_ms"/utf8>>, gleam@json:int(Total_duration)}, {<<"system"/utf8>>, build_system_json()}, {<<"summary"/utf8>>, build_summary_json(Results)}, {<<"tests"/utf8>>, gleam@json:array(Results, fun build_test_json/1)}] ). -file("src/dream_test/reporter/json.gleam", 91). ?DOC( " Format test results as a compact JSON string.\n" "\n" " Returns a single-line JSON string suitable for machine parsing.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let json_string = format(results)\n" " file.write(\"test-results.json\", json_string)\n" " ```\n" ). -spec format(list(dream_test@types:test_result())) -> binary(). format(Results) -> _pipe = build_report_json(Results), gleam@json:to_string(_pipe). -file("src/dream_test/reporter/json.gleam", 107). ?DOC( " Format test results as pretty-printed JSON.\n" "\n" " Returns an indented, human-readable JSON string with 2-space indentation.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " let json_string = format_pretty(results)\n" " io.println(json_string)\n" " ```\n" ). -spec format_pretty(list(dream_test@types:test_result())) -> binary(). format_pretty(Results) -> _pipe = build_report_json(Results), _pipe@1 = gleam@json:to_string(_pipe), prettify_json(_pipe@1). -file("src/dream_test/reporter/json.gleam", 140). ?DOC( " Print test results as JSON using a provided writer function.\n" "\n" " This is the main entry point for JSON reporting. The writer function\n" " receives the JSON string and can print it, log it, or write it to a file.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " // Print to stdout\n" " results |> json.report(io.print)\n" "\n" " // Write to file\n" " results |> json.report(fn(s) { file.write(\"results.json\", s) })\n" " ```\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" " |> bdd.report(io.print)\n" " |> json.report(write_to_file)\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. -file("src/dream_test/reporter/json.gleam", 158). ?DOC( " Print test results as pretty-printed JSON using a provided writer function.\n" "\n" " Same as `report` but with indented, human-readable output.\n" "\n" " ## Example\n" "\n" " ```gleam\n" " results |> json.report_pretty(io.print)\n" " ```\n" ). -spec report_pretty( list(dream_test@types:test_result()), fun((binary()) -> nil) ) -> list(dream_test@types:test_result()). report_pretty(Results, Write) -> Write(format_pretty(Results)), Results.