prop/test/microwaveprop_web/live/map_live_test.exs
Graham McIntire 434fca2ad1
Show forecast-hour progress on the map pipeline chip
PropagationGridWorker now broadcasts a {:propagation_pipeline_progress,
%{forecast_hour:, valid_time:}} message on the propagation:pipeline
PubSub topic at the start of each process_forecast_hour/4 call.
MapLive subscribes on mount, stashes the last message in the new
:pipeline_progress assign, and clears it on the refresh tick whenever
PipelineStatus flips out of :running.

The chip component consults a new PipelineStatus.running_label/1
helper that formats the payload as "Updating propagation · now" for
f00 and "Updating propagation · +Nh" for f01-f18, falling back to
the base running label until the first progress message arrives.
Covered by unit tests on the formatter and an integration test in
MapLiveTest that seeds an executing grid job, broadcasts progress,
and asserts the rendered chip.
2026-04-14 11:12:51 -05:00

155 lines
4.8 KiB
Elixir

defmodule MicrowavepropWeb.MapLiveTest do
use MicrowavepropWeb.ConnCase, async: false
import Phoenix.LiveViewTest
describe "mount" do
test "renders full-page map", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ ~s(id="propagation-map")
assert html =~ ~s(phx-hook="PropagationMap")
end
test "renders band dropdown with all bands", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ "10 GHz"
assert html =~ "24 GHz"
assert html =~ "47 GHz"
assert html =~ "75 GHz"
assert html =~ "241 GHz"
end
test "defaults to 10 GHz selected", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ ~s(class="active")
end
test "includes data-scores attribute", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ "data-scores"
end
test "renders grid toggle", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ "Grid squares"
assert html =~ ~s(phx-click="toggle_grid")
end
test "renders scoring algorithm link", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ "Scoring Algorithm"
assert html =~ ~s(/algo)
end
test "renders submit QSO link", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ "Submit a Contact"
assert html =~ ~s(/submit)
end
end
describe "select_band" do
test "updates selected band on event", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/map")
html = render_click(lv, "select_band", %{"value" => 24_000})
assert html =~ "24 GHz"
end
end
describe "antenna height" do
test "renders antenna height input with default 33 ft", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ ~s(name="height_ft")
assert html =~ ~s(value="33")
assert html =~ "ft"
end
test "updates antenna height on change", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/map")
html = render_change(lv, "set_antenna_height", %{"height_ft" => "20"})
assert html =~ ~s(value="20")
end
end
describe "compute_viewshed" do
test "accepts compute_viewshed event without crashing", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/map")
# Event should be accepted; async computation may fail (no SRTM in test)
# but the LiveView should not crash
render_click(lv, "compute_viewshed", %{"lat" => 32.9, "lon" => -97.0})
# If we get here without crash, the event handler exists
assert render(lv) =~ "propagation-map"
end
end
describe "toggle_grid" do
test "toggles grid visibility", %{conn: conn} do
{:ok, lv, html} = live(conn, ~p"/map")
refute html =~ "checked"
html = render_click(lv, "toggle_grid")
assert html =~ "checked"
html = render_click(lv, "toggle_grid")
refute html =~ "checked"
end
end
describe "pipeline status chip" do
test "renders the aggregated pipeline status chip on mount", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
# Both the mobile and desktop sidebars carry their own chip.
assert html =~ ~s(id="pipeline-status-mobile")
assert html =~ ~s(id="pipeline-status-desktop")
# With a clean test DB no pipeline workers have ever run.
assert html =~ ~s(data-pipeline-state="unknown")
end
test "shows the current forecast hour while PropagationGridWorker runs", %{conn: conn} do
# Put a grid-worker row in executing state so PipelineStatus.current/0
# reports :running. Bypassing Oban's normal insert keeps the SQL
# sandbox from running the worker inline.
now = DateTime.utc_now()
Microwaveprop.Repo.insert!(
struct!(Oban.Job, %{
state: "executing",
queue: "propagation",
worker: "Microwaveprop.Workers.PropagationGridWorker",
args: %{},
attempt: 1,
max_attempts: 3,
inserted_at: now,
scheduled_at: now,
attempted_at: now
})
)
{:ok, lv, html} = live(conn, ~p"/map")
# Before any progress message arrives the chip uses the base label.
assert html =~ "Updating propagation"
assert html =~ ~s(data-pipeline-state="running")
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"propagation:pipeline",
{:propagation_pipeline_progress, %{forecast_hour: 3, valid_time: ~U[2026-04-14 18:00:00Z]}}
)
html = render(lv)
assert html =~ "+3h"
Phoenix.PubSub.broadcast(
Microwaveprop.PubSub,
"propagation:pipeline",
{:propagation_pipeline_progress, %{forecast_hour: 0, valid_time: ~U[2026-04-14 15:00:00Z]}}
)
html = render(lv)
assert html =~ "now"
end
end
end