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.
155 lines
4.8 KiB
Elixir
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
|