Fix toasts not appearing for notes and barcode scan

This commit is contained in:
Claudio Ortolina
2026-05-21 07:49:36 +01:00
parent e46d890143
commit 66fb688b2a
4 changed files with 80 additions and 44 deletions
@@ -468,11 +468,12 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do
"Failed to search release for barcode #{number}: #{inspect(reason)}" "Failed to search release for barcode #{number}: #{inspect(reason)}"
end) end)
put_toast( put_toast!(
socket,
:error, :error,
gettext("Failed to search release for barcode %{number}", number: number) gettext("Failed to search release for barcode %{number}", number: number)
) )
socket
end end
{:noreply, socket} {:noreply, socket}
@@ -509,7 +510,8 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do
socket socket
|> maybe_toast_errors(sync_errors) |> maybe_toast_errors(sync_errors)
|> put_toast(
put_toast!(
:info, :info,
ngettext( ngettext(
"Importing %{count} record in the background...", "Importing %{count} record in the background...",
@@ -518,10 +520,13 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do
count: async_count count: async_count
) )
) )
socket
else else
case BarcodeScan.import_results(scan_results, current_time) do case BarcodeScan.import_results(scan_results, current_time) do
[] -> [] ->
put_toast(socket, :info, gettext("Records imported successfully")) put_toast!(:info, gettext("Records imported successfully"))
socket
errors -> errors ->
maybe_toast_errors(socket, errors) maybe_toast_errors(socket, errors)
@@ -544,11 +549,12 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do
"#{number}: #{ErrorMessages.friendly_message(reason)}" "#{number}: #{ErrorMessages.friendly_message(reason)}"
end) end)
put_toast( put_toast!(
socket,
:error, :error,
gettext("Some records could not be imported: %{summary}", summary: errors_summary) gettext("Some records could not be imported: %{summary}", summary: errors_summary)
) )
socket
end end
defp release_format_label(release) do defp release_format_label(release) do
+6 -4
View File
@@ -117,14 +117,15 @@ defmodule MusicLibraryWeb.Components.Notes do
defp update_note(note_params, socket) do defp update_note(note_params, socket) do
case Notes.update_note(socket.assigns.note, note_params) do case Notes.update_note(socket.assigns.note, note_params) do
{:ok, note} -> {:ok, note} ->
put_toast!(:info, gettext("Note updated successfully"))
changeset = changeset =
Notes.change_note(note, %{}) Notes.change_note(note, %{})
{:noreply, {:noreply,
socket socket
|> assign(:note, note) |> assign(:note, note)
|> assign(:form, to_form(changeset)) |> assign(:form, to_form(changeset))}
|> put_toast(:info, gettext("Note updated successfully"))}
{:error, %Ecto.Changeset{} = changeset} -> {:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))} {:noreply, assign(socket, form: to_form(changeset))}
@@ -134,14 +135,15 @@ defmodule MusicLibraryWeb.Components.Notes do
defp create_note(note_params, socket) do defp create_note(note_params, socket) do
case Notes.create_note(socket.assigns.note, note_params) do case Notes.create_note(socket.assigns.note, note_params) do
{:ok, note} -> {:ok, note} ->
put_toast!(:info, gettext("Note created successfully"))
changeset = changeset =
Notes.change_note(note, %{}) Notes.change_note(note, %{})
{:noreply, {:noreply,
socket socket
|> assign(:note, note) |> assign(:note, note)
|> assign(:form, to_form(changeset)) |> assign(:form, to_form(changeset))}
|> put_toast(:info, gettext("Note created successfully"))}
{:error, %Ecto.Changeset{} = changeset} -> {:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))} {:noreply, assign(socket, form: to_form(changeset))}
@@ -571,11 +571,13 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
end) end)
# With a transport_error stub, the scan should fail gracefully. # With a transport_error stub, the scan should fail gracefully.
# The cart should remain empty (no results added). # The cart should remain empty (no results added) and an error
# toast should be displayed.
conn conn
|> visit(~p"/collection/scan") |> visit(~p"/collection/scan")
|> trigger_hook("#barcode-scanner", "barcode_scanned", %{"number" => barcode}) |> trigger_hook("#barcode-scanner", "barcode_scanned", %{"number" => barcode})
|> assert_has("#cart-empty") |> assert_has("#cart-empty")
|> assert_has("#toast-group", text: "Failed to search release for barcode 123")
end end
test "removes one scanned result from the cart", %{conn: conn} do test "removes one scanned result from the cart", %{conn: conn} do
@@ -646,13 +648,12 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
assert_has(session, "#cart-empty") assert_has(session, "#cart-empty")
end end
test "enqueues import jobs for 2+ new releases via context function" do test "enqueues import jobs for 2+ new releases via UI", %{conn: conn} do
barcode1 = "5037300650128" barcode1 = "5037300650128"
barcode2 = "1234567890123" barcode2 = "1234567890123"
releases = releases(:marbles) releases = releases(:marbles)
# Ensure clean MusicBrainz stub state # Stub MusicBrainz for both barcode scans
# The barcode search query includes "barcode:NUMBER AND NOT format:digitalmedia"
Test.stub(MusicBrainz.API, fn conn -> Test.stub(MusicBrainz.API, fn conn ->
query = conn.params["query"] || "" query = conn.params["query"] || ""
@@ -663,26 +664,23 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do
end end
end) end)
# Scan both barcodes to get scan results # Trigger both barcode scans through the UI hook
{:ok, result1} = MusicLibrary.BarcodeScan.scan(barcode1) session =
{:ok, result2} = MusicLibrary.BarcodeScan.scan(barcode2) conn
|> visit(~p"/collection/scan")
|> trigger_hook("#barcode-scanner", "barcode_scanned", %{"number" => barcode1})
|> trigger_hook("#barcode-scanner", "barcode_scanned", %{"number" => barcode2})
results = [result1, result2] assert_has(session, "#cart-items li", count: 2)
# Both should be new results (no existing records) # Click the "Add 2 releases" button (the button is in the cart sidebar)
assert MusicLibrary.BarcodeScan.should_import_async?(results) session
|> click_button("Add 2 releases")
# Import async should succeed and enqueue jobs # Verify exactly two distinct import jobs were enqueued
current_time = DateTime.utc_now() enqueued = all_enqueued(worker: ImportFromMusicbrainzRelease)
assert length(enqueued) == 2
{:ok, [], async_count} = assert Enum.all?(enqueued, & &1.args["release_id"])
MusicLibrary.BarcodeScan.import_results_async(results, current_time)
assert async_count == 2
# Verify jobs were enqueued
assert_enqueued(worker: ImportFromMusicbrainzRelease)
assert_enqueued(worker: ImportFromMusicbrainzRelease)
# No records should have been inserted synchronously # No records should have been inserted synchronously
assert MusicLibrary.Repo.all(Record) == [] assert MusicLibrary.Repo.all(Record) == []
@@ -5,6 +5,8 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do
only: [ only: [
render_click: 1, render_click: 1,
render_hook: 3, render_hook: 3,
render_submit: 1,
form: 3,
element: 2 element: 2
] ]
@@ -453,19 +455,25 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do
%{record: record} %{record: record}
end end
test "creates a new note through the Notes context", %{record: record} do test "creates a new note through the Notes component form", %{conn: conn, record: record} do
# No note exists yet # No note exists yet
assert Notes.get_note(:record, record.musicbrainz_id) == nil assert Notes.get_note(:record, record.musicbrainz_id) == nil
{:ok, note} = session =
Notes.create_note( conn
%MusicLibrary.Notes.Note{entity: :record, musicbrainz_id: record.musicbrainz_id}, |> visit(~p"/collection/#{record.id}")
%{"content" => "My test note content"} |> render_async()
)
assert note.content == "My test note content" # The Notes component starts in "edit" mode when no note exists.
assert note.entity == :record # Submit the notes form (always in the DOM, even when the Fluxon sheet is hidden).
assert note.musicbrainz_id == record.musicbrainz_id session =
unwrap(session, fn view ->
view
|> form("#notes-form", %{"note" => %{"content" => "My test note content"}})
|> render_submit()
end)
assert_has(session, "#toast-group", text: "Note created successfully")
# Verify persistence # Verify persistence
fetched = Notes.get_note(:record, record.musicbrainz_id) fetched = Notes.get_note(:record, record.musicbrainz_id)
@@ -489,16 +497,38 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do
assert_has(session, "#read-panel article", text: "Existing note content") assert_has(session, "#read-panel article", text: "Existing note content")
end end
test "updates existing note content through the Notes context", %{record: record} do test "updates existing note content through the Notes component form", %{
conn: conn,
record: record
} do
# Pre-create a note # Pre-create a note
{:ok, note} = {:ok, _note} =
Notes.create_note( Notes.create_note(
%MusicLibrary.Notes.Note{entity: :record, musicbrainz_id: record.musicbrainz_id}, %MusicLibrary.Notes.Note{entity: :record, musicbrainz_id: record.musicbrainz_id},
%{"content" => "Original content"} %{"content" => "Original content"}
) )
{:ok, updated} = Notes.update_note(note, %{"content" => "Updated content"}) session =
assert updated.content == "Updated content" conn
|> visit(~p"/collection/#{record.id}")
|> render_async()
# Switch to "edit" tab inside the Notes component
session =
unwrap(session, fn view ->
view
|> element("button[aria-controls='edit-panel']")
|> render_click()
end)
# Submit updated content through the form
session
|> unwrap(fn view ->
view
|> form("#notes-form", %{"note" => %{"content" => "Updated content"}})
|> render_submit()
end)
|> assert_has("#toast-group", text: "Note updated successfully")
# Verify persistence # Verify persistence
fetched = Notes.get_note(:record, record.musicbrainz_id) fetched = Notes.get_note(:record, record.musicbrainz_id)