defmodule MicrowavepropWeb.SkewtLiveTest do use MicrowavepropWeb.ConnCase, async: false import Phoenix.LiveViewTest alias Microwaveprop.Weather.HrrrProfile alias Microwaveprop.Weather.Sounding alias Microwaveprop.Weather.Station describe "with HRRR profile data near the queried location" do setup do profile_data = [ %{"pres" => 1000.0, "hght" => 110, "tmpc" => 25.0, "dwpc" => 18.0}, %{"pres" => 925.0, "hght" => 770, "tmpc" => 18.0, "dwpc" => 12.0}, %{"pres" => 850.0, "hght" => 1500, "tmpc" => 14.0, "dwpc" => 8.0}, %{"pres" => 700.0, "hght" => 3000, "tmpc" => 0.0, "dwpc" => -10.0}, %{"pres" => 500.0, "hght" => 5500, "tmpc" => -20.0, "dwpc" => -30.0}, %{"pres" => 400.0, "hght" => 7000, "tmpc" => -30.0, "dwpc" => -40.0}, %{"pres" => 300.0, "hght" => 9000, "tmpc" => -45.0, "dwpc" => -55.0}, %{"pres" => 250.0, "hght" => 10_500, "tmpc" => -55.0, "dwpc" => -65.0}, %{"pres" => 200.0, "hght" => 12_000, "tmpc" => -55.0, "dwpc" => -65.0}, %{"pres" => 150.0, "hght" => 13_500, "tmpc" => -55.0, "dwpc" => -65.0}, %{"pres" => 100.0, "hght" => 16_000, "tmpc" => -55.0, "dwpc" => -65.0} ] # Insert near EM12kp (32.646, -97.125) now = DateTime.truncate(DateTime.utc_now(), :second) %HrrrProfile{} |> HrrrProfile.changeset(%{ valid_time: now, run_time: now, lat: 32.65, lon: -97.12, profile: profile_data, hpbl_m: 1500.0, pwat_mm: 25.0, surface_temp_c: 25.0, surface_dewpoint_c: 18.0, surface_pressure_mb: 1013.0, surface_refractivity: 320.5, min_refractivity_gradient: -45.0, ducting_detected: false }) |> Microwaveprop.Repo.insert!() %{valid_time: now} end test "renders the time selector when valid_times list is non-empty", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") _ = render_async(view) # We can't reliably assert HRRR data renders since fetch_rich_cell # tries an HTTP call that fails — but the page should not crash. assert render(view) =~ "Skew-T" end test "select_time event triggers a re-load with the chosen valid_time", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") _ = render_async(view) # Trigger select_time with a valid future ISO. Even if no profile is # found for this time, the handler exercises the full re-load path. future = DateTime.utc_now() |> DateTime.add(3600, :second) |> DateTime.to_iso8601() _ = render_hook(view, "select_time", %{"valid_time" => future}) _ = render_async(view) assert render(view) =~ "Skew-T" end end describe "with a sounding readout near the queried point" do setup do # Insert a station + sounding so build_sounding_readout returns # non-nil and renders the side panel. now = DateTime.truncate(DateTime.utc_now(), :second) {:ok, station} = %Station{} |> Station.changeset(%{ station_code: "FWD", station_type: "sounding", name: "Fort Worth", lat: 32.835, lon: -97.298 }) |> Microwaveprop.Repo.insert() sounding_attrs = %{ station_id: station.id, observed_at: now, ducting_detected: false, min_refractivity_gradient: -45.0, boundary_layer_depth_m: 1500.0, precipitable_water_mm: 25.0, surface_refractivity: 320.0, level_count: 3, profile: [ %{"pres" => 1000.0, "hght" => 110, "tmpc" => 25.0, "dwpc" => 18.0}, %{"pres" => 925.0, "hght" => 770, "tmpc" => 18.0, "dwpc" => 12.0}, %{"pres" => 850.0, "hght" => 1500, "tmpc" => 14.0, "dwpc" => 8.0} ] } %Sounding{} |> Sounding.changeset(sounding_attrs) |> Microwaveprop.Repo.insert!() :ok end test "renders without crashing when sounding data is in the DB", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") _ = render_async(view) assert render(view) =~ "Skew-T" end end describe "select_time event" do test "ignored when no location is set yet", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt") # No location resolved → select_time falls through to the no-op. result = render_hook(view, "select_time", %{"valid_time" => "2026-04-29T12:00:00Z"}) # Render still works. assert result =~ "Skew-T" end test "ignored when valid_time is unparseable", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") render_async(view) result = render_hook(view, "select_time", %{"valid_time" => "not-a-timestamp"}) assert result =~ "Skew-T" end end describe "search event" do test "patches URL when user submits the form", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt") render_hook(view, "search", %{"q" => "EM13"}) assert render_async(view) =~ "EM13" end end describe "GET /skewt" do test "renders search bar with no results when no query is supplied", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/skewt") assert html =~ "Skew-T-Log-P" assert html =~ ~s|placeholder="EM12kp| refute html =~ "viewBox=\"0 0 720" end test "initial render with a URL query shows the page chrome + a loading indicator", %{conn: conn} do {:ok, _view, html} = live(conn, ~p"/skewt?q=EM12kp") assert html =~ "Skew-T-Log-P" assert html =~ ~s|placeholder="EM12kp| assert html =~ "Resolving location" refute html =~ "EM12KP" end test "after the async completes the resolved location label appears", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") html = render_async(view) assert html =~ "EM12KP" assert html =~ "32.646" assert html =~ "-97.125" refute html =~ "Resolving location" end test "submitting the form patches the URL with the query", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt") view |> form("form", q: "EM12") |> render_submit() assert render_async(view) =~ "EM12" end test "shows info alert when no HRRR profiles are stored", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") html = render_async(view) assert html =~ "No HRRR profiles are currently stored" assert has_element?(view, ".alert.alert-info") end test "time selector is not shown when there are no valid times", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") render_async(view) refute has_element?(view, "button[phx-click=select_time]") end test "page renders with loading indicator or resolved content after mount", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") # The async may have already completed by the time we render. # Await it and assert the resolved state. html = render_async(view) assert html =~ "EM12KP" end test "on successful async the loading state is cleared and location shown", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") html = render_async(view) refute html =~ "Resolving location" assert html =~ "EM12KP" assert html =~ "32.646" end test "coordinates are formatted with 3 decimal places", %{conn: conn} do {:ok, view, _html} = live(conn, ~p"/skewt?q=EM12kp") html = render_async(view) assert html =~ "32.646" assert html =~ "-97.125" end end end