diff --git a/test/towerops/coverages/profile_test.exs b/test/towerops/coverages/profile_test.exs index ffbeaa3a..376f799a 100644 --- a/test/towerops/coverages/profile_test.exs +++ b/test/towerops/coverages/profile_test.exs @@ -94,4 +94,45 @@ defmodule Towerops.Coverages.ProfileTest do assert_in_delta d, 96_000.0, 2_000.0 end end + + describe "sample/5 with clutter polygons" do + test "samples report a building when the great-circle path crosses one" do + # Flat 100m grid that the path will sample + grid = flat_grid(100.0) + + # Path from (0.95, -0.95) to (0.05, -0.05) — diagonal across the grid + tx = {0.95, -0.95} + rx = {0.05, -0.05} + + # A building polygon at the midpoint of the path with height 30 m + # Polygon coords are {lon, lat}, closed ring (last == first) + building = %{ + height_m: 30.0, + coords: [ + {-0.55, 0.45}, + {-0.45, 0.45}, + {-0.45, 0.55}, + {-0.55, 0.55}, + {-0.55, 0.45} + ] + } + + samples = Profile.sample(grid, tx, rx, 9, clutter: [building]) + + # 9 samples requested, should get exactly 9 + assert length(samples) == 9 + # At least one sample sits inside the polygon → ground (100) + building (30) + assert 130.0 in samples + end + + test "samples include canopy max when canopy grid is provided" do + grid = flat_grid(50.0) + canopy = flat_grid(10.0) + + samples = Profile.sample(grid, {0.95, -0.95}, {0.05, -0.05}, 5, canopy: canopy) + + # All samples should be at least DEM height (50 m); canopy adds clutter on top. + assert Enum.all?(samples, &(&1 >= 50.0)) + end + end end diff --git a/test/towerops_web/live/components/global_search_component_test.exs b/test/towerops_web/live/components/global_search_component_test.exs index 91e96e49..28cd57aa 100644 --- a/test/towerops_web/live/components/global_search_component_test.exs +++ b/test/towerops_web/live/components/global_search_component_test.exs @@ -77,5 +77,55 @@ defmodule ToweropsWeb.Live.Components.GlobalSearchComponentTest do assert html =~ "No results found" end + + test "close hides the overlay", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/dashboard") + + view + |> element("#global-search") + |> render_hook("open", %{}) + + html = + view + |> element("#global-search") + |> render_hook("close", %{}) + + refute html =~ "Search devices" + end + + test "ArrowDown / ArrowUp keyboard navigation cycles selected index", + %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/dashboard") + + view |> element("#global-search") |> render_hook("open", %{}) + view |> element("#global-search") |> render_hook("search", %{"value" => "Core"}) + + # Arrows shouldn't crash even at boundaries + view |> element("#global-search") |> render_hook("keydown", %{"key" => "ArrowDown"}) + view |> element("#global-search") |> render_hook("keydown", %{"key" => "ArrowDown"}) + view |> element("#global-search") |> render_hook("keydown", %{"key" => "ArrowUp"}) + + # An unrelated key is a no-op + html = + view + |> element("#global-search") + |> render_hook("keydown", %{"key" => "Tab"}) + + assert html =~ "Core" + end + + test "Enter on an empty result list is a no-op", %{conn: conn} do + {:ok, view, _html} = live(conn, ~p"/dashboard") + view |> element("#global-search") |> render_hook("open", %{}) + + html = + view + |> element("#global-search") + |> render_hook("keydown", %{"key" => "Enter"}) + + # No navigation, view stays alive + assert is_binary(html) + assert Process.alive?(view.pid) + end end end diff --git a/test/towerops_web/live/status_page_live_test.exs b/test/towerops_web/live/status_page_live_test.exs index 5b92f7c1..c21d91b9 100644 --- a/test/towerops_web/live/status_page_live_test.exs +++ b/test/towerops_web/live/status_page_live_test.exs @@ -97,5 +97,38 @@ defmodule ToweropsWeb.StatusPageLiveTest do {:ok, view, _html} = live(conn, ~p"/status/unknown-slug-12345") refute has_element?(view, "#status-page") end + + test "renders the status page when slug exists", %{conn: conn} do + user = Towerops.AccountsFixtures.user_fixture() + {:ok, org} = Towerops.Organizations.create_organization(%{name: "SP Org"}, user.id) + + {:ok, config} = + Towerops.StatusPages.create_config(%{ + organization_id: org.id, + slug: "sp-#{System.unique_integer([:positive])}", + company_name: "Acme Co", + enabled: true + }) + + {:ok, _view, html} = live(conn, ~p"/status/#{config.slug}") + assert html =~ "Acme Co" + end + + test ":status_updated PubSub message refreshes assigns", %{conn: conn} do + user = Towerops.AccountsFixtures.user_fixture() + {:ok, org} = Towerops.Organizations.create_organization(%{name: "Push Org"}, user.id) + + {:ok, config} = + Towerops.StatusPages.create_config(%{ + organization_id: org.id, + slug: "push-#{System.unique_integer([:positive])}", + company_name: "Push Inc", + enabled: true + }) + + {:ok, view, _html} = live(conn, ~p"/status/#{config.slug}") + send(view.pid, {:status_updated, %{}}) + assert render(view) =~ "Push Inc" + end end end