diff --git a/lib/live_debugger/live_views/channel_dashboard_live.ex b/lib/live_debugger/live_views/channel_dashboard_live.ex index 93694fd5f..d4ec87916 100644 --- a/lib/live_debugger/live_views/channel_dashboard_live.ex +++ b/lib/live_debugger/live_views/channel_dashboard_live.ex @@ -5,6 +5,7 @@ defmodule LiveDebugger.LiveViews.ChannelDashboardLive do require Logger + alias LiveDebugger.Structs.LvProcess alias Phoenix.LiveView.JS alias LiveDebugger.Utils.URL alias Phoenix.LiveView.AsyncResult @@ -98,11 +99,10 @@ defmodule LiveDebugger.LiveViews.ChannelDashboardLive do @impl true def handle_async(:fetch_lv_process, {:ok, nil}, socket) do with %{debugged_module: module} when not is_nil(module) <- socket.assigns, - [lv_process] <- LiveViewDiscoveryService.successor_lv_processes(module) do + %LvProcess{socket_id: socket_id, transport_pid: transport_pid} <- + LiveViewDiscoveryService.successor_lv_process(module) do socket - |> push_navigate( - to: Routes.channel_dashboard(lv_process.socket_id, lv_process.transport_pid) - ) + |> push_navigate(to: Routes.channel_dashboard(socket_id, transport_pid)) |> noreply() else _ -> diff --git a/lib/live_debugger/services/live_view_discovery_service.ex b/lib/live_debugger/services/live_view_discovery_service.ex index c2e64a607..544adb198 100644 --- a/lib/live_debugger/services/live_view_discovery_service.ex +++ b/lib/live_debugger/services/live_view_discovery_service.ex @@ -46,15 +46,20 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryService do end @doc """ - Finds potential successor LvProcess based on module when websocket connection breaks and new one is created. - This is a common scenario when user recompiles code or refreshes the page + Finds potential successors LvProcesses based on module when websocket connection breaks and new one is created. + This is a common scenario when user recompiles code or refreshes the page. + When more than one process is found, `nil` is returned. """ - @spec successor_lv_processes(module :: module()) :: [LvProcess.t()] - def successor_lv_processes(module) do + @spec successor_lv_process(module :: module()) :: LvProcess.t() | nil + def successor_lv_process(module) when is_atom(module) do lv_processes() |> Enum.filter(fn lv_process -> - not lv_process.debugger? and lv_process.module == module + lv_process.module == module end) + |> case do + [lv_process] -> lv_process + _ -> nil + end end @doc """ diff --git a/test/services/live_view_discovery_service_test.exs b/test/services/live_view_discovery_service_test.exs index 5dcd1b9ea..0161ef9ed 100644 --- a/test/services/live_view_discovery_service_test.exs +++ b/test/services/live_view_discovery_service_test.exs @@ -55,6 +55,29 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do end end + test "debugger_lv_processes/0 returns only LiveDebugger LvProcesses" do + live_debugger_pid = :c.pid(0, 0, 2) + live_view_pid = :c.pid(0, 0, 1) + + live_debugger_module = :"Elixir.LiveDebugger.SomLiveView" + live_view_module = :"Elixir.SomeLiveView" + + MockProcessService + |> expect(:list, fn -> [live_debugger_pid, live_view_pid] end) + |> expect(:initial_call, fn _ -> {live_debugger_module, :mount} end) + |> expect(:initial_call, fn _ -> {live_view_module, :mount} end) + |> expect(:state, fn ^live_debugger_pid -> + {:ok, Fakes.state(root_pid: live_debugger_pid, module: live_debugger_module)} + end) + |> expect(:state, fn ^live_view_pid -> + {:ok, Fakes.state(root_pid: live_view_pid, module: live_view_module)} + end) + + assert [ + %LvProcess{pid: ^live_debugger_pid} + ] = LiveViewDiscoveryService.debugger_lv_processes() + end + describe "lv_process/1" do test "returns LvProcess based on socket_id" do searched_live_view_pid = :c.pid(0, 1, 0) @@ -112,6 +135,230 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do end end + describe "lv_process/2" do + test "returns LvProcess based on socket_id and transport_pid" do + searched_live_view_pid = :c.pid(0, 1, 0) + searched_module = :"Elixir.SearchedLiveView" + socket_id = "phx-GBsi_6M7paYhySQj" + transport_pid = :c.pid(0, 7, 1) + + live_view_pid_1 = :c.pid(0, 0, 1) + live_view_pid_2 = :c.pid(0, 0, 2) + other_module = :"Elixir.SomeLiveView" + other_socket_id = "phx-other-socket" + other_transport_pid = :c.pid(0, 7, 2) + + MockProcessService + |> expect(:list, fn -> [searched_live_view_pid, live_view_pid_1, live_view_pid_2] end) + |> expect(:initial_call, fn _ -> {searched_module, :mount} end) + |> expect(:initial_call, 2, fn _ -> {other_module, :mount} end) + |> expect(:state, fn ^searched_live_view_pid -> + {:ok, + Fakes.state( + root_pid: searched_live_view_pid, + module: searched_module, + socket_id: socket_id, + transport_pid: transport_pid + )} + end) + |> expect(:state, fn live_view_pid -> + {:ok, + Fakes.state( + root_pid: live_view_pid, + module: other_module, + socket_id: other_socket_id, + transport_pid: transport_pid + )} + end) + |> expect(:state, fn live_view_pid -> + {:ok, + Fakes.state( + root_pid: live_view_pid, + module: other_module, + socket_id: socket_id, + transport_pid: other_transport_pid + )} + end) + + assert %LvProcess{pid: ^searched_live_view_pid} = + LiveViewDiscoveryService.lv_process(socket_id, transport_pid) + end + + test "returns nil if no LiveView process of given socket_id and transport_pid" do + socket_id = "phx-GBsi_6M7paYhySQj" + transport_pid = :c.pid(0, 7, 1) + + live_view_pid_1 = :c.pid(0, 0, 1) + live_view_pid_2 = :c.pid(0, 0, 2) + + other_socket_id = "phx-other-socket" + other_transport_pid = :c.pid(0, 7, 2) + + MockProcessService + |> expect(:list, fn -> [live_view_pid_1, live_view_pid_2] end) + |> expect(:initial_call, 2, fn _ -> {:"Elixir.SomeLiveView", :mount} end) + |> expect(:state, fn live_view_pid -> + {:ok, + Fakes.state( + root_pid: live_view_pid, + socket_id: other_socket_id, + transport_pid: transport_pid + )} + end) + |> expect(:state, fn live_view_pid -> + {:ok, + Fakes.state( + root_pid: live_view_pid, + socket_id: socket_id, + transport_pid: other_transport_pid + )} + end) + + assert nil == + LiveViewDiscoveryService.lv_process(socket_id, transport_pid) + end + end + + describe "successor_lv_process/1" do + test "returns successor LvProcesses of the given module" do + successor_pid = :c.pid(0, 0, 1) + live_view_pid = :c.pid(0, 1, 0) + + successor_module = :"Elixir.SomeLiveView" + other_module = :"Elixir.OtherLiveView" + + MockProcessService + |> expect(:list, fn -> [successor_pid, live_view_pid] end) + |> expect(:initial_call, fn _ -> {successor_module, :mount} end) + |> expect(:initial_call, fn _ -> {other_module, :mount} end) + |> expect(:state, fn ^successor_pid -> + {:ok, Fakes.state(root_pid: successor_pid, module: successor_module)} + end) + |> expect(:state, fn ^live_view_pid -> + {:ok, Fakes.state(root_pid: live_view_pid, module: other_module)} + end) + + assert %LvProcess{pid: ^successor_pid} = + LiveViewDiscoveryService.successor_lv_process(successor_module) + end + + test "returns nil if no LiveView process of given module" do + live_view_pid = :c.pid(0, 0, 1) + + successor_module = :"Elixir.SomeLiveView" + other_module = :"Elixir.OtherLiveView" + + MockProcessService + |> expect(:list, fn -> [live_view_pid] end) + |> expect(:initial_call, fn _ -> {other_module, :mount} end) + |> expect(:state, fn ^live_view_pid -> + {:ok, Fakes.state(root_pid: live_view_pid, module: other_module)} + end) + + assert nil == LiveViewDiscoveryService.successor_lv_process(successor_module) + end + + test "returns nil if more than one LiveViewProcess of given module found" do + live_view_pid_1 = :c.pid(0, 0, 1) + live_view_pid_2 = :c.pid(0, 0, 2) + + successor_module = :"Elixir.SomeLiveView" + + MockProcessService + |> expect(:list, fn -> [live_view_pid_1, live_view_pid_2] end) + |> expect(:initial_call, 2, fn _ -> {successor_module, :mount} end) + |> expect(:state, 2, fn live_view_pid -> + {:ok, Fakes.state(root_pid: live_view_pid, module: successor_module)} + end) + + assert nil == LiveViewDiscoveryService.successor_lv_process(successor_module) + end + end + + test "group_lv_processes/1 groups LvProcesses into proper map" do + pid_1 = :c.pid(0, 0, 1) + pid_2 = :c.pid(0, 0, 2) + + root_pid_1 = :c.pid(0, 1, 1) + root_pid_2 = :c.pid(0, 1, 2) + root_pid_3 = :c.pid(0, 1, 3) + + transport_pid_1 = :c.pid(0, 7, 1) + transport_pid_2 = :c.pid(0, 7, 2) + + lv_process_1 = %LvProcess{ + pid: root_pid_1, + root_pid: root_pid_1, + transport_pid: transport_pid_1 + } + + lv_process_2 = %LvProcess{ + pid: pid_1, + root_pid: root_pid_1, + transport_pid: transport_pid_1 + } + + lv_process_3 = %LvProcess{ + pid: root_pid_2, + root_pid: root_pid_2, + transport_pid: transport_pid_2 + } + + lv_process_4 = %LvProcess{ + pid: pid_2, + root_pid: root_pid_2, + transport_pid: transport_pid_2 + } + + lv_process_5 = %LvProcess{ + pid: root_pid_3, + root_pid: root_pid_3, + transport_pid: transport_pid_2 + } + + assert %{ + transport_pid_1 => %{ + lv_process_1 => [lv_process_2] + }, + transport_pid_2 => %{ + lv_process_3 => [lv_process_4], + lv_process_5 => [] + } + } == + LiveViewDiscoveryService.group_lv_processes([ + lv_process_1, + lv_process_2, + lv_process_3, + lv_process_4, + lv_process_5 + ]) + end + + test "lv_processes/0 returns all LiveView processes" do + live_view_pid_1 = :c.pid(0, 0, 1) + live_view_pid_2 = :c.pid(0, 0, 2) + non_live_view_pid = :c.pid(0, 0, 3) + + module = :"Elixir.SomeLiveView" + non_live_view_module = :"Elixir.SomeOtherModule" + + MockProcessService + |> expect(:list, fn -> [live_view_pid_1, live_view_pid_2, non_live_view_pid] end) + |> expect(:initial_call, 2, fn _ -> {module, :mount} end) + |> expect(:initial_call, fn _ -> {non_live_view_module, :some_initial_call} end) + |> expect(:state, fn ^live_view_pid_1 -> + {:ok, Fakes.state(root_pid: live_view_pid_1, module: module)} + end) + |> expect(:state, fn ^live_view_pid_2 -> + {:ok, Fakes.state(root_pid: live_view_pid_2, module: module)} + end) + + assert [ + %LvProcess{pid: ^live_view_pid_1}, + %LvProcess{pid: ^live_view_pid_2} + ] = LiveViewDiscoveryService.lv_processes() + end + describe "children_lv_processes/1" do test "returns children LvProcesses of the given pid" do parent_pid = :c.pid(0, 0, 1) diff --git a/test/support/fakes.ex b/test/support/fakes.ex index af09422e3..9a48fe84e 100644 --- a/test/support/fakes.ex +++ b/test/support/fakes.ex @@ -7,6 +7,7 @@ defmodule LiveDebugger.Fakes do socket_id = Keyword.get(opts, :socket_id, "phx-GBsi_6M7paYhySQj") root_pid = Keyword.get(opts, :root_pid, :c.pid(0, 0, 0)) parent_pid = Keyword.get(opts, :parent_pid, nil) + transport_pid = Keyword.get(opts, :transport_pid, :c.pid(0, 7, 0)) module = Keyword.get(opts, :module, LiveDebugger.LiveViews.Main) %{ @@ -17,6 +18,7 @@ defmodule LiveDebugger.Fakes do parent_pid: parent_pid, root_pid: root_pid, router: LiveDebuggerDev.Router, + transport_pid: transport_pid, assigns: %{ assign: :value, counter: 0,