From 66fb688b2accdcb72cb5e7db2034e1be91754d50 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Thu, 21 May 2026 07:49:36 +0100 Subject: [PATCH] Fix toasts not appearing for notes and barcode scan --- .../components/barcode_scanner.ex | 18 ++++-- lib/music_library_web/components/notes.ex | 10 ++-- .../live/collection_live/index_test.exs | 40 +++++++------ .../live/collection_live/show_test.exs | 56 ++++++++++++++----- 4 files changed, 80 insertions(+), 44 deletions(-) diff --git a/lib/music_library_web/components/barcode_scanner.ex b/lib/music_library_web/components/barcode_scanner.ex index 36405665..ae239059 100644 --- a/lib/music_library_web/components/barcode_scanner.ex +++ b/lib/music_library_web/components/barcode_scanner.ex @@ -468,11 +468,12 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do "Failed to search release for barcode #{number}: #{inspect(reason)}" end) - put_toast( - socket, + put_toast!( :error, gettext("Failed to search release for barcode %{number}", number: number) ) + + socket end {:noreply, socket} @@ -509,7 +510,8 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do socket |> maybe_toast_errors(sync_errors) - |> put_toast( + + put_toast!( :info, ngettext( "Importing %{count} record in the background...", @@ -518,10 +520,13 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do count: async_count ) ) + + socket else 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 -> maybe_toast_errors(socket, errors) @@ -544,11 +549,12 @@ defmodule MusicLibraryWeb.Components.BarcodeScanner do "#{number}: #{ErrorMessages.friendly_message(reason)}" end) - put_toast( - socket, + put_toast!( :error, gettext("Some records could not be imported: %{summary}", summary: errors_summary) ) + + socket end defp release_format_label(release) do diff --git a/lib/music_library_web/components/notes.ex b/lib/music_library_web/components/notes.ex index 41de8025..67701dfe 100644 --- a/lib/music_library_web/components/notes.ex +++ b/lib/music_library_web/components/notes.ex @@ -117,14 +117,15 @@ defmodule MusicLibraryWeb.Components.Notes do defp update_note(note_params, socket) do case Notes.update_note(socket.assigns.note, note_params) do {:ok, note} -> + put_toast!(:info, gettext("Note updated successfully")) + changeset = Notes.change_note(note, %{}) {:noreply, socket |> assign(:note, note) - |> assign(:form, to_form(changeset)) - |> put_toast(:info, gettext("Note updated successfully"))} + |> assign(:form, to_form(changeset))} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, form: to_form(changeset))} @@ -134,14 +135,15 @@ defmodule MusicLibraryWeb.Components.Notes do defp create_note(note_params, socket) do case Notes.create_note(socket.assigns.note, note_params) do {:ok, note} -> + put_toast!(:info, gettext("Note created successfully")) + changeset = Notes.change_note(note, %{}) {:noreply, socket |> assign(:note, note) - |> assign(:form, to_form(changeset)) - |> put_toast(:info, gettext("Note created successfully"))} + |> assign(:form, to_form(changeset))} {:error, %Ecto.Changeset{} = changeset} -> {:noreply, assign(socket, form: to_form(changeset))} diff --git a/test/music_library_web/live/collection_live/index_test.exs b/test/music_library_web/live/collection_live/index_test.exs index 1b0a8678..6bff8239 100644 --- a/test/music_library_web/live/collection_live/index_test.exs +++ b/test/music_library_web/live/collection_live/index_test.exs @@ -571,11 +571,13 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do end) # 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 |> visit(~p"/collection/scan") |> trigger_hook("#barcode-scanner", "barcode_scanned", %{"number" => barcode}) |> assert_has("#cart-empty") + |> assert_has("#toast-group", text: "Failed to search release for barcode 123") end 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") 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" barcode2 = "1234567890123" releases = releases(:marbles) - # Ensure clean MusicBrainz stub state - # The barcode search query includes "barcode:NUMBER AND NOT format:digitalmedia" + # Stub MusicBrainz for both barcode scans Test.stub(MusicBrainz.API, fn conn -> query = conn.params["query"] || "" @@ -663,26 +664,23 @@ defmodule MusicLibraryWeb.CollectionLive.IndexTest do end end) - # Scan both barcodes to get scan results - {:ok, result1} = MusicLibrary.BarcodeScan.scan(barcode1) - {:ok, result2} = MusicLibrary.BarcodeScan.scan(barcode2) + # Trigger both barcode scans through the UI hook + session = + 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) - assert MusicLibrary.BarcodeScan.should_import_async?(results) + # Click the "Add 2 releases" button (the button is in the cart sidebar) + session + |> click_button("Add 2 releases") - # Import async should succeed and enqueue jobs - current_time = DateTime.utc_now() - - {:ok, [], async_count} = - MusicLibrary.BarcodeScan.import_results_async(results, current_time) - - assert async_count == 2 - - # Verify jobs were enqueued - assert_enqueued(worker: ImportFromMusicbrainzRelease) - assert_enqueued(worker: ImportFromMusicbrainzRelease) + # Verify exactly two distinct import jobs were enqueued + enqueued = all_enqueued(worker: ImportFromMusicbrainzRelease) + assert length(enqueued) == 2 + assert Enum.all?(enqueued, & &1.args["release_id"]) # No records should have been inserted synchronously assert MusicLibrary.Repo.all(Record) == [] diff --git a/test/music_library_web/live/collection_live/show_test.exs b/test/music_library_web/live/collection_live/show_test.exs index edd19eb5..853c41d9 100644 --- a/test/music_library_web/live/collection_live/show_test.exs +++ b/test/music_library_web/live/collection_live/show_test.exs @@ -5,6 +5,8 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do only: [ render_click: 1, render_hook: 3, + render_submit: 1, + form: 3, element: 2 ] @@ -453,19 +455,25 @@ defmodule MusicLibraryWeb.CollectionLive.ShowTest do %{record: record} 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 assert Notes.get_note(:record, record.musicbrainz_id) == nil - {:ok, note} = - Notes.create_note( - %MusicLibrary.Notes.Note{entity: :record, musicbrainz_id: record.musicbrainz_id}, - %{"content" => "My test note content"} - ) + session = + conn + |> visit(~p"/collection/#{record.id}") + |> render_async() - assert note.content == "My test note content" - assert note.entity == :record - assert note.musicbrainz_id == record.musicbrainz_id + # The Notes component starts in "edit" mode when no note exists. + # Submit the notes form (always in the DOM, even when the Fluxon sheet is hidden). + 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 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") 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 - {:ok, note} = + {:ok, _note} = Notes.create_note( %MusicLibrary.Notes.Note{entity: :record, musicbrainz_id: record.musicbrainz_id}, %{"content" => "Original content"} ) - {:ok, updated} = Notes.update_note(note, %{"content" => "Updated content"}) - assert updated.content == "Updated content" + session = + 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 fetched = Notes.get_note(:record, record.musicbrainz_id)