defmodule MicrowavepropWeb.ContactLive.IndexTest do use MicrowavepropWeb.ConnCase, async: true import Microwaveprop.AccountsFixtures import Phoenix.LiveViewTest alias Microwaveprop.Radio.Contact alias Microwaveprop.Repo alias Microwaveprop.Terrain.ElevationClient alias Microwaveprop.Weather.HrrrNativeProfile alias Microwaveprop.Weather.NarrProfile setup do Req.Test.stub(ElevationClient, fn conn -> Req.Test.json(conn, []) end) Req.Test.stub(Microwaveprop.Weather.HrrrClient, fn conn -> Plug.Conn.send_resp(conn, 404, "not found") end) Req.Test.stub(Microwaveprop.Weather.SolarClient, fn conn -> Req.Test.text(conn, "") end) :ok end defp create_contact(attrs \\ %{}) do default = %{ station1: "W5XD", station2: "K5TR", qso_timestamp: ~U[2026-03-28 18:00:00Z], mode: "CW", band: Decimal.new("1296"), grid1: "EM12", grid2: "EM00", pos1: %{"lat" => 32.9, "lon" => -97.0}, pos2: %{"lat" => 30.3, "lon" => -97.7}, distance_km: Decimal.new("295") } merged = Map.merge(default, attrs) {user_id, changeset_attrs} = Map.pop(merged, :user_id) {:ok, contact} = %Contact{user_id: user_id} |> Contact.changeset(changeset_attrs) |> Repo.insert() contact end describe "Index" do test "renders page with Contacts heading", %{conn: conn} do {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "Contacts" end test "shows total contact count in subtitle", %{conn: conn} do create_contact() create_contact(%{station1: "N5AC"}) create_contact(%{station1: "K5TR"}) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "3 total" end test "table shows contact data", %{conn: conn} do create_contact() {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "W5XD" assert html =~ "K5TR" assert html =~ "CW" assert html =~ "1296" end test "row links to detail page", %{conn: conn} do contact = create_contact() {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ ~p"/contacts/#{contact.id}" end test "sort params order the table", %{conn: conn} do create_contact(%{station1: "ZZ9ZZ"}) create_contact(%{station1: "AA1AA"}) {:ok, _lv, html} = live(conn, ~p"/contacts?sort_params[station1]=asc") aa_pos = html |> :binary.match("AA1AA") |> elem(0) zz_pos = html |> :binary.match("ZZ9ZZ") |> elem(0) assert aa_pos < zz_pos end test "search by single callsign matches either station", %{conn: conn} do create_contact(%{station1: "W5LUA", station2: "W5HN"}) create_contact(%{station1: "K5TR", station2: "N5AC"}) {:ok, _lv, html} = live(conn, ~p"/contacts?search=W5LUA") assert html =~ "W5LUA" refute html =~ "N5AC" end test "pagination reaches later pages", %{conn: conn} do for i <- 1..25 do ts = DateTime.add(~U[2026-01-01 00:00:00Z], i * 3600, :second) create_contact(%{qso_timestamp: ts, station1: "W#{i}TEST"}) end {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "Page" {:ok, _lv, html2} = live(conn, ~p"/contacts?page=2") assert html2 =~ "Page" end test "renders a per-month bar chart that sums across years", %{conn: conn} do create_contact(%{qso_timestamp: ~U[2025-03-15 12:00:00Z], station1: "W5MAR"}) create_contact(%{qso_timestamp: ~U[2026-03-22 12:00:00Z], station1: "K5MAR"}) create_contact(%{qso_timestamp: ~U[2026-07-10 12:00:00Z], station1: "W5JUL"}) Microwaveprop.Cache.invalidate({MicrowavepropWeb.ContactLive.Index, :monthly_bars}) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "Contacts by month" assert html =~ ~s|data-month="3"| assert html =~ ~s|data-month-count="2"| assert html =~ ~s|data-month="7"| assert html =~ ~s|data-month-count="1"| assert html =~ ~s|data-month="1"| assert html =~ ~s|data-month-count="0"| end end describe "Index private visibility" do test "anonymous viewer does not see private contacts in the table", %{conn: conn} do _private = create_contact(%{station1: "W5PRV", private: true}) _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]}) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "W5PUB" refute html =~ "W5PRV" end test "owner sees their own private inline with Yes badge", %{conn: conn} do owner = user_fixture() _private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id}) _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]}) conn = log_in_user(conn, owner) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "W5PRV" assert html =~ "W5PUB" assert html =~ "badge-warning" end test "admin sees all private contacts", %{conn: conn} do admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!() _private = create_contact(%{station1: "W5PRV", private: true}) _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]}) conn = log_in_user(conn, admin) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "W5PRV" assert html =~ "W5PUB" end test "non-owning user does not see others' private", %{conn: conn} do owner = user_fixture() viewer = user_fixture() _private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id}) _public = create_contact(%{station1: "W5PUB", qso_timestamp: ~U[2026-03-28 19:00:00Z]}) conn = log_in_user(conn, viewer) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ "W5PUB" refute html =~ "W5PRV" end end describe "Index private column visibility" do test "column hidden for anonymous viewer when no private contacts exist", %{conn: conn} do _public = create_contact(%{station1: "W5PUB"}) {:ok, _lv, html} = live(conn, ~p"/contacts") refute html =~ ">Private<" end test "column hidden for anonymous viewer even when others have private contacts", %{conn: conn} do _private = create_contact(%{station1: "W5PRV", private: true}) _public = create_contact(%{station1: "W5PUB"}) {:ok, _lv, html} = live(conn, ~p"/contacts") refute html =~ ">Private<" end test "column hidden for a user who has no private contacts of their own", %{conn: conn} do owner = user_fixture() viewer = user_fixture() _others_private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id}) conn = log_in_user(conn, viewer) {:ok, _lv, html} = live(conn, ~p"/contacts") refute html =~ ">Private<" end test "column visible for a user with at least one of their own private contacts", %{conn: conn} do owner = user_fixture() _private = create_contact(%{station1: "W5PRV", private: true, user_id: owner.id}) conn = log_in_user(conn, owner) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ ">Private<" end test "column hidden for admin when there are no private contacts anywhere", %{conn: conn} do admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!() _public = create_contact(%{station1: "W5PUB"}) conn = log_in_user(conn, admin) {:ok, _lv, html} = live(conn, ~p"/contacts") refute html =~ ">Private<" end test "column visible for admin as soon as any private contact exists", %{conn: conn} do admin = user_fixture() |> Ecto.Changeset.change(is_admin: true) |> Repo.update!() _private = create_contact(%{station1: "W5PRV", private: true}) conn = log_in_user(conn, admin) {:ok, _lv, html} = live(conn, ~p"/contacts") assert html =~ ">Private<" end end end