towerops/test/towerops_web/live/user_settings_live/helpers_test.exs
Graham McIntire 872bef3512
feat: add 12/24-hour time format setting with consistent timezone display
wire time_format into scope, add settings UI selector, change defaults
to 24h, and replace all user-facing Calendar.strftime calls with
centralized TimeHelpers using the user's timezone and time format.
2026-02-13 15:16:29 -06:00

271 lines
10 KiB
Elixir

defmodule ToweropsWeb.UserSettingsLive.HelpersTest do
use ExUnit.Case, async: true
alias ToweropsWeb.UserSettingsLive.Helpers
describe "current_session?/2" do
test "returns true when session token matches current token" do
session = %{user_token_id: "abc123"}
assert Helpers.current_session?(session, "abc123")
end
test "returns false when session token does not match" do
session = %{user_token_id: "abc123"}
refute Helpers.current_session?(session, "different-token")
end
test "returns false when current token is nil" do
session = %{user_token_id: "abc123"}
refute Helpers.current_session?(session, nil)
end
test "returns true when both are nil" do
session = %{user_token_id: nil}
assert Helpers.current_session?(session, nil)
end
end
describe "format_timestamp_in_timezone/2" do
test "formats datetime in the given timezone" do
{:ok, datetime} = DateTime.new(~D[2025-03-15], ~T[14:30:00], "Etc/UTC")
result = Helpers.format_timestamp_in_timezone(datetime, "America/New_York")
assert result =~ "Mar 15, 2025"
assert result =~ "EDT" or result =~ "EST"
end
test "formats datetime in UTC with 24h default" do
{:ok, datetime} = DateTime.new(~D[2025-06-20], ~T[08:15:00], "Etc/UTC")
result = Helpers.format_timestamp_in_timezone(datetime, "Etc/UTC")
assert result =~ "Jun 20, 2025"
assert result =~ "08:15:00"
assert result =~ "UTC"
end
test "formats datetime in 12h format when specified" do
{:ok, datetime} = DateTime.new(~D[2025-06-20], ~T[08:15:00], "Etc/UTC")
result = Helpers.format_timestamp_in_timezone(datetime, "Etc/UTC", "12h")
assert result =~ "Jun 20, 2025"
assert result =~ "08:15 AM"
assert result =~ "UTC"
end
test "falls back to UTC for invalid timezone" do
{:ok, datetime} = DateTime.new(~D[2025-01-10], ~T[12:00:00], "Etc/UTC")
result = Helpers.format_timestamp_in_timezone(datetime, "Invalid/Zone")
assert result =~ "Jan 10, 2025"
assert result =~ "UTC"
end
test "formats PM times correctly in 24h format" do
{:ok, datetime} = DateTime.new(~D[2025-12-25], ~T[23:45:00], "Etc/UTC")
result = Helpers.format_timestamp_in_timezone(datetime, "Etc/UTC")
assert result =~ "Dec 25, 2025"
assert result =~ "23:45:00"
end
test "formats PM times correctly in 12h format" do
{:ok, datetime} = DateTime.new(~D[2025-12-25], ~T[23:45:00], "Etc/UTC")
result = Helpers.format_timestamp_in_timezone(datetime, "Etc/UTC", "12h")
assert result =~ "Dec 25, 2025"
assert result =~ "11:45 PM"
end
test "formats midnight correctly in 24h format" do
{:ok, datetime} = DateTime.new(~D[2025-07-04], ~T[00:00:00], "Etc/UTC")
result = Helpers.format_timestamp_in_timezone(datetime, "Etc/UTC")
assert result =~ "Jul 04, 2025"
assert result =~ "00:00:00"
end
end
describe "format_browser_info/1" do
test "returns device_name when browser_name is nil and device_name is set" do
session = %{browser_name: nil, browser_version: nil, os_name: nil, device_name: "iPhone 15"}
assert Helpers.format_browser_info(session) == "iPhone 15"
end
test "returns 'Unknown Browser' when browser_name and device_name are nil" do
session = %{browser_name: nil, browser_version: nil, os_name: nil, device_name: nil}
assert Helpers.format_browser_info(session) == "Unknown Browser"
end
test "returns browser name only when version and os are nil" do
session = %{browser_name: "Firefox", browser_version: nil, os_name: nil, device_name: nil}
assert Helpers.format_browser_info(session) == "Firefox"
end
test "returns browser with version when os is nil" do
session = %{browser_name: "Chrome", browser_version: "120", os_name: nil, device_name: nil}
assert Helpers.format_browser_info(session) == "Chrome 120"
end
test "returns browser on os when version is nil" do
session = %{browser_name: "Safari", browser_version: nil, os_name: "macOS", device_name: nil}
assert Helpers.format_browser_info(session) == "Safari on macOS"
end
test "returns browser version on os when all present" do
session = %{
browser_name: "Chrome",
browser_version: "120",
os_name: "Windows 11",
device_name: nil
}
assert Helpers.format_browser_info(session) == "Chrome 120 on Windows 11"
end
test "prioritizes device_name over os/version when browser_name is nil" do
session = %{
browser_name: nil,
browser_version: "1.0",
os_name: "iOS",
device_name: "iPad Pro"
}
assert Helpers.format_browser_info(session) == "iPad Pro"
end
end
describe "format_location/1" do
test "returns full location with city, state, and country" do
record = %{city_name: "Portland", subdivision_1_name: "Oregon", country_name: "United States"}
assert Helpers.format_location(record) == "Portland, Oregon, United States"
end
test "returns city and country when state is nil" do
record = %{city_name: "London", subdivision_1_name: nil, country_name: "United Kingdom"}
assert Helpers.format_location(record) == "London, United Kingdom"
end
test "returns country only when city and state are nil" do
record = %{city_name: nil, subdivision_1_name: nil, country_name: "Japan"}
assert Helpers.format_location(record) == "Japan"
end
test "returns 'Unknown Location' when all fields are nil" do
record = %{city_name: nil, subdivision_1_name: nil, country_name: nil}
assert Helpers.format_location(record) == "Unknown Location"
end
test "returns 'Unknown Location' for state only (no city or country)" do
record = %{city_name: nil, subdivision_1_name: "California", country_name: nil}
assert Helpers.format_location(record) == "Unknown Location"
end
test "returns 'Unknown Location' for city and state without country" do
record = %{city_name: "Portland", subdivision_1_name: "Oregon", country_name: nil}
assert Helpers.format_location(record) == "Unknown Location"
end
end
describe "format_relative_time/1" do
test "returns 'Just now' for times less than 60 seconds ago" do
datetime = DateTime.utc_now()
assert Helpers.format_relative_time(datetime) == "Just now"
end
test "returns 'Just now' for time 30 seconds ago" do
datetime = DateTime.add(DateTime.utc_now(), -30, :second)
assert Helpers.format_relative_time(datetime) == "Just now"
end
test "returns minutes ago for times between 60 seconds and 1 hour" do
datetime = DateTime.add(DateTime.utc_now(), -300, :second)
assert Helpers.format_relative_time(datetime) == "5 minutes ago"
end
test "returns 1 minutes ago for times just over 60 seconds" do
datetime = DateTime.add(DateTime.utc_now(), -90, :second)
assert Helpers.format_relative_time(datetime) == "1 minutes ago"
end
test "returns hours ago for times between 1 hour and 24 hours" do
datetime = DateTime.add(DateTime.utc_now(), -7200, :second)
assert Helpers.format_relative_time(datetime) == "2 hours ago"
end
test "returns days ago for times between 1 day and 7 days" do
datetime = DateTime.add(DateTime.utc_now(), -259_200, :second)
assert Helpers.format_relative_time(datetime) == "3 days ago"
end
test "returns formatted date for times older than 7 days" do
{:ok, datetime} = DateTime.new(~D[2024-01-15], ~T[10:00:00], "Etc/UTC")
result = Helpers.format_relative_time(datetime)
assert result =~ "Jan 15, 2024"
end
end
describe "login_method_info/1" do
test "returns lock icon and 'Password' for password method" do
assert Helpers.login_method_info("password") == {"hero-lock-closed", "Password"}
end
test "returns envelope icon and 'Magic Link' for magic_link method" do
assert Helpers.login_method_info("magic_link") == {"hero-envelope", "Magic Link"}
end
test "returns phone icon and 'Mobile QR' for mobile_qr method" do
assert Helpers.login_method_info("mobile_qr") == {"hero-device-phone-mobile", "Mobile QR"}
end
test "returns question mark icon and method string for unknown methods" do
assert Helpers.login_method_info("sso") == {"hero-question-mark-circle", "sso"}
end
test "returns the raw method string as label for unknown methods" do
assert Helpers.login_method_info("webauthn") == {"hero-question-mark-circle", "webauthn"}
end
end
describe "pagination_range/2" do
test "returns all pages when total is 7 or fewer" do
assert Helpers.pagination_range(1, 5) == [1, 2, 3, 4, 5]
assert Helpers.pagination_range(3, 7) == [1, 2, 3, 4, 5, 6, 7]
assert Helpers.pagination_range(1, 1) == [1]
end
test "returns start-biased range when current page is near the start" do
assert Helpers.pagination_range(1, 20) == [1, 2, 3, 4, 5, :ellipsis, 20]
assert Helpers.pagination_range(3, 20) == [1, 2, 3, 4, 5, :ellipsis, 20]
assert Helpers.pagination_range(4, 20) == [1, 2, 3, 4, 5, :ellipsis, 20]
end
test "returns end-biased range when current page is near the end" do
assert Helpers.pagination_range(20, 20) == [1, :ellipsis, 16, 17, 18, 19, 20]
assert Helpers.pagination_range(18, 20) == [1, :ellipsis, 16, 17, 18, 19, 20]
assert Helpers.pagination_range(17, 20) == [1, :ellipsis, 16, 17, 18, 19, 20]
end
test "returns middle range with ellipsis on both sides" do
assert Helpers.pagination_range(10, 20) == [1, :ellipsis, 9, 10, 11, :ellipsis, 20]
assert Helpers.pagination_range(5, 10) == [1, :ellipsis, 4, 5, 6, :ellipsis, 10]
end
test "handles boundary between start and middle" do
# Page 5 of 10 should be in the middle range
assert Helpers.pagination_range(5, 10) == [1, :ellipsis, 4, 5, 6, :ellipsis, 10]
end
test "handles boundary between middle and end" do
# Page 7 of 10 should be near the end
assert Helpers.pagination_range(7, 10) == [1, :ellipsis, 6, 7, 8, 9, 10]
end
test "handles 8 total pages with page 1 selected" do
assert Helpers.pagination_range(1, 8) == [1, 2, 3, 4, 5, :ellipsis, 8]
end
test "handles 8 total pages with page 8 selected" do
assert Helpers.pagination_range(8, 8) == [1, :ellipsis, 4, 5, 6, 7, 8]
end
end
end