Skip to content

Commit 64c709a

Browse files
authored
Enhancement: Assigns section polish (#946)
* Change temporary assigns fetching behaviour * Filter out temporary assigns from regular assigns * Fix * Exclude non essential assigns * Fix ids * Rewrite assigns e2e tests to playwright * CR suggestions * Fix tests * Reduce number of playwright workers on CI * Simplify * Refactor
1 parent 4ca3e77 commit 64c709a

12 files changed

Lines changed: 289 additions & 224 deletions

File tree

e2e/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default defineConfig({
1111
forbidOnly: !!process.env.CI,
1212
/* Retry on CI only */
1313
retries: process.env.CI ? 2 : 0,
14-
workers: process.env.CI ? 4 : undefined,
14+
workers: process.env.CI ? 2 : undefined,
1515
maxFailures: process.env.CI ? 5 : undefined,
1616
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
1717
reporter: 'html',

e2e/tests/assigns.spec.ts

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import {
2+
expect,
3+
findClearTracesButton,
4+
prepareDevDebuggerPairTest,
5+
Page,
6+
findSwitchTracingButton,
7+
} from './dev-dbg-test';
8+
9+
const test = prepareDevDebuggerPairTest();
10+
11+
const termEntry = (
12+
page: Page,
13+
containerId: string,
14+
key: string,
15+
value: string
16+
) =>
17+
page.locator(
18+
`xpath=//*[@id="${containerId}"]//*[contains(normalize-space(text()), "${key}:")]/../..//*[contains(normalize-space(text()), "${value}")]`
19+
);
20+
21+
const showButton = async (page: Page, selector: string) => {
22+
await page.evaluate((sel) => {
23+
const el = document.querySelector(sel);
24+
if (el) (el as HTMLElement).style.display = 'block';
25+
}, selector);
26+
};
27+
28+
const clickPinButton = async (page: Page, assignKey: string) => {
29+
const selector = `#all-assigns button[phx-click="pin-assign"][phx-value-key="${assignKey}"]`;
30+
await showButton(page, selector);
31+
await page.locator(selector).click();
32+
};
33+
34+
const clickUnpinButton = async (page: Page, assignKey: string) => {
35+
const selector = `#pinned-assigns button[phx-click="unpin-assign"][phx-value-key="${assignKey}"]`;
36+
await showButton(page, selector);
37+
await page.locator(selector).click();
38+
};
39+
40+
test('user can search assigns using the searchbar', async ({ dbgApp }) => {
41+
await expect(termEntry(dbgApp, 'all-assigns', 'counter', '0')).toBeVisible();
42+
43+
await expect(
44+
dbgApp.locator('#all-assigns pre').filter({ hasText: '"deep value"' })
45+
).not.toBeVisible();
46+
47+
await dbgApp.locator('#assigns-search-input').fill('deep value');
48+
await expect(
49+
dbgApp.locator('#all-assigns pre').filter({ hasText: '"deep value"' })
50+
).toBeVisible();
51+
52+
await dbgApp.reload();
53+
54+
await dbgApp.locator('button[aria-label="Icon expand"]').click();
55+
56+
await expect(
57+
dbgApp
58+
.locator('#all-assigns-fullscreen pre')
59+
.filter({ hasText: '"deep value"' })
60+
).not.toBeVisible();
61+
62+
await dbgApp.locator('#assigns-search-input-fullscreen').fill('deep value');
63+
await expect(
64+
dbgApp
65+
.locator('#all-assigns-fullscreen pre')
66+
.filter({ hasText: '"deep value"' })
67+
).toBeVisible();
68+
});
69+
70+
test('user can pin and unpin specific assigns', async ({ devApp, dbgApp }) => {
71+
await expect(dbgApp.locator('#pinned-assigns')).toContainText(
72+
'No pinned assigns'
73+
);
74+
75+
await clickPinButton(dbgApp, 'counter');
76+
await expect(
77+
termEntry(dbgApp, 'pinned-assigns', 'counter', '0')
78+
).toBeVisible();
79+
80+
await devApp.locator('#increment-button').click();
81+
await devApp.locator('#increment-button').click();
82+
await devApp.locator('#send-button').click();
83+
84+
await expect(termEntry(dbgApp, 'all-assigns', 'counter', '2')).toBeVisible();
85+
await expect(
86+
termEntry(dbgApp, 'pinned-assigns', 'counter', '2')
87+
).toBeVisible();
88+
89+
await clickUnpinButton(dbgApp, 'counter');
90+
await expect(dbgApp.locator('#pinned-assigns')).toContainText(
91+
'No pinned assigns'
92+
);
93+
});
94+
95+
test('user can see temporary assigns', async ({ devApp, dbgApp }) => {
96+
await expect(
97+
termEntry(dbgApp, 'temporary-assigns', 'message', 'nil')
98+
).toBeVisible();
99+
100+
await devApp.locator('#append-message').click();
101+
await expect(
102+
termEntry(dbgApp, 'temporary-assigns', 'message', '%{...}')
103+
).toBeVisible();
104+
105+
await devApp.locator('#increment-button').click();
106+
await expect(
107+
termEntry(dbgApp, 'temporary-assigns', 'message', '%{...}')
108+
).toBeVisible();
109+
110+
await devApp.goto('/nested');
111+
await dbgApp.getByRole('button', { name: 'Continue' }).click();
112+
113+
await expect(dbgApp.locator('#temporary-assigns')).toContainText(
114+
'No temporary assigns'
115+
);
116+
});
117+
118+
test('user can go through assigns change history', async ({
119+
devApp,
120+
dbgApp,
121+
}) => {
122+
await devApp.locator('#increment-button').click();
123+
await devApp.locator('#increment-button').click();
124+
await devApp.locator('#send-button').click();
125+
126+
await dbgApp
127+
.locator('#all-assigns button[phx-click="open-assigns-history"]')
128+
.click();
129+
130+
await expect(
131+
termEntry(dbgApp, 'history-old-assigns', 'counter', '2')
132+
).toBeVisible();
133+
await expect(
134+
termEntry(dbgApp, 'history-old-assigns', 'datetime', 'nil')
135+
).toBeVisible();
136+
await expect(
137+
termEntry(dbgApp, 'history-new-assigns', 'counter', '2')
138+
).toBeVisible();
139+
await expect(
140+
termEntry(dbgApp, 'history-new-assigns', 'datetime', '~U[')
141+
).toBeVisible();
142+
143+
await dbgApp.locator('button[phx-click="go-back"]').click();
144+
await expect(
145+
termEntry(dbgApp, 'history-old-assigns', 'counter', '1')
146+
).toBeVisible();
147+
await expect(
148+
termEntry(dbgApp, 'history-old-assigns', 'datetime', 'nil')
149+
).toBeVisible();
150+
await expect(
151+
termEntry(dbgApp, 'history-new-assigns', 'counter', '2')
152+
).toBeVisible();
153+
await expect(
154+
termEntry(dbgApp, 'history-new-assigns', 'datetime', 'nil')
155+
).toBeVisible();
156+
157+
await dbgApp.locator('button[phx-click="go-forward"]').click();
158+
await expect(
159+
termEntry(dbgApp, 'history-old-assigns', 'counter', '2')
160+
).toBeVisible();
161+
await expect(
162+
termEntry(dbgApp, 'history-old-assigns', 'datetime', 'nil')
163+
).toBeVisible();
164+
await expect(
165+
termEntry(dbgApp, 'history-new-assigns', 'counter', '2')
166+
).toBeVisible();
167+
await expect(
168+
termEntry(dbgApp, 'history-new-assigns', 'datetime', '~U[')
169+
).toBeVisible();
170+
171+
await dbgApp.locator('button[phx-click="go-back-end"]').click();
172+
await expect(
173+
termEntry(dbgApp, 'history-new-assigns', 'counter', '0')
174+
).toBeVisible();
175+
await expect(
176+
termEntry(dbgApp, 'history-new-assigns', 'datetime', 'nil')
177+
).toBeVisible();
178+
179+
await dbgApp.locator('button[phx-click="go-forward-end"]').click();
180+
await expect(
181+
termEntry(dbgApp, 'history-new-assigns', 'counter', '2')
182+
).toBeVisible();
183+
await expect(
184+
termEntry(dbgApp, 'history-new-assigns', 'datetime', '~U[')
185+
).toBeVisible();
186+
187+
await devApp.locator('#increment-button').click();
188+
await devApp.locator('#increment-button').click();
189+
190+
await expect(
191+
termEntry(dbgApp, 'history-old-assigns', 'counter', '3')
192+
).toBeVisible();
193+
await expect(
194+
termEntry(dbgApp, 'history-new-assigns', 'counter', '4')
195+
).toBeVisible();
196+
197+
await dbgApp.locator('#assigns-history-close').click();
198+
await findSwitchTracingButton(dbgApp).click();
199+
await findClearTracesButton(dbgApp).click();
200+
await dbgApp
201+
.locator('#all-assigns button[phx-click="open-assigns-history"]')
202+
.click();
203+
await expect(dbgApp.locator('#assigns-history')).toContainText(
204+
'No history records'
205+
);
206+
});

e2e/tests/dev-dbg-test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ export const findRefreshTracesButton = (page: Page) =>
4343
export const findFiltersButton = (page: Page) =>
4444
page.locator('button[aria-label="Open filters"]');
4545

46+
export const findNodeInspectorButton = (page: Page) =>
47+
page.locator('#node-inspector-navbar-item a');
48+
4649
export { expect, Page } from '@playwright/test';

e2e/tests/exception-trace.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
findClearTracesButton,
44
findSwitchTracingButton,
55
findFiltersButton,
6+
findNodeInspectorButton,
67
prepareDevDebuggerPairTest,
78
Page,
89
} from './dev-dbg-test';
@@ -15,9 +16,6 @@ const findTraces = (page: Page) =>
1516
const findGlobalCallbackTracesButton = (page: Page) =>
1617
page.locator('#global-traces-navbar-item a');
1718

18-
const findNodeInspectorButton = (page: Page) =>
19-
page.locator('#node-inspector-navbar-item a');
20-
2119
const findCrashModule = (page: Page) =>
2220
page.locator(
2321
`button[phx-value-module="Elixir.LiveDebuggerDev.LiveComponents.Crash"]`

e2e/tests/node-inspector.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ test('callback traces have proper execution times displayed', async ({
6868
const refreshTracesButton = findRefreshTracesButton(dbgApp);
6969
await refreshTracesButton.click();
7070

71-
await expect(traces).toHaveCount(0);
72-
7371
await dbgApp.waitForTimeout(405);
7472

7573
await refreshTracesButton.click();

lib/live_debugger/app/debugger/node_state/queries.ex

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ defmodule LiveDebugger.App.Debugger.NodeState.Queries do
1313
@type history_length :: non_neg_integer()
1414
@type history_entries :: {assigns1 :: map(), assigns2 :: map()} | {assigns :: map()}
1515

16+
@exclude_from_assigns [:__changed__]
17+
1618
@spec fetch_node_assigns(pid(), TreeNode.id()) :: {:ok, map()} | {:error, term()}
1719
def fetch_node_assigns(pid, node_id) when is_pid(node_id) do
1820
case fetch_node_state(pid) do
1921
{:ok, %LvState{socket: %{assigns: assigns}}} ->
20-
{:ok, assigns}
22+
exclude = fetch_temporary_assigns_keys(pid, node_id) ++ @exclude_from_assigns
23+
{:ok, Map.drop(assigns, exclude)}
2124

2225
{:error, reason} ->
2326
{:error, reason}
@@ -27,14 +30,21 @@ defmodule LiveDebugger.App.Debugger.NodeState.Queries do
2730
def fetch_node_assigns(pid, %Phoenix.LiveComponent.CID{} = cid) do
2831
case fetch_node_state(pid) do
2932
{:ok, %LvState{components: components}} ->
30-
get_component_assigns(components, cid)
33+
exclude = fetch_temporary_assigns_keys(pid, cid) ++ @exclude_from_assigns
34+
35+
components
36+
|> get_component_assigns(cid)
37+
|> case do
38+
{:ok, assigns} -> {:ok, Map.drop(assigns, exclude)}
39+
other -> other
40+
end
3141

3242
{:error, reason} ->
3343
{:error, reason}
3444
end
3545
end
3646

37-
def fetch_node_assigns(_, _) do
47+
def fetch_node_assigns(_, _, _) do
3848
{:error, "Invalid node ID"}
3949
end
4050

@@ -52,15 +62,16 @@ defmodule LiveDebugger.App.Debugger.NodeState.Queries do
5262
result =
5363
render_traces
5464
|> Enum.slice(index, 2)
55-
|> Enum.map(&(&1.args |> hd() |> Map.delete(:socket)))
65+
|> Enum.map(&(&1.args |> hd() |> Map.drop([:socket | @exclude_from_assigns])))
5666

5767
{:ok, {result, history_length}}
5868
end
5969
rescue
6070
error -> {:error, error}
6171
end
6272

63-
@spec fetch_node_temporary_assigns(pid(), TreeNode.id()) :: {:ok, map()} | {:error, term()}
73+
@spec fetch_node_temporary_assigns(pid(), TreeNode.id()) ::
74+
{:ok, map() | nil} | {:error, term()}
6475
def fetch_node_temporary_assigns(pid, node_id) do
6576
with {:ok, node_assigns} <- fetch_last_render_assigns(pid, node_id),
6677
%{temporary_assigns: temporary_assigns} <- node_assigns.socket.private do
@@ -102,4 +113,11 @@ defmodule LiveDebugger.App.Debugger.NodeState.Queries do
102113
{:ok, assigns}
103114
end
104115
end
116+
117+
defp fetch_temporary_assigns_keys(pid, node_id) do
118+
case fetch_node_temporary_assigns(pid, node_id) do
119+
{:ok, temp_assigns} when is_map(temp_assigns) -> Map.keys(temp_assigns)
120+
_ -> []
121+
end
122+
end
105123
end

lib/live_debugger/app/debugger/node_state/web/components.ex

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
114114

115115
attr(:name, :string, required: true)
116116
attr(:icon, :string, default: nil)
117+
117118
slot(:right)
118119

119120
defp section_title(assigns) do
@@ -199,6 +200,13 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
199200
icon="icon-clock-3"
200201
>
201202
<:right :if={@has_temporary_assigns}>
203+
<.tooltip
204+
id={@id <> "-tooltip"}
205+
content="Displayed temporary assigns values are the last recorded values for each assign"
206+
position="top-center"
207+
>
208+
<.icon name="icon-info" class="w-4 h-4 bg-button-secondary-content" />
209+
</.tooltip>
202210
<div class="flex">
203211
<.copy_button
204212
id={"temporary-assigns-copy-button" <> "-" <> @id}
@@ -247,18 +255,18 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.Components do
247255
defp all_assigns_section(assigns) do
248256
~H"""
249257
<div id={@id}>
250-
<.section_title name="All assigns">
258+
<.section_title name="Current assigns">
251259
<:right>
252-
<AssignsHistory.button />
260+
<AssignsHistory.button id={@id <> "-history"} />
253261
<div class="flex">
254262
<.copy_button
255-
id="assigns-copy-button"
263+
id={"assigns-copy-button" <> "-" <> @id}
256264
variant="icon-button"
257265
value={@copy_string}
258266
class="rounded-e-none! border-r-0!"
259267
/>
260268
<.copy_button
261-
id="json-assigns-copy-button"
269+
id={"json-assigns-copy-button" <> "-" <> @id}
262270
variant="button"
263271
text="JSON"
264272
value={@json_string}

lib/live_debugger/app/debugger/node_state/web/hook_components/assigns_history.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,13 @@ defmodule LiveDebugger.App.Debugger.NodeState.Web.HookComponents.AssignsHistory
8181
"""
8282
end
8383

84+
attr(:id, :string, default: nil)
85+
8486
def button(assigns) do
85-
assigns = assign(assigns, id: @assigns_history_id)
87+
assigns = assign(assigns, id: assigns.id || @assigns_history_id)
8688

8789
~H"""
88-
<.tooltip id="open-assigns-history-tooltip" content="Assigns history" position="top-center">
90+
<.tooltip id={@id <> "-tooltip"} content="Assigns history" position="top-center">
8991
<.icon_button
9092
id={"#{@id}-button"}
9193
phx-click="open-assigns-history"

0 commit comments

Comments
 (0)