defmodule AiDebug.LiveViewLoggerTest do
use ExUnit.Case, async: true
alias AiDebug.LiveViewLogger
# Test module that uses the logger
defmodule LoggedLiveView do
use Phoenix.LiveView
use AiDebug.LiveViewLogger, log_level: :debug
def mount(_params, _session, socket) do
{:ok, assign(socket, :data, "test")}
end
def handle_event("test_event", params, socket) do
{:noreply, assign(socket, :last_event, params)}
end
def handle_info(:test_message, socket) do
{:noreply, socket}
end
def render(assigns) do
~H"""
Data: <%= @data %>
"""
end
end
setup do
# Enable logger for tests
LiveViewLogger.enable()
on_exit(fn -> LiveViewLogger.disable() end)
:ok
end
describe "lifecycle logging" do
test "logs mount events" do
# Subscribe to logger events
LiveViewLogger.subscribe(LoggedLiveView)
# Create a test socket
socket = %Phoenix.LiveView.Socket{
id: "test-socket",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}}
}
# Mount the LiveView
{:ok, _socket} = LoggedLiveView.mount(%{}, %{}, socket)
# Should receive mount event
assert_receive {:liveview_event, :mount, LoggedLiveView, metadata}
assert metadata.socket_id == "test-socket"
assert_receive {:liveview_event, :mount_completed, LoggedLiveView, metadata}
assert is_integer(metadata.duration_us)
end
test "logs handle_event calls" do
LiveViewLogger.subscribe(LoggedLiveView)
socket = %Phoenix.LiveView.Socket{
id: "test-socket",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}, data: "test"}
}
# Call handle_event
{:noreply, _socket} = LoggedLiveView.handle_event("test_event", %{"value" => 42}, socket)
assert_receive {:liveview_event, :handle_event, LoggedLiveView, metadata}
assert metadata.event == "test_event"
assert metadata.params == %{"value" => 42}
assert_receive {:liveview_event, :handle_event_completed, LoggedLiveView, metadata}
assert metadata.event == "test_event"
assert metadata.reply? == false
end
test "logs handle_info calls" do
LiveViewLogger.subscribe(LoggedLiveView)
socket = %Phoenix.LiveView.Socket{
id: "test-socket",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}, data: "test"}
}
# Call handle_info
{:noreply, _socket} = LoggedLiveView.handle_info(:test_message, socket)
assert_receive {:liveview_event, :handle_info, LoggedLiveView, metadata}
assert metadata.message_type == :test_message
assert_receive {:liveview_event, :handle_info_completed, LoggedLiveView, metadata}
assert is_integer(metadata.duration_us)
end
end
describe "statistics tracking" do
test "tracks mount times" do
# Clear any existing stats
:persistent_term.put({LiveViewLogger, :stats, LoggedLiveView}, nil)
socket = %Phoenix.LiveView.Socket{
id: "test-socket",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}}
}
# Mount multiple times to generate stats
for _ <- 1..5 do
{:ok, _} = LoggedLiveView.mount(%{}, %{}, socket)
end
# Wait for async updates
Process.sleep(100)
stats = LiveViewLogger.get_stats(LoggedLiveView)
assert stats.events >= 10 # At least mount + mount_completed for each
assert stats.errors == 0
assert stats.avg_mount_time > 0
end
test "tracks event handling times" do
socket = %Phoenix.LiveView.Socket{
id: "test-socket",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}, data: "test"}
}
# Handle multiple events
for i <- 1..5 do
{:noreply, _} = LoggedLiveView.handle_event("test_event", %{"n" => i}, socket)
end
Process.sleep(100)
stats = LiveViewLogger.get_stats(LoggedLiveView)
assert stats.avg_event_time > 0
end
end
describe "global enable/disable" do
test "can be disabled globally" do
LiveViewLogger.disable()
assert LiveViewLogger.enabled?() == false
# Should not log when disabled
LiveViewLogger.subscribe_all()
socket = %Phoenix.LiveView.Socket{
id: "test-socket",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}}
}
{:ok, _} = LoggedLiveView.mount(%{}, %{}, socket)
# Should not receive any events
refute_receive {:liveview_event, _, _, _}, 100
end
end
describe "subscription" do
test "can subscribe to specific module" do
# Another test module
defmodule OtherLiveView do
use Phoenix.LiveView
use AiDebug.LiveViewLogger
def mount(_, _, socket), do: {:ok, socket}
def render(assigns), do: ~H"
Other
"
end
# Subscribe only to LoggedLiveView
LiveViewLogger.subscribe(LoggedLiveView)
socket1 = %Phoenix.LiveView.Socket{
id: "test-1",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}}
}
socket2 = %Phoenix.LiveView.Socket{
id: "test-2",
endpoint: AiDebugWeb.Endpoint,
view: OtherLiveView,
assigns: %{__changed__: %{}}
}
{:ok, _} = LoggedLiveView.mount(%{}, %{}, socket1)
{:ok, _} = OtherLiveView.mount(%{}, %{}, socket2)
# Should only receive events from LoggedLiveView
assert_receive {:liveview_event, :mount, LoggedLiveView, _}
refute_receive {:liveview_event, :mount, OtherLiveView, _}, 100
end
test "can subscribe to all modules" do
LiveViewLogger.subscribe_all()
socket = %Phoenix.LiveView.Socket{
id: "test-socket",
endpoint: AiDebugWeb.Endpoint,
view: LoggedLiveView,
assigns: %{__changed__: %{}}
}
{:ok, _} = LoggedLiveView.mount(%{}, %{}, socket)
# Should receive event with :all subscription
assert_receive {:liveview_event, :mount, LoggedLiveView, _}
end
end
end