towerops/test/towerops_web/live/live_helpers_test.exs
2026-06-14 08:27:57 -05:00

349 lines
12 KiB
Elixir

defmodule ToweropsWeb.LiveHelpersTest do
@moduledoc """
Consolidated tests for pure helpers promoted from various LiveView modules.
"""
use ExUnit.Case, async: true
alias ToweropsWeb.ActivityFeedLive
alias ToweropsWeb.InsightsLive.Index
alias ToweropsWeb.MaintenanceLive.Form
alias ToweropsWeb.ReportsLive
alias ToweropsWeb.UserRegistrationLive
describe "OnboardingLive.next_step/1" do
test "advances through the onboarding sequence" do
assert :site == ToweropsWeb.OnboardingLive.next_step(:snmp)
assert :billing == ToweropsWeb.OnboardingLive.next_step(:site)
assert :agent == ToweropsWeb.OnboardingLive.next_step(:billing)
assert :done == ToweropsWeb.OnboardingLive.next_step(:agent)
end
end
describe "OnboardingLive.step_index/1" do
test "returns 0-indexed position" do
assert 0 == ToweropsWeb.OnboardingLive.step_index(:snmp)
assert 1 == ToweropsWeb.OnboardingLive.step_index(:site)
assert 2 == ToweropsWeb.OnboardingLive.step_index(:billing)
assert 3 == ToweropsWeb.OnboardingLive.step_index(:agent)
end
end
describe "InsightsLive.Index.urgency_classes/1" do
test "critical is red" do
assert String.contains?(Index.urgency_classes("critical"), "red")
end
test "warning is yellow" do
assert String.contains?(Index.urgency_classes("warning"), "yellow")
end
test "info is blue" do
assert String.contains?(Index.urgency_classes("info"), "blue")
end
test "unknown is gray" do
assert String.contains?(Index.urgency_classes("mystery"), "gray")
assert String.contains?(Index.urgency_classes(nil), "gray")
end
end
describe "InsightsLive.Index.source_classes/1" do
test "preseem is indigo" do
assert String.contains?(Index.source_classes("preseem"), "indigo")
end
test "gaiia is emerald" do
assert String.contains?(Index.source_classes("gaiia"), "emerald")
end
test "snmp is orange" do
assert String.contains?(Index.source_classes("snmp"), "orange")
end
test "system is gray" do
assert String.contains?(Index.source_classes("system"), "gray")
end
test "unknown is gray" do
assert String.contains?(Index.source_classes("x"), "gray")
end
end
describe "InsightsLive.Index.build_filter_params/2" do
test "merges overrides into base" do
base = %{"status" => "active"}
result = Index.build_filter_params(base, %{"urgency" => "critical"})
assert result == %{"status" => "active", "urgency" => "critical"}
end
test "drops nil values" do
base = %{"status" => "active"}
result = Index.build_filter_params(base, %{"urgency" => nil})
refute Map.has_key?(result, "urgency")
end
test "override wins over base" do
base = %{"status" => "active"}
result = Index.build_filter_params(base, %{"status" => "resolved"})
assert result == %{"status" => "resolved"}
end
end
describe "InsightsLive.Index selection helpers" do
test "selected?/2 returns true when id is in set" do
ids = MapSet.new(["a", "b"])
assert Index.selected?(ids, "a")
refute Index.selected?(ids, "c")
end
test "any_selected?/1 returns true for non-empty set" do
assert Index.any_selected?(MapSet.new(["a"]))
refute Index.any_selected?(MapSet.new())
end
end
describe "ActivityFeedLive.severity_dot_color/2" do
test "critical is red regardless of type" do
assert "bg-red-500" == ActivityFeedLive.severity_dot_color(:critical, :anything)
end
test "warning is yellow" do
assert "bg-yellow-500" == ActivityFeedLive.severity_dot_color(:warning, :anything)
end
test "info + alert_resolved is green" do
assert "bg-green-500" == ActivityFeedLive.severity_dot_color(:info, :alert_resolved)
end
test "info + sync is blue" do
assert "bg-blue-500" == ActivityFeedLive.severity_dot_color(:info, :sync)
end
test "info + device_added is cyan" do
assert "bg-cyan-500" == ActivityFeedLive.severity_dot_color(:info, :device_added)
end
test "info + unknown defaults to gray" do
assert "bg-gray-400" == ActivityFeedLive.severity_dot_color(:info, :other)
end
test "catch-all is gray" do
assert "bg-gray-400" == ActivityFeedLive.severity_dot_color(:unknown, :other)
end
end
describe "ActivityFeedLive.severity_text_color/2" do
test "critical is red text" do
result = ActivityFeedLive.severity_text_color(:critical, :alert_fired)
assert String.contains?(result, "red")
end
test "alert_resolved is green text" do
result = ActivityFeedLive.severity_text_color(:info, :alert_resolved)
assert String.contains?(result, "green")
end
test "fallback is default gray/white" do
result = ActivityFeedLive.severity_text_color(:info, :sync)
assert result =~ ~r/gray|white/
end
end
describe "ActivityFeedLive.type_badge_class/1" do
test "known types map to their colors" do
for {type, color} <- [
{:config_change, "orange"},
{:alert_fired, "red"},
{:alert_resolved, "green"},
{:device_event, "purple"},
{:sync, "blue"},
{:device_added, "cyan"}
] do
assert String.contains?(ActivityFeedLive.type_badge_class(type), color),
"expected #{type} badge to contain '#{color}'"
end
end
test "unknown defaults to gray" do
assert String.contains?(ActivityFeedLive.type_badge_class(:other), "gray")
end
end
describe "ActivityFeedLive.type_label/1" do
test "returns human labels" do
assert "Config" == ActivityFeedLive.type_label(:config_change)
assert "Alert" == ActivityFeedLive.type_label(:alert_fired)
assert "Resolved" == ActivityFeedLive.type_label(:alert_resolved)
assert "Event" == ActivityFeedLive.type_label(:device_event)
assert "Sync" == ActivityFeedLive.type_label(:sync)
assert "Device" == ActivityFeedLive.type_label(:device_added)
assert "Other" == ActivityFeedLive.type_label(:unknown)
end
end
describe "ActivityFeedLive.relative_time/2" do
setup do
now = ~U[2026-04-23 20:00:00Z]
%{now: now}
end
test "very recent = 'just now'", %{now: now} do
ts = DateTime.add(now, -2, :second)
assert "just now" == ActivityFeedLive.relative_time(ts, now)
end
test "seconds ago", %{now: now} do
ts = DateTime.add(now, -30, :second)
assert "30s ago" == ActivityFeedLive.relative_time(ts, now)
end
test "minutes ago", %{now: now} do
ts = DateTime.add(now, -600, :second)
assert "10m ago" == ActivityFeedLive.relative_time(ts, now)
end
test "hours ago", %{now: now} do
ts = DateTime.add(now, -3 * 3600, :second)
assert "3h ago" == ActivityFeedLive.relative_time(ts, now)
end
test "yesterday", %{now: now} do
ts = DateTime.add(now, -30 * 3600, :second)
assert "yesterday" == ActivityFeedLive.relative_time(ts, now)
end
test "days ago", %{now: now} do
ts = DateTime.add(now, -5 * 86_400, :second)
assert "5d ago" == ActivityFeedLive.relative_time(ts, now)
end
test "over a week shows absolute date", %{now: now} do
ts = DateTime.add(now, -30 * 86_400, :second)
result = ActivityFeedLive.relative_time(ts, now)
# Absolute format like "Mar 24, 2026"
assert result =~ ~r/\w+ \d+, \d{4}/
end
end
describe "MaintenanceLive.Form.maybe_clear_scope/2" do
test "org scope nils both site_id and device_id" do
result = Form.maybe_clear_scope(%{"site_id" => "s1", "device_id" => "d1"}, "org")
assert result["site_id"] == nil
assert result["device_id"] == nil
end
test "site scope nils only device_id" do
result = Form.maybe_clear_scope(%{"site_id" => "s1", "device_id" => "d1"}, "site")
assert result["site_id"] == "s1"
assert result["device_id"] == nil
end
test "device scope preserves both" do
result = Form.maybe_clear_scope(%{"site_id" => "s1", "device_id" => "d1"}, "device")
assert result["site_id"] == "s1"
assert result["device_id"] == "d1"
end
end
describe "MaintenanceLive.Form.format_datetime_local/1" do
test "nil returns empty string" do
assert "" == Form.format_datetime_local(nil)
end
test "non-DateTime returns empty string" do
assert "" == Form.format_datetime_local("not a datetime")
assert "" == Form.format_datetime_local(42)
end
test "DateTime formatted as HTML datetime-local" do
dt = ~U[2026-04-23 14:30:00Z]
assert "2026-04-23T14:30" == Form.format_datetime_local(dt)
end
end
describe "ReportsLive helpers" do
test "humanize_type/1 known types" do
assert "Uptime Summary" == ReportsLive.humanize_type("uptime_summary")
assert "Alert History" == ReportsLive.humanize_type("alert_history")
assert "Capacity Trends" == ReportsLive.humanize_type("capacity_trends")
assert "RF Link Health" == ReportsLive.humanize_type("rf_link_health")
end
test "humanize_type/1 passes through unknown" do
assert "custom" == ReportsLive.humanize_type("custom")
end
test "humanize_schedule/1 capitalizes type" do
assert "Weekly" == ReportsLive.humanize_schedule(%{"type" => "weekly"})
assert "Daily" == ReportsLive.humanize_schedule(%{"type" => "daily"})
end
test "humanize_schedule/1 returns dash for missing type" do
assert "-" == ReportsLive.humanize_schedule(%{})
assert "-" == ReportsLive.humanize_schedule(nil)
end
test "format_recipients/1 joins list with commas" do
assert "a@b.com, c@d.com" == ReportsLive.format_recipients(["a@b.com", "c@d.com"])
end
test "format_recipients/1 returns dash for non-list" do
assert "-" == ReportsLive.format_recipients(nil)
assert "-" == ReportsLive.format_recipients("string")
end
test "status_badge/1 known statuses" do
assert {"Success", class} = ReportsLive.status_badge("success")
assert String.contains?(class, "green")
assert {"Failed", class} = ReportsLive.status_badge("failed")
assert String.contains?(class, "red")
assert {"Partial", class} = ReportsLive.status_badge("partial_failure")
assert String.contains?(class, "yellow")
end
test "status_badge/1 unknown maps to 'Never Run'" do
assert {"Never Run", class} = ReportsLive.status_badge(nil)
assert String.contains?(class, "gray")
end
end
describe "UserRegistrationLive.normalize_consent_params/1" do
test "'on' becomes true for both fields" do
result =
UserRegistrationLive.normalize_consent_params(%{
"privacy_policy_consent" => "on",
"terms_of_service_consent" => "on"
})
assert result["privacy_policy_consent"] == true
assert result["terms_of_service_consent"] == true
end
test "preserves existing boolean values" do
result =
UserRegistrationLive.normalize_consent_params(%{
"privacy_policy_consent" => true,
"terms_of_service_consent" => false
})
assert result["privacy_policy_consent"] == true
assert result["terms_of_service_consent"] == false
end
test "missing fields remain nil" do
result = UserRegistrationLive.normalize_consent_params(%{})
assert result["privacy_policy_consent"] == nil
assert result["terms_of_service_consent"] == nil
end
test "other values pass through unchanged" do
result = UserRegistrationLive.normalize_consent_params(%{"privacy_policy_consent" => "nope"})
assert result["privacy_policy_consent"] == "nope"
end
end
end