From 4bf1a6b3b3749001570f857fc291de75cafe8cf0 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 8 May 2026 12:11:14 -0500 Subject: [PATCH] Add coverage tests for CoreComponents (input form/field, safe_row_id) and DataBuilder edge cases --- .../components/core_components_test.exs | 90 +++++++++++++++++++ .../map_live/data_builder_extras_test.exs | 54 +++++++++++ 2 files changed, 144 insertions(+) diff --git a/test/aprsme_web/components/core_components_test.exs b/test/aprsme_web/components/core_components_test.exs index ad1dff5..9e229b5 100644 --- a/test/aprsme_web/components/core_components_test.exs +++ b/test/aprsme_web/components/core_components_test.exs @@ -129,6 +129,96 @@ defmodule AprsmeWeb.CoreComponentsTest do end end + describe "table/1 row id branches via safe_row_id" do + test "uses string-keyed 'id' when present" do + html = + render_component(&CoreComponents.table/1, %{ + id: "rows", + rows: [%{"id" => "row-string", "name" => "X"}], + col: [%{label: "Name", inner_block: fn _, row -> Map.get(row, "name") end}], + action: [], + row_click: nil, + row_id: nil, + row_item: & &1, + rest: %{} + }) + + assert html =~ "row-string" + end + + test "falls back to MD5 hash when row map has no id key" do + html = + render_component(&CoreComponents.table/1, %{ + id: "rows-noid", + rows: [%{name: "noid"}], + col: [%{label: "Name", inner_block: fn _, row -> Map.get(row, :name) end}], + action: [], + row_click: nil, + row_id: nil, + row_item: & &1, + rest: %{} + }) + + # The rendered HTML must include the rows table without crashing. + assert html =~ "rows-noid" + end + + test "delegates non-map rows to Phoenix.Param" do + # Phoenix.Param.to_param("string-row") returns the string itself. + html = + render_component(&CoreComponents.table/1, %{ + id: "string-rows", + rows: ["alpha", "beta"], + col: [%{label: "Value", inner_block: fn _, row -> row end}], + action: [], + row_click: nil, + row_id: nil, + row_item: & &1, + rest: %{} + }) + + assert html =~ "alpha" + assert html =~ "beta" + end + end + + describe "input/1 with a {form, field} tuple" do + test "extracts name/id/value/errors from the form via the {form, field} clause" do + # Builds a Phoenix.HTML.Form via Plug.Conn-less helpers — Form.from_struct + # gives a minimal form value to thread through input/1's first clause. + f = Phoenix.Component.to_form(%{"username" => "alice"}, as: :user) + + html = + render_component(&CoreComponents.input/1, %{ + field: {f, :username}, + type: "text", + label: "Username", + multiple: false, + errors: [] + }) + + assert html =~ ~s(name="user[username]") + assert html =~ "alice" + end + + test "appends [] to the name when multiple is true" do + f = Phoenix.Component.to_form(%{"tags" => []}, as: :user) + + html = + render_component(&CoreComponents.input/1, %{ + field: {f, :tags}, + type: "select", + label: "Tags", + multiple: true, + options: [{"Foo", "foo"}, {"Bar", "bar"}], + errors: [], + prompt: nil + }) + + assert html =~ ~s(name="user[tags][]) + end + end + describe "input/1 checkbox" do test "renders a checkbox with a hidden false companion input" do html = diff --git a/test/aprsme_web/live/map_live/data_builder_extras_test.exs b/test/aprsme_web/live/map_live/data_builder_extras_test.exs index ae43526..033fa83 100644 --- a/test/aprsme_web/live/map_live/data_builder_extras_test.exs +++ b/test/aprsme_web/live/map_live/data_builder_extras_test.exs @@ -102,6 +102,60 @@ defmodule AprsmeWeb.MapLive.DataBuilderExtrasTest do end end + describe "build_packet_data with invalid coordinates" do + test "returns nil when lat is out of range" do + packet = %{ + "id" => "bad-1", + "sender" => "BAD-1", + "callsign" => "BAD-1", + "received_at" => DateTime.utc_now(), + "lat" => 99.9, + "lon" => -96.0 + } + + assert is_nil(DataBuilder.build_packet_data(packet, true)) + end + + test "returns nil when lat/lon are non-numeric" do + packet = %{ + "id" => "bad-2", + "sender" => "BAD-2", + "callsign" => "BAD-2", + "received_at" => DateTime.utc_now(), + "lat" => "not-a-number", + "lon" => "also-bad" + } + + assert is_nil(DataBuilder.build_packet_data(packet, true)) + end + end + + describe "build_packet_data_list/1 with empty groups" do + test "returns [] for an empty list" do + assert DataBuilder.build_packet_data_list([]) == [] + end + + test "deduplicates packets with the same callsign_group" do + now = DateTime.utc_now() + + packets = + for i <- 1..3 do + %{ + "id" => "dup-#{i}", + "sender" => "DUP-1", + "callsign" => "DUP-1", + "callsign_group" => "DUP-1", + "received_at" => DateTime.add(now, -i * 60, :second), + "lat" => 33.0 + i * 0.001, + "lon" => -96.0 + i * 0.001 + } + end + + result = DataBuilder.build_packet_data_list(packets) + assert is_list(result) + end + end + describe "build_packet_data with string-typed weather values exercises convert_unit" do test "string numeric weather values parse via Float.parse branch" do packet = %{