Skip to content

Commit 49ea4a8

Browse files
authored
Task: Add more tests for LiveViewDiscoveryService (#310)
* add debugged_lv_process test * add tests for lv_process/2 * refactor and test successor_lv_process/1 * add test for group_lv_processes/1 * add test for lv_processes/0
1 parent ca1d5d7 commit 49ea4a8

4 files changed

Lines changed: 263 additions & 9 deletions

File tree

lib/live_debugger/live_views/channel_dashboard_live.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule LiveDebugger.LiveViews.ChannelDashboardLive do
55

66
require Logger
77

8+
alias LiveDebugger.Structs.LvProcess
89
alias Phoenix.LiveView.JS
910
alias LiveDebugger.Utils.URL
1011
alias Phoenix.LiveView.AsyncResult
@@ -98,11 +99,10 @@ defmodule LiveDebugger.LiveViews.ChannelDashboardLive do
9899
@impl true
99100
def handle_async(:fetch_lv_process, {:ok, nil}, socket) do
100101
with %{debugged_module: module} when not is_nil(module) <- socket.assigns,
101-
[lv_process] <- LiveViewDiscoveryService.successor_lv_processes(module) do
102+
%LvProcess{socket_id: socket_id, transport_pid: transport_pid} <-
103+
LiveViewDiscoveryService.successor_lv_process(module) do
102104
socket
103-
|> push_navigate(
104-
to: Routes.channel_dashboard(lv_process.socket_id, lv_process.transport_pid)
105-
)
105+
|> push_navigate(to: Routes.channel_dashboard(socket_id, transport_pid))
106106
|> noreply()
107107
else
108108
_ ->

lib/live_debugger/services/live_view_discovery_service.ex

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,20 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryService do
4646
end
4747

4848
@doc """
49-
Finds potential successor LvProcess based on module when websocket connection breaks and new one is created.
50-
This is a common scenario when user recompiles code or refreshes the page
49+
Finds potential successors LvProcesses based on module when websocket connection breaks and new one is created.
50+
This is a common scenario when user recompiles code or refreshes the page.
51+
When more than one process is found, `nil` is returned.
5152
"""
52-
@spec successor_lv_processes(module :: module()) :: [LvProcess.t()]
53-
def successor_lv_processes(module) do
53+
@spec successor_lv_process(module :: module()) :: LvProcess.t() | nil
54+
def successor_lv_process(module) when is_atom(module) do
5455
lv_processes()
5556
|> Enum.filter(fn lv_process ->
56-
not lv_process.debugger? and lv_process.module == module
57+
lv_process.module == module
5758
end)
59+
|> case do
60+
[lv_process] -> lv_process
61+
_ -> nil
62+
end
5863
end
5964

6065
@doc """

test/services/live_view_discovery_service_test.exs

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do
5555
end
5656
end
5757

58+
test "debugger_lv_processes/0 returns only LiveDebugger LvProcesses" do
59+
live_debugger_pid = :c.pid(0, 0, 2)
60+
live_view_pid = :c.pid(0, 0, 1)
61+
62+
live_debugger_module = :"Elixir.LiveDebugger.SomLiveView"
63+
live_view_module = :"Elixir.SomeLiveView"
64+
65+
MockProcessService
66+
|> expect(:list, fn -> [live_debugger_pid, live_view_pid] end)
67+
|> expect(:initial_call, fn _ -> {live_debugger_module, :mount} end)
68+
|> expect(:initial_call, fn _ -> {live_view_module, :mount} end)
69+
|> expect(:state, fn ^live_debugger_pid ->
70+
{:ok, Fakes.state(root_pid: live_debugger_pid, module: live_debugger_module)}
71+
end)
72+
|> expect(:state, fn ^live_view_pid ->
73+
{:ok, Fakes.state(root_pid: live_view_pid, module: live_view_module)}
74+
end)
75+
76+
assert [
77+
%LvProcess{pid: ^live_debugger_pid}
78+
] = LiveViewDiscoveryService.debugger_lv_processes()
79+
end
80+
5881
describe "lv_process/1" do
5982
test "returns LvProcess based on socket_id" do
6083
searched_live_view_pid = :c.pid(0, 1, 0)
@@ -112,6 +135,230 @@ defmodule LiveDebugger.Services.LiveViewDiscoveryServiceTest do
112135
end
113136
end
114137

138+
describe "lv_process/2" do
139+
test "returns LvProcess based on socket_id and transport_pid" do
140+
searched_live_view_pid = :c.pid(0, 1, 0)
141+
searched_module = :"Elixir.SearchedLiveView"
142+
socket_id = "phx-GBsi_6M7paYhySQj"
143+
transport_pid = :c.pid(0, 7, 1)
144+
145+
live_view_pid_1 = :c.pid(0, 0, 1)
146+
live_view_pid_2 = :c.pid(0, 0, 2)
147+
other_module = :"Elixir.SomeLiveView"
148+
other_socket_id = "phx-other-socket"
149+
other_transport_pid = :c.pid(0, 7, 2)
150+
151+
MockProcessService
152+
|> expect(:list, fn -> [searched_live_view_pid, live_view_pid_1, live_view_pid_2] end)
153+
|> expect(:initial_call, fn _ -> {searched_module, :mount} end)
154+
|> expect(:initial_call, 2, fn _ -> {other_module, :mount} end)
155+
|> expect(:state, fn ^searched_live_view_pid ->
156+
{:ok,
157+
Fakes.state(
158+
root_pid: searched_live_view_pid,
159+
module: searched_module,
160+
socket_id: socket_id,
161+
transport_pid: transport_pid
162+
)}
163+
end)
164+
|> expect(:state, fn live_view_pid ->
165+
{:ok,
166+
Fakes.state(
167+
root_pid: live_view_pid,
168+
module: other_module,
169+
socket_id: other_socket_id,
170+
transport_pid: transport_pid
171+
)}
172+
end)
173+
|> expect(:state, fn live_view_pid ->
174+
{:ok,
175+
Fakes.state(
176+
root_pid: live_view_pid,
177+
module: other_module,
178+
socket_id: socket_id,
179+
transport_pid: other_transport_pid
180+
)}
181+
end)
182+
183+
assert %LvProcess{pid: ^searched_live_view_pid} =
184+
LiveViewDiscoveryService.lv_process(socket_id, transport_pid)
185+
end
186+
187+
test "returns nil if no LiveView process of given socket_id and transport_pid" do
188+
socket_id = "phx-GBsi_6M7paYhySQj"
189+
transport_pid = :c.pid(0, 7, 1)
190+
191+
live_view_pid_1 = :c.pid(0, 0, 1)
192+
live_view_pid_2 = :c.pid(0, 0, 2)
193+
194+
other_socket_id = "phx-other-socket"
195+
other_transport_pid = :c.pid(0, 7, 2)
196+
197+
MockProcessService
198+
|> expect(:list, fn -> [live_view_pid_1, live_view_pid_2] end)
199+
|> expect(:initial_call, 2, fn _ -> {:"Elixir.SomeLiveView", :mount} end)
200+
|> expect(:state, fn live_view_pid ->
201+
{:ok,
202+
Fakes.state(
203+
root_pid: live_view_pid,
204+
socket_id: other_socket_id,
205+
transport_pid: transport_pid
206+
)}
207+
end)
208+
|> expect(:state, fn live_view_pid ->
209+
{:ok,
210+
Fakes.state(
211+
root_pid: live_view_pid,
212+
socket_id: socket_id,
213+
transport_pid: other_transport_pid
214+
)}
215+
end)
216+
217+
assert nil ==
218+
LiveViewDiscoveryService.lv_process(socket_id, transport_pid)
219+
end
220+
end
221+
222+
describe "successor_lv_process/1" do
223+
test "returns successor LvProcesses of the given module" do
224+
successor_pid = :c.pid(0, 0, 1)
225+
live_view_pid = :c.pid(0, 1, 0)
226+
227+
successor_module = :"Elixir.SomeLiveView"
228+
other_module = :"Elixir.OtherLiveView"
229+
230+
MockProcessService
231+
|> expect(:list, fn -> [successor_pid, live_view_pid] end)
232+
|> expect(:initial_call, fn _ -> {successor_module, :mount} end)
233+
|> expect(:initial_call, fn _ -> {other_module, :mount} end)
234+
|> expect(:state, fn ^successor_pid ->
235+
{:ok, Fakes.state(root_pid: successor_pid, module: successor_module)}
236+
end)
237+
|> expect(:state, fn ^live_view_pid ->
238+
{:ok, Fakes.state(root_pid: live_view_pid, module: other_module)}
239+
end)
240+
241+
assert %LvProcess{pid: ^successor_pid} =
242+
LiveViewDiscoveryService.successor_lv_process(successor_module)
243+
end
244+
245+
test "returns nil if no LiveView process of given module" do
246+
live_view_pid = :c.pid(0, 0, 1)
247+
248+
successor_module = :"Elixir.SomeLiveView"
249+
other_module = :"Elixir.OtherLiveView"
250+
251+
MockProcessService
252+
|> expect(:list, fn -> [live_view_pid] end)
253+
|> expect(:initial_call, fn _ -> {other_module, :mount} end)
254+
|> expect(:state, fn ^live_view_pid ->
255+
{:ok, Fakes.state(root_pid: live_view_pid, module: other_module)}
256+
end)
257+
258+
assert nil == LiveViewDiscoveryService.successor_lv_process(successor_module)
259+
end
260+
261+
test "returns nil if more than one LiveViewProcess of given module found" do
262+
live_view_pid_1 = :c.pid(0, 0, 1)
263+
live_view_pid_2 = :c.pid(0, 0, 2)
264+
265+
successor_module = :"Elixir.SomeLiveView"
266+
267+
MockProcessService
268+
|> expect(:list, fn -> [live_view_pid_1, live_view_pid_2] end)
269+
|> expect(:initial_call, 2, fn _ -> {successor_module, :mount} end)
270+
|> expect(:state, 2, fn live_view_pid ->
271+
{:ok, Fakes.state(root_pid: live_view_pid, module: successor_module)}
272+
end)
273+
274+
assert nil == LiveViewDiscoveryService.successor_lv_process(successor_module)
275+
end
276+
end
277+
278+
test "group_lv_processes/1 groups LvProcesses into proper map" do
279+
pid_1 = :c.pid(0, 0, 1)
280+
pid_2 = :c.pid(0, 0, 2)
281+
282+
root_pid_1 = :c.pid(0, 1, 1)
283+
root_pid_2 = :c.pid(0, 1, 2)
284+
root_pid_3 = :c.pid(0, 1, 3)
285+
286+
transport_pid_1 = :c.pid(0, 7, 1)
287+
transport_pid_2 = :c.pid(0, 7, 2)
288+
289+
lv_process_1 = %LvProcess{
290+
pid: root_pid_1,
291+
root_pid: root_pid_1,
292+
transport_pid: transport_pid_1
293+
}
294+
295+
lv_process_2 = %LvProcess{
296+
pid: pid_1,
297+
root_pid: root_pid_1,
298+
transport_pid: transport_pid_1
299+
}
300+
301+
lv_process_3 = %LvProcess{
302+
pid: root_pid_2,
303+
root_pid: root_pid_2,
304+
transport_pid: transport_pid_2
305+
}
306+
307+
lv_process_4 = %LvProcess{
308+
pid: pid_2,
309+
root_pid: root_pid_2,
310+
transport_pid: transport_pid_2
311+
}
312+
313+
lv_process_5 = %LvProcess{
314+
pid: root_pid_3,
315+
root_pid: root_pid_3,
316+
transport_pid: transport_pid_2
317+
}
318+
319+
assert %{
320+
transport_pid_1 => %{
321+
lv_process_1 => [lv_process_2]
322+
},
323+
transport_pid_2 => %{
324+
lv_process_3 => [lv_process_4],
325+
lv_process_5 => []
326+
}
327+
} ==
328+
LiveViewDiscoveryService.group_lv_processes([
329+
lv_process_1,
330+
lv_process_2,
331+
lv_process_3,
332+
lv_process_4,
333+
lv_process_5
334+
])
335+
end
336+
337+
test "lv_processes/0 returns all LiveView processes" do
338+
live_view_pid_1 = :c.pid(0, 0, 1)
339+
live_view_pid_2 = :c.pid(0, 0, 2)
340+
non_live_view_pid = :c.pid(0, 0, 3)
341+
342+
module = :"Elixir.SomeLiveView"
343+
non_live_view_module = :"Elixir.SomeOtherModule"
344+
345+
MockProcessService
346+
|> expect(:list, fn -> [live_view_pid_1, live_view_pid_2, non_live_view_pid] end)
347+
|> expect(:initial_call, 2, fn _ -> {module, :mount} end)
348+
|> expect(:initial_call, fn _ -> {non_live_view_module, :some_initial_call} end)
349+
|> expect(:state, fn ^live_view_pid_1 ->
350+
{:ok, Fakes.state(root_pid: live_view_pid_1, module: module)}
351+
end)
352+
|> expect(:state, fn ^live_view_pid_2 ->
353+
{:ok, Fakes.state(root_pid: live_view_pid_2, module: module)}
354+
end)
355+
356+
assert [
357+
%LvProcess{pid: ^live_view_pid_1},
358+
%LvProcess{pid: ^live_view_pid_2}
359+
] = LiveViewDiscoveryService.lv_processes()
360+
end
361+
115362
describe "children_lv_processes/1" do
116363
test "returns children LvProcesses of the given pid" do
117364
parent_pid = :c.pid(0, 0, 1)

test/support/fakes.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ defmodule LiveDebugger.Fakes do
77
socket_id = Keyword.get(opts, :socket_id, "phx-GBsi_6M7paYhySQj")
88
root_pid = Keyword.get(opts, :root_pid, :c.pid(0, 0, 0))
99
parent_pid = Keyword.get(opts, :parent_pid, nil)
10+
transport_pid = Keyword.get(opts, :transport_pid, :c.pid(0, 7, 0))
1011
module = Keyword.get(opts, :module, LiveDebugger.LiveViews.Main)
1112

1213
%{
@@ -17,6 +18,7 @@ defmodule LiveDebugger.Fakes do
1718
parent_pid: parent_pid,
1819
root_pid: root_pid,
1920
router: LiveDebuggerDev.Router,
21+
transport_pid: transport_pid,
2022
assigns: %{
2123
assign: :value,
2224
counter: 0,

0 commit comments

Comments
 (0)