ML-169.10: async delete actions with initial implementation
This commit is contained in:
@@ -13,6 +13,7 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
|
||||
alias MusicLibrary.Records.Record
|
||||
alias MusicLibrary.Worker.ImportFromMusicbrainzRelease
|
||||
alias MusicLibrary.Worker.ImportFromMusicbrainzReleaseGroup
|
||||
alias MusicLibraryWeb.CollectionLive.Index, as: CollectionIndex
|
||||
alias Req.Test
|
||||
|
||||
# make it a multiple of 4 for easier calculations
|
||||
@@ -137,6 +138,7 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
|
||||
|> assert_has("#records-#{record.id}")
|
||||
|> click_link("#records-#{record.id} a[data-confirm='Are you sure?']", "Delete")
|
||||
|> refute_has("#records-#{record.id}")
|
||||
|> render_async()
|
||||
|
||||
assert_raise Ecto.NoResultsError, fn ->
|
||||
Records.get_record!(record.id)
|
||||
@@ -775,4 +777,72 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
|
||||
payload = Transform.encode!(transform)
|
||||
~p"/assets/#{payload}"
|
||||
end
|
||||
|
||||
describe "handle_async delete_record" do
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
defp build_socket(conn) do
|
||||
{:ok, view, _html} = live(conn, ~p"/collection")
|
||||
state = :sys.get_state(view.pid)
|
||||
state.socket
|
||||
end
|
||||
|
||||
test "{:ok, {:ok, _}} is a no-op", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Success"})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
CollectionIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:ok, {:ok, record}},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:ok, {:error, _}} does not crash", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Error"})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
CollectionIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:ok, {:error, :not_found}},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:exit, _} does not crash", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Exit"})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
CollectionIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:exit, :killed},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:exit, _} uses generic message, never leaks reason", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Exit No Leak"})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
{:noreply, socket} =
|
||||
CollectionIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:exit, {:killed, "some stacktrace that should not leak"}},
|
||||
socket
|
||||
)
|
||||
|
||||
# Verify the toast contains the generic message, NOT the raw exit reason
|
||||
assert socket.assigns[:toasts_sync]
|
||||
[toast | _] = socket.assigns.toasts_sync
|
||||
refute inspect(toast) =~ "stacktrace"
|
||||
refute inspect(toast) =~ "killed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule MusicLibraryWeb.ScrobbledTracksLiveTest do
|
||||
import MusicLibrary.ScrobbledTracksFixtures
|
||||
|
||||
alias MusicLibrary.ListeningStats
|
||||
alias MusicLibraryWeb.ScrobbledTracksLive.Index, as: ScrobbledTracksIndex
|
||||
|
||||
@valid_track_attrs %{
|
||||
title: "Updated Track Title",
|
||||
@@ -95,6 +96,7 @@ defmodule MusicLibraryWeb.ScrobbledTracksLiveTest do
|
||||
|> assert_has("p", track.title)
|
||||
|> click_button("button[data-confirm='Are you sure?']", "Delete")
|
||||
|> refute_has("p", track.title)
|
||||
|> render_async()
|
||||
|
||||
assert_raise Ecto.NoResultsError, fn ->
|
||||
ListeningStats.get_track!(track.scrobbled_at_uts)
|
||||
@@ -154,4 +156,75 @@ defmodule MusicLibraryWeb.ScrobbledTracksLiveTest do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle_async delete_track" do
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
defp build_socket(conn) do
|
||||
{:ok, view, _html} = live(conn, ~p"/scrobbled-tracks")
|
||||
state = :sys.get_state(view.pid)
|
||||
state.socket
|
||||
end
|
||||
|
||||
test "{:ok, {{:ok, _}, _}} is a no-op", %{conn: conn} do
|
||||
track = track_fixture()
|
||||
element = %{track: track}
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :tracks, element)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
ScrobbledTracksIndex.handle_async(
|
||||
{:delete_track, track.scrobbled_at_uts},
|
||||
{:ok, {{:ok, track}, element}},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:ok, {{:error, _}, _}} does not crash", %{conn: conn} do
|
||||
track = track_fixture()
|
||||
element = %{track: track}
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :tracks, element)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
ScrobbledTracksIndex.handle_async(
|
||||
{:delete_track, track.scrobbled_at_uts},
|
||||
{:ok, {{:error, :not_found}, element}},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:exit, _} does not crash", %{conn: conn} do
|
||||
track = track_fixture()
|
||||
element = %{track: track}
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :tracks, element)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
ScrobbledTracksIndex.handle_async(
|
||||
{:delete_track, track.scrobbled_at_uts},
|
||||
{:exit, :killed},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:exit, _} uses generic message, never leaks reason", %{conn: conn} do
|
||||
track = track_fixture()
|
||||
element = %{track: track}
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :tracks, element)
|
||||
|
||||
{:noreply, socket} =
|
||||
ScrobbledTracksIndex.handle_async(
|
||||
{:delete_track, track.scrobbled_at_uts},
|
||||
{:exit, {:killed, "some stacktrace that should not leak"}},
|
||||
socket
|
||||
)
|
||||
|
||||
assert socket.assigns[:toasts_sync]
|
||||
[toast | _] = socket.assigns.toasts_sync
|
||||
refute inspect(toast) =~ "stacktrace"
|
||||
refute inspect(toast) =~ "killed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ defmodule MusicLibraryWeb.WishlistLive.IndexTest do
|
||||
alias MusicLibrary.Records
|
||||
alias MusicLibrary.Records.Record
|
||||
alias MusicLibrary.Worker.ImportFromMusicbrainzReleaseGroup
|
||||
alias MusicLibraryWeb.WishlistLive.Index, as: WishlistIndex
|
||||
alias Req.Test
|
||||
|
||||
defp fill_wishlist(_) do
|
||||
@@ -53,6 +54,7 @@ defmodule MusicLibraryWeb.WishlistLive.IndexTest do
|
||||
|> assert_has("#records-#{record.id}")
|
||||
|> click_link("#records-#{record.id} a[data-confirm='Are you sure?']", "Delete")
|
||||
|> refute_has("#records-#{record.id}")
|
||||
|> render_async()
|
||||
|
||||
assert_raise Ecto.NoResultsError, fn ->
|
||||
Records.get_record!(record.id)
|
||||
@@ -174,4 +176,71 @@ defmodule MusicLibraryWeb.WishlistLive.IndexTest do
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
describe "handle_async delete_record" do
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
defp build_socket(conn) do
|
||||
{:ok, view, _html} = live(conn, ~p"/wishlist")
|
||||
state = :sys.get_state(view.pid)
|
||||
state.socket
|
||||
end
|
||||
|
||||
test "{:ok, {:ok, _}} is a no-op", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Success", purchased_at: nil})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
WishlistIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:ok, {:ok, record}},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:ok, {:error, _}} does not crash", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Error", purchased_at: nil})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
WishlistIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:ok, {:error, :not_found}},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:exit, _} does not crash", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Exit", purchased_at: nil})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
assert {:noreply, _socket} =
|
||||
WishlistIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:exit, :killed},
|
||||
socket
|
||||
)
|
||||
end
|
||||
|
||||
test "{:exit, _} uses generic message, never leaks reason", %{conn: conn} do
|
||||
record = record(%{title: "HandleAsync Exit No Leak", purchased_at: nil})
|
||||
socket = build_socket(conn)
|
||||
socket = Phoenix.LiveView.stream_delete(socket, :records, record)
|
||||
|
||||
{:noreply, socket} =
|
||||
WishlistIndex.handle_async(
|
||||
{:delete_record, record.id},
|
||||
{:exit, {:killed, "some stacktrace that should not leak"}},
|
||||
socket
|
||||
)
|
||||
|
||||
assert socket.assigns[:toasts_sync]
|
||||
[toast | _] = socket.assigns.toasts_sync
|
||||
refute inspect(toast) =~ "stacktrace"
|
||||
refute inspect(toast) =~ "killed"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user