ML-188: guard PubSub record updates during edit mode

Add live_action guard to handle_info({:update, record}) in both
CollectionLive.Show and WishlistLive.Show. When the user is editing,
background worker updates are skipped and a warning toast is shown
instead of overwriting the socket. Normal show-mode updates and
mismatched-ID no-ops are unchanged.

6 tests added covering all three code paths.
This commit is contained in:
Claudio Ortolina
2026-05-19 11:38:42 +01:00
parent 2fca0bb106
commit e204e555bd
9 changed files with 236 additions and 27 deletions
@@ -7,6 +7,7 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do
alias MusicBrainz.Fixtures
alias MusicLibrary.Assets.Transform
alias MusicLibrary.Records.Record
alias Phoenix.PubSub
describe "Edit record from show page" do
test "can navigate to the record edit form", %{conn: conn} do
@@ -68,6 +69,93 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do
end
end
describe "handle_info({:update, record}) with live_action guard" do
test "updates record when showing (live_action is :show)", %{conn: conn} do
record = record()
release_response = Fixtures.Release.release(:marbles)
Req.Test.stub(MusicBrainz.API, fn conn ->
Req.Test.json(conn, release_response)
end)
updated_record = %{record | title: "Background Updated Title"}
session =
conn
|> visit(~p"/collection/#{record.id}")
|> render_async()
PubSub.broadcast(
MusicLibrary.PubSub,
"records:#{record.id}",
{:update, updated_record}
)
session
|> assert_has("*", text: "Background Updated Title", timeout: 200)
|> assert_has("#toast-group", text: "Record updated in the background")
end
test "skips update when editing and shows warning toast", %{conn: conn} do
record = record()
release_response = Fixtures.Release.release(:marbles)
Req.Test.stub(MusicBrainz.API, fn conn ->
Req.Test.json(conn, release_response)
end)
updated_record = %{record | title: "Should Not Appear"}
session =
conn
|> visit(~p"/collection/#{record.id}")
|> render_async()
|> click_link("Edit")
PubSub.broadcast(
MusicLibrary.PubSub,
"records:#{record.id}",
{:update, updated_record}
)
session
|> refute_has("h2", text: "Should Not Appear")
|> assert_has(
"#toast-group",
text:
"Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
)
end
test "no-ops when broadcasted record has mismatched ID", %{conn: conn} do
record = record()
other_record = record()
release_response = Fixtures.Release.release(:marbles)
Req.Test.stub(MusicBrainz.API, fn conn ->
Req.Test.json(conn, release_response)
end)
session =
conn
|> visit(~p"/collection/#{record.id}")
|> render_async()
PubSub.broadcast(
MusicLibrary.PubSub,
"records:#{record.id}",
{:update, other_record}
)
session
|> assert_has("h2", text: escape(record.title))
|> refute_has("#toast-group", text: "Record updated in the background")
end
end
describe "Side panel" do
test "shows a record's tracks", %{conn: conn} do
record = record()
@@ -6,6 +6,7 @@ defmodule MusicLibraryWeb.WishlistLive.ShowTest do
alias MusicLibrary.Assets.Transform
alias MusicLibrary.Records.Record
alias Phoenix.PubSub
describe "Edit record from show page" do
test "can navigate to the record edit form", %{conn: conn} do
@@ -51,4 +52,68 @@ defmodule MusicLibraryWeb.WishlistLive.ShowTest do
end
end
end
describe "handle_info({:update, record}) with live_action guard" do
test "updates record when showing (live_action is :show)", %{conn: conn} do
record = record(purchased_at: nil)
updated_record = %{record | title: "Background Updated Title"}
session =
conn
|> visit(~p"/wishlist/#{record.id}")
PubSub.broadcast(
MusicLibrary.PubSub,
"records:#{record.id}",
{:update, updated_record}
)
session
|> assert_has("*", text: "Background Updated Title", timeout: 200)
|> assert_has("#toast-group", text: "Record updated in the background")
end
test "skips update when editing and shows warning toast", %{conn: conn} do
record = record(purchased_at: nil)
updated_record = %{record | title: "Should Not Appear"}
session =
conn
|> visit(~p"/wishlist/#{record.id}")
|> click_link("Edit")
PubSub.broadcast(
MusicLibrary.PubSub,
"records:#{record.id}",
{:update, updated_record}
)
session
|> refute_has("h2", text: "Should Not Appear")
|> assert_has(
"#toast-group",
text:
"Record was updated in the background. Your edits may be stale — save and re-open to see the latest data."
)
end
test "no-ops when broadcasted record has mismatched ID", %{conn: conn} do
record = record(purchased_at: nil)
other_record = record(purchased_at: nil)
session =
conn
|> visit(~p"/wishlist/#{record.id}")
PubSub.broadcast(
MusicLibrary.PubSub,
"records:#{record.id}",
{:update, other_record}
)
session
|> assert_has("h2", text: escape(record.title))
|> refute_has("#toast-group", text: "Record updated in the background")
end
end
end