refactor: pattern-match InfoLive.Show path parsing + add tests

- parse_path_element_with_link splits its fat `if`/or-chain into three
  focused helpers: linkable_callsign?/1 (regex + alias check),
  path_alias?/1 (four String.starts_with guards), and
  classify_path_element/2 (link vs. text dispatched on the boolean).
- display_for_element/2 chooses the callsign form with/without the
  asterisk via two function heads instead of an inline `if`.

Adds 7 tests covering the parse_path_with_links/1 public API — plain
callsigns, heard-via asterisk preservation, non-linkable aliases
(WIDE/TRACE/RELAY/TCPIP), q-constructs, whitespace trimming, and mixed
paths.
This commit is contained in:
Graham McIntire 2026-04-23 14:23:11 -05:00
parent bcbaddc18b
commit 1d68868d27
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 70 additions and 16 deletions

View file

@ -599,24 +599,35 @@ defmodule AprsmeWeb.InfoLive.Show do
defp parse_path_element_with_link(element) do
element = String.trim(element)
# Check if it's a callsign (with or without SSID)
if Regex.match?(~r/^[A-Z0-9]{1,6}(-\d{1,2})?(\*)?$/, element) and
not String.starts_with?(element, "WIDE") and
not String.starts_with?(element, "TRACE") and
not String.starts_with?(element, "RELAY") and
not String.starts_with?(element, "TCPIP") and
not String.starts_with?(element, "q") do
# Remove asterisk if present (indicates the packet was digipeated through this station)
callsign = String.replace(element, "*", "")
display = if String.ends_with?(element, "*"), do: "#{callsign}*", else: callsign
{:link, callsign, display}
else
# Not a linkable callsign, just return as text
{:text, element, element}
end
classify_path_element(linkable_callsign?(element), element)
end
# Only real callsigns (not digipeater aliases or q-constructs) become links.
defp linkable_callsign?(element) do
Regex.match?(~r/^[A-Z0-9]{1,6}(-\d{1,2})?(\*)?$/, element) and
not path_alias?(element)
end
defp path_alias?(element) do
String.starts_with?(element, "WIDE") or
String.starts_with?(element, "TRACE") or
String.starts_with?(element, "RELAY") or
String.starts_with?(element, "TCPIP") or
String.starts_with?(element, "q")
end
# Strip the trailing `*` from the canonical callsign while preserving it
# in the display string so the UI still shows "was-heard-via" markers.
defp classify_path_element(true, element) do
callsign = String.replace(element, "*", "")
{:link, callsign, display_for_element(String.ends_with?(element, "*"), callsign)}
end
defp classify_path_element(false, element), do: {:text, element, element}
defp display_for_element(true, callsign), do: "#{callsign}*"
defp display_for_element(false, callsign), do: callsign
defp decode_path_element(element) do
element = String.trim(element)

View file

@ -99,4 +99,47 @@ defmodule AprsmeWeb.InfoLive.ShowHelpersTest do
assert d > 0
end
end
describe "parse_path_with_links/1" do
test "returns empty list for non-binary input" do
assert Show.parse_path_with_links(nil) == []
assert Show.parse_path_with_links("") == []
assert Show.parse_path_with_links(123) == []
end
test "splits on commas and links plain callsigns" do
result = Show.parse_path_with_links("K5ABC-1,W1XYZ")
assert [{:link, "K5ABC-1", "K5ABC-1"}, {:link, "W1XYZ", "W1XYZ"}] = result
end
test "preserves the heard-via asterisk in the display text" do
assert [{:link, "K5ABC", "K5ABC*"}] = Show.parse_path_with_links("K5ABC*")
end
test "does not link WIDE/TRACE/RELAY/TCPIP path aliases" do
assert [{:text, "WIDE1-1", "WIDE1-1"}] = Show.parse_path_with_links("WIDE1-1")
assert [{:text, "TRACE3-3", "TRACE3-3"}] = Show.parse_path_with_links("TRACE3-3")
assert [{:text, "RELAY", "RELAY"}] = Show.parse_path_with_links("RELAY")
assert [{:text, "TCPIP*", "TCPIP*"}] = Show.parse_path_with_links("TCPIP*")
end
test "does not link q-constructs" do
assert [{:text, "qAC", "qAC"}] = Show.parse_path_with_links("qAC")
assert [{:text, "qAR", "qAR"}] = Show.parse_path_with_links("qAR")
end
test "trims whitespace before linking" do
assert [{:link, "K5ABC", "K5ABC"}] = Show.parse_path_with_links(" K5ABC ")
end
test "mixes links and text in a single path" do
result = Show.parse_path_with_links("K5ABC*,WIDE1-1,W1XYZ")
assert [
{:link, "K5ABC", "K5ABC*"},
{:text, "WIDE1-1", "WIDE1-1"},
{:link, "W1XYZ", "W1XYZ"}
] = result
end
end
end