diff --git a/test/aprsme/packets/prepared_queries_test.exs b/test/aprsme/packets/prepared_queries_test.exs
index fca3d7e..6ed4c38 100644
--- a/test/aprsme/packets/prepared_queries_test.exs
+++ b/test/aprsme/packets/prepared_queries_test.exs
@@ -472,4 +472,99 @@ defmodule Aprsme.Packets.PreparedQueriesTest do
assert length(result) == 2
end
end
+
+ describe "get_recent_packets_in_bounds/2" do
+ test "returns packets within the bounding box" do
+ {:ok, _} =
+ create_positioned_packet(%{
+ sender: "INBOUNDS-1",
+ base_callsign: "INBOUNDS",
+ lat: Decimal.new("33.5"),
+ lon: Decimal.new("-96.5")
+ })
+
+ {:ok, _} =
+ create_positioned_packet(%{
+ sender: "OUTOFBOUNDS-1",
+ base_callsign: "OUTOFBOUNDS",
+ lat: Decimal.new("50.0"),
+ lon: Decimal.new("-120.0")
+ })
+
+ # bounds = [north, south, east, west]
+ result = PreparedQueries.get_recent_packets_in_bounds([34.0, 33.0, -96.0, -97.0], 24)
+
+ senders = Enum.map(result, & &1.sender)
+ assert "INBOUNDS-1" in senders
+ refute "OUTOFBOUNDS-1" in senders
+ end
+ end
+
+ describe "get_packet_count_in_area/2" do
+ test "counts only packets inside the bounding box" do
+ {:ok, _} =
+ create_positioned_packet(%{
+ sender: "COUNT-IN-1",
+ base_callsign: "COUNT",
+ lat: Decimal.new("33.25"),
+ lon: Decimal.new("-96.25")
+ })
+
+ {:ok, _} =
+ create_positioned_packet(%{
+ sender: "COUNT-OUT",
+ base_callsign: "COUNT",
+ lat: Decimal.new("55.0"),
+ lon: Decimal.new("-30.0")
+ })
+
+ # bounds = [north, south, east, west]
+ count = PreparedQueries.get_packet_count_in_area([34.0, 33.0, -96.0, -97.0], 24)
+ assert is_integer(count)
+ assert count >= 1
+ end
+
+ test "returns 0 when area has no packets" do
+ assert PreparedQueries.get_packet_count_in_area([0.0, -1.0, 180.0, 179.0], 1) == 0
+ end
+ end
+
+ describe "has_weather_packets?/1" do
+ test "returns true when callsign has weather packets" do
+ {:ok, _} =
+ create_positioned_packet(%{
+ sender: "HASWX-1",
+ base_callsign: "HASWX",
+ temperature: 72.0,
+ humidity: 50.0
+ })
+
+ assert PreparedQueries.has_weather_packets?("HASWX-1")
+ end
+
+ test "returns false when callsign has no weather packets" do
+ refute PreparedQueries.has_weather_packets?("NOWX-NEVER")
+ end
+ end
+
+ describe "weather_callsigns/1" do
+ test "returns a MapSet containing callsigns with weather data" do
+ {:ok, _} =
+ create_positioned_packet(%{
+ sender: "WXSET-1",
+ base_callsign: "WXSET",
+ has_weather: true,
+ temperature: 65.0
+ })
+
+ result = PreparedQueries.weather_callsigns(["WXSET-1", "NOWX-1"])
+ assert %MapSet{} = result
+ assert MapSet.member?(result, "WXSET-1")
+ refute MapSet.member?(result, "NOWX-1")
+ end
+
+ test "returns an empty MapSet for empty input" do
+ assert PreparedQueries.weather_callsigns([]) == MapSet.new()
+ end
+ end
end
diff --git a/test/aprsme_web/live/info_live/show_helpers_test.exs b/test/aprsme_web/live/info_live/show_helpers_test.exs
index 9c0568c..8b193f2 100644
--- a/test/aprsme_web/live/info_live/show_helpers_test.exs
+++ b/test/aprsme_web/live/info_live/show_helpers_test.exs
@@ -165,4 +165,33 @@ defmodule AprsmeWeb.InfoLive.ShowHelpersTest do
assert html =~ "
"Test error",
+ "stack" => "at test:1",
+ "component_id" => "test-component"
+ })
+ end
+
+ test "update_map_state with bounds in params", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/", on_error: :warn)
+
+ assert render_hook(view, "update_map_state", %{
+ "center" => %{"lat" => 33.0, "lng" => -96.0},
+ "zoom" => 10,
+ "bounds" => %{
+ "north" => 34.0,
+ "south" => 32.0,
+ "east" => -95.0,
+ "west" => -97.0
+ }
+ })
+ end
+
+ test "update_map_state with invalid/non-map center uses socket default", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/?lat=33.0&lng=-96.0", on_error: :warn)
+
+ assert render_hook(view, "update_map_state", %{
+ "center" => "not-a-map",
+ "zoom" => 8
+ })
+ end
+
+ test "marker_hover_start with invalid path does not crash", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/", on_error: :warn)
+
+ assert render_hook(view, "marker_hover_start", %{
+ "id" => "m1",
+ "path" => "",
+ "lat" => 33.0,
+ "lng" => -96.0
+ })
+ end
+
+ test "set_location event updates center", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/", on_error: :warn)
+
+ assert render_hook(view, "set_location", %{"lat" => 40.7, "lng" => -74.0})
+ end
+
+ test "update_trail_duration with extreme value is clamped", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/", on_error: :warn)
+
+ # Very large value should be parsed and clamped
+ assert render_hook(view, "update_trail_duration", %{"trail_duration" => "9999"})
+ assert render_hook(view, "update_trail_duration", %{"trail_duration" => "0"})
+ assert render_hook(view, "update_trail_duration", %{"trail_duration" => "invalid"})
+ end
+
+ test "update_historical_hours with invalid input", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/", on_error: :warn)
+
+ assert render_hook(view, "update_historical_hours", %{"historical_hours" => "invalid"})
+ assert render_hook(view, "update_historical_hours", %{"historical_hours" => "0"})
+ end
+
+ test "handles process_bounds_update via direct send", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/", on_error: :warn)
+
+ bounds = %{north: 34.0, south: 32.0, east: -95.0, west: -97.0}
+ send(view.pid, {:process_bounds_update, bounds})
+ Process.sleep(50)
+ assert render(view) =~ "aprs-map"
+ end
+
+ test "handles postgres_packet broadcast via batcher", %{conn: conn} do
+ {:ok, view, _html} = live(conn, "/?lat=33.0&lng=-96.0&z=12", on_error: :warn)
+
+ render_hook(view, "map_ready", %{})
+ Process.sleep(50)
+
+ packet = %{
+ id: "pg-1",
+ sender: "PG-1",
+ base_callsign: "PG",
+ ssid: "1",
+ lat: 33.0,
+ lon: -96.0,
+ has_position: true,
+ symbol_table_id: "/",
+ symbol_code: ">",
+ received_at: DateTime.utc_now(),
+ path: ""
+ }
+
+ send(view.pid, {:postgres_packet, packet})
+ Process.sleep(50)
+ assert render(view) =~ "aprs-map"
+ end
+ end
end
diff --git a/test/aprsme_web/live/weather_live/callsign_view_test.exs b/test/aprsme_web/live/weather_live/callsign_view_test.exs
index f03188d..88df5dc 100644
--- a/test/aprsme_web/live/weather_live/callsign_view_test.exs
+++ b/test/aprsme_web/live/weather_live/callsign_view_test.exs
@@ -77,5 +77,68 @@ defmodule AprsmeWeb.WeatherLive.CallsignViewTest do
# returned as-is.
assert CallsignView.format_weather_value(%{humidity: "65"}, :humidity, "en") == "65"
end
+
+ test "returns nil for unparseable string values on tracked keys" do
+ assert CallsignView.format_weather_value(%{temperature: "not-a-number"}, :temperature, "en") ==
+ nil
+ end
+
+ test "returns stringified value for untracked numeric keys" do
+ # humidity is numeric but not in the formatter map — passes through as string.
+ assert CallsignView.format_weather_value(%{humidity: 65}, :humidity, "en") == "65"
+ end
+
+ test "formats rain fields with units" do
+ result = CallsignView.format_weather_value(%{rain_1h: 0.25}, :rain_1h, "en")
+ assert is_binary(result)
+ assert String.contains?(result, " ")
+ end
+ end
+
+ describe "live packet broadcast handling" do
+ test "ignores non-matching postgres_packet broadcasts", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, ~p"/weather/OWNCALL", on_error: :warn)
+
+ # Broadcast an unrelated packet with no weather data
+ unrelated = %{
+ sender: "DIFFERENT",
+ temperature: nil,
+ humidity: nil,
+ pressure: nil,
+ wind_speed: nil,
+ wind_direction: nil,
+ rain_1h: nil
+ }
+
+ send(lv.pid, {:postgres_packet, unrelated})
+ Process.sleep(20)
+ assert Process.alive?(lv.pid)
+ end
+
+ test "ignores postgres_packet with wrong callsign", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, ~p"/weather/MYCALL", on_error: :warn)
+
+ wrong_call = %{
+ sender: "OTHER",
+ temperature: 72.5,
+ humidity: 50,
+ pressure: nil,
+ wind_speed: nil,
+ wind_direction: nil,
+ rain_1h: nil
+ }
+
+ send(lv.pid, {:postgres_packet, wrong_call})
+ Process.sleep(20)
+ assert Process.alive?(lv.pid)
+ end
+
+ test "ignores unknown messages without crashing", %{conn: conn} do
+ {:ok, lv, _html} = live(conn, ~p"/weather/UNKCALL", on_error: :warn)
+
+ send(lv.pid, :some_unknown_message)
+ Process.sleep(20)
+ assert Process.alive?(lv.pid)
+ end
end
end