prop/test/microwaveprop_web/live/eme_live_test.exs
Graham McIntire fd976b0cd5
fix: resolve 391 Credo issues across codebase
- Add jump_credo_checks ~> 0.4 with all 20 checks enabled
- Fix all standard Credo issues: 139 @spec (113 done, 26 remain),
  4 refactoring, 3 alias usage, 9 System.cmd env, 5 unsafe_to_atom,
  2 max line length, 9 assert_receive timeout
- Fix 170+ jump_credo_checks warnings:
  - 117 TopLevelAliasImportRequire: move nested alias/import to module top
  - 32 UseObanProWorker: switch to Oban.Pro.Worker
  - 4 DoctestIExExamples: add doctests / create test file
  - ~20 WeakAssertion: strengthen type-check assertions
  - Various ConditionalAssertion, AssertReceiveTimeout fixes
- Exclude vendor/ from Credo analysis
- Remaining: 175 warnings (mostly opinionated WeakAssertion,
  AvoidSocketAssignsInTest), 26 @spec annotations
2026-06-12 13:51:32 -05:00

183 lines
6.4 KiB
Elixir
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

defmodule MicrowavepropWeb.EmeLiveTest do
use MicrowavepropWeb.ConnCase, async: true
use ExUnitProperties
import Phoenix.LiveViewTest
alias Microwaveprop.Propagation.BandConfig
setup do
# CallsignClient.locate/1 falls through to Qrz.Client for any input
# that isn't a grid or coord pair. Stub the QRZ HTTP plug so the
# "unknown callsign" path returns a deterministic error instead of
# raising about a missing stub.
Req.Test.stub(Microwaveprop.Qrz.Client, fn conn ->
Plug.Conn.send_resp(conn, 404, "not found")
end)
:ok
end
describe "GET /eme" do
test "renders the empty form when no source is supplied", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/eme")
assert html =~ "EME Calculator"
assert html =~ "Station (callsign, grid, or"
assert html =~ "Calculate"
# No aim readout yet.
refute html =~ "Antenna aim"
end
test "renders the live Moon aim + link budget when a grid source is provided", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/eme?source=EM13&band=1296&tx_power_dbm=50&tx_gain_dbi=30")
assert html =~ "Antenna aim"
assert html =~ "Link budget"
assert html =~ "EM13"
assert html =~ "Azimuth"
assert html =~ "Elevation"
assert html =~ "Slant range"
# Path loss should be in the high 200s (dB) for 1296 MHz.
assert html =~ "dB"
end
test "submitting the form patches the URL with the supplied params", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/eme")
view
|> form("form",
source: "EM13",
band: "432",
tx_power_dbm: "60",
tx_gain_dbi: "25",
bandwidth_hz: "100",
t_sys_k: "150"
)
|> render_submit()
# Flush any pending assign updates to catch redirects/patches.
html = render(view)
assert html =~ "EM13"
assert html =~ "Antenna aim"
end
test "coordinate pair 'lat,lon' resolves without touching the callsign API", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/eme?source=32.9,-97.0&band=1296")
assert html =~ "Antenna aim"
assert html =~ "32.9"
assert html =~ "-97.0"
end
test "invalid Maidenhead grid surfaces a validation error", %{conn: conn} do
# A 4-char prefix that passes the loose regex but trips
# Maidenhead.to_latlon's strict validator (subsquare "ZZ" isn't A-X).
{:ok, _view, html} = live(conn, ~p"/eme?source=EM12ZZ")
assert html =~ "Invalid grid square"
refute html =~ "Antenna aim"
end
test "unknown callsign input surfaces a 'Could not find' error", %{conn: conn} do
# A random string that's neither a coord nor a maidenhead grid falls
# into the CallsignClient.locate path. The test DB has no cached
# entry and the real QRZ API is unreachable in tests → error branch.
{:ok, _view, html} = live(conn, ~p"/eme?source=NOSUCHCALL")
assert html =~ "Could not find"
refute html =~ "Antenna aim"
end
test "empty source renders the 'enter callsign or grid' empty-state message",
%{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/eme?source=&band=1296")
assert html =~ "Enter your callsign or grid"
refute html =~ "Antenna aim"
end
test "typing into form emits the WebGL globe hook state via push_event",
%{conn: conn} do
# Initial render: empty source, no eme:update. Triggering a form
# change with a valid grid should both patch the URL and push the
# eme:update payload to the hook.
{:ok, view, _html} = live(conn, ~p"/eme")
render_change(
form(view, "form",
source: "EM13",
band: "1296",
tx_power_dbm: "50",
tx_gain_dbi: "30",
bandwidth_hz: "100",
t_sys_k: "150"
)
)
assert_push_event(view, "eme:update", %{lat: _, lon: _, az: _, el: _})
end
test "renders the EarthMoon geometry WebGL hook with initial station geometry", %{conn: conn} do
{:ok, _view, html} = live(conn, ~p"/eme?source=EM13&band=1296")
assert html =~ "EarthMoon geometry"
# The WebGL globe is rendered by a LiveView hook — assert the
# hook element + the initial station/moon data attributes that
# feed the Three.js scene on mount.
assert html =~ ~s(id="eme-globe")
assert html =~ ~s(phx-hook="EmeGlobe")
assert html =~ ~s(phx-update="ignore")
assert html =~ ~s(data-station-lat=")
assert html =~ ~s(data-station-lon=")
assert html =~ ~s(data-moon-az=")
assert html =~ ~s(data-moon-el=")
end
end
describe "property: form validation across valid/invalid source inputs" do
property "valid inputs render an Antenna aim readout, invalid inputs render a validation error" do
bands = Enum.map(BandConfig.band_options(), fn {_label, value} -> value end)
# A valid-input sample is a Maidenhead grid + any configured band;
# an invalid-input sample is a non-grid/non-callsign string that
# neither resolves as coords nor passes the maidenhead prefix
# regex. Both categories are exercised in the same property.
valid_source_gen =
StreamData.member_of(["EM13", "FM19", "EN00", "DM04", "FN31", "CM87", "EL09"])
invalid_source_gen =
StreamData.member_of(["ZZ99", "ZZ00", "Z1", "@@@@", "ZZZZZZ"])
check all(
band <- StreamData.member_of(bands),
valid_source <- valid_source_gen,
invalid_source <- invalid_source_gen,
max_runs: 10
) do
conn = Phoenix.ConnTest.build_conn()
{:ok, _valid_view, valid_html} =
live(conn, ~p"/eme?source=#{valid_source}&band=#{band}")
assert valid_html =~ "Antenna aim", """
Expected a valid input to produce an Antenna aim readout.
source=#{inspect(valid_source)} band=#{band}
"""
{:ok, _invalid_view, invalid_html} =
live(conn, ~p"/eme?source=#{invalid_source}&band=#{band}")
# Either the loose maidenhead regex catches it (invalid grid
# error) or it falls through to callsign lookup (not found).
assert invalid_html =~ ~r/Invalid grid square|Could not find/,
"""
Expected an invalid input to produce a validation error.
source=#{inspect(invalid_source)} band=#{band}
"""
refute invalid_html =~ "Antenna aim"
end
end
end
end