Corrections to the previous weather-map pass: * The "helper" that was supposed to live in the top-right was the color legend, not the layer description. Move the Leaflet legend control to `topright` (was `bottomright` on desktop). * Put the layer description back in the desktop sidebar where it used to live, now with a "Group · Label" header above the body text so it matches the new style in the mockup. * Add play/stop controls to the weather forecast timeline, ported from the propagation map: start plays "now" → end at 1s/step and loops; stop returns to the time closest to wall clock. A manual click interrupts playback the same way it does on /map. * Fix the reconnect/reload loop I introduced by making mount do a synchronous ProfilesFile read + derive for the full 92k-point grid. That was pushing mount past the LV socket join timeout on a cold pod, which makes the LV client fall back to a full HTTP reload every ~20 seconds (no server-side error). Mount now uses the original `latest_weather_grid/1` cache-only hot path and only `weather_grid_at/2` (sync disk read) on user timeline scrubs. * Clean up the playback timer in the hook's `destroyed` callback.
114 lines
3.3 KiB
Elixir
114 lines
3.3 KiB
Elixir
defmodule MicrowavepropWeb.WeatherMapLiveTest do
|
|
# async: false because we write to the shared ProfilesFile directory
|
|
# which the WeatherMapLive reads at mount.
|
|
use MicrowavepropWeb.ConnCase, async: false
|
|
|
|
import Phoenix.LiveViewTest
|
|
|
|
alias Microwaveprop.Propagation.ProfilesFile
|
|
alias Microwaveprop.Weather.GridCache
|
|
|
|
setup do
|
|
GridCache.clear()
|
|
|
|
on_exit(fn ->
|
|
GridCache.clear()
|
|
# Scrub any profile files our tests wrote so other async tests
|
|
# that enumerate the ProfilesFile dir (e.g. latest_valid_time)
|
|
# aren't affected.
|
|
ProfilesFile.prune_older_than(~U[2099-12-31 23:59:59Z])
|
|
end)
|
|
|
|
:ok
|
|
end
|
|
|
|
defp write_profile(valid_time, lat, lon) do
|
|
profile =
|
|
for level <- [1000, 925, 850, 700], do: %{"pres" => level * 1.0, "tmpc" => 15.0, "dwpc" => 5.0}
|
|
|
|
grid_data = %{
|
|
{lat, lon} => %{
|
|
surface_temp_c: 22.0,
|
|
surface_dewpoint_c: 12.0,
|
|
surface_pressure_mb: 1010.0,
|
|
hpbl_m: 800.0,
|
|
pwat_mm: 25.0,
|
|
profile: profile
|
|
}
|
|
}
|
|
|
|
ProfilesFile.write!(valid_time, grid_data)
|
|
end
|
|
|
|
describe "layer description in sidebar" do
|
|
test "renders Group · Label header and description in the desktop sidebar", %{conn: conn} do
|
|
{:ok, _lv, html} = live(conn, ~p"/weather")
|
|
|
|
# Default layer is refractivity_gradient → group Surface, label N-Gradient.
|
|
assert html =~ "Surface"
|
|
assert html =~ "N-Gradient"
|
|
assert html =~ "Minimum refractivity gradient"
|
|
end
|
|
end
|
|
|
|
describe "forecast timeline" do
|
|
test "mount pushes data-valid-times for every persisted profile", %{conn: conn} do
|
|
base =
|
|
DateTime.utc_now()
|
|
|> DateTime.truncate(:second)
|
|
|> Map.put(:minute, 0)
|
|
|> Map.put(:second, 0)
|
|
|
|
times = for h <- 0..2, do: DateTime.add(base, h * 3600, :second)
|
|
# Seed a profile file at each forecast hour.
|
|
Enum.each(times, &write_profile(&1, 33.0, -97.0))
|
|
|
|
{:ok, _lv, html} = live(conn, ~p"/weather")
|
|
|
|
# Timeline container exists.
|
|
assert html =~ ~s(id="weather-forecast-timeline")
|
|
|
|
# Each valid_time is present in the map element's data attribute.
|
|
Enum.each(times, fn t ->
|
|
iso = DateTime.to_iso8601(t)
|
|
assert html =~ iso
|
|
end)
|
|
end
|
|
|
|
test "select_time replaces the rendered weather data with the new hour's grid", %{conn: conn} do
|
|
base =
|
|
DateTime.utc_now()
|
|
|> DateTime.truncate(:second)
|
|
|> Map.put(:minute, 0)
|
|
|> Map.put(:second, 0)
|
|
|
|
now_t = base
|
|
future_t = DateTime.add(base, 3 * 3600, :second)
|
|
|
|
# Seed f00 with a 22°C surface and f03 with a 10°C surface so we
|
|
# can tell them apart in the push_event payload.
|
|
write_profile(now_t, 33.0, -97.0)
|
|
|
|
grid_data = %{
|
|
{33.0, -97.0} => %{
|
|
surface_temp_c: 10.0,
|
|
surface_dewpoint_c: 5.0,
|
|
surface_pressure_mb: 1010.0,
|
|
hpbl_m: 500.0,
|
|
pwat_mm: 20.0,
|
|
profile: []
|
|
}
|
|
}
|
|
|
|
ProfilesFile.write!(future_t, grid_data)
|
|
|
|
{:ok, lv, _html} = live(conn, ~p"/weather")
|
|
|
|
# Select the future hour.
|
|
render_hook(lv, "select_time", %{"time" => DateTime.to_iso8601(future_t)})
|
|
|
|
assert_push_event(lv, "update_weather", %{data: data})
|
|
assert Enum.any?(data, fn r -> r.temperature == 10.0 end)
|
|
end
|
|
end
|
|
end
|