Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/live_debugger/live_views/channel_dashboard_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
_ ->
Expand Down
15 changes: 10 additions & 5 deletions lib/live_debugger/services/live_view_discovery_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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 """
Expand Down
247 changes: 247 additions & 0 deletions test/services/live_view_discovery_service_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions test/support/fakes.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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)

%{
Expand All @@ -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,
Expand Down