From 8911afad9468df24d161c3b1c1895dd57dafbac7 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Thu, 23 Apr 2026 16:41:44 +0100 Subject: [PATCH] Add tests for Top Albums component --- .../live/stats_live/top_albums_test.exs | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 test/music_library_web/live/stats_live/top_albums_test.exs diff --git a/test/music_library_web/live/stats_live/top_albums_test.exs b/test/music_library_web/live/stats_live/top_albums_test.exs new file mode 100644 index 00000000..da29209c --- /dev/null +++ b/test/music_library_web/live/stats_live/top_albums_test.exs @@ -0,0 +1,208 @@ +defmodule MusicLibraryWeb.StatsLive.TopAlbumsTest do + use MusicLibraryWeb.ConnCase + + import MusicLibrary.Fixtures.Records + import MusicLibrary.ScrobbledTracksFixtures + import Phoenix.LiveViewTest + + # Any release_id from the marbles release-group fixture. Records created by + # `record/0` include `musicbrainz_data: ReleaseGroup.release_group(:marbles)`, + # which expands `release_ids` to include this ID, so scrobbles referencing it + # will be matched against those records. + @marbles_release_id "d3f9b9e2-73f5-4b47-a2a7-2c2199aad608" + @marillion_mbid "3acc72b3-4183-4d64-9b88-c0cf50d43ad3" + + defp two_marbles_scrobbles(_) do + now = System.system_time(:second) + + track_fixture(%{ + title: "The Invisible Man", + album_title: "Marbles", + album_musicbrainz_id: @marbles_release_id, + artist_name: "Marillion", + artist_musicbrainz_id: @marillion_mbid, + scrobbled_at_uts: now - 100 + }) + + track_fixture(%{ + title: "You're Gone", + album_title: "Marbles", + album_musicbrainz_id: @marbles_release_id, + artist_name: "Marillion", + artist_musicbrainz_id: @marillion_mbid, + scrobbled_at_uts: now - 200 + }) + + :ok + end + + describe "section" do + test "renders with the 'Top Albums' heading", %{conn: conn} do + conn + |> visit("/") + |> assert_has("#top-albums h1", text: "Top Albums") + end + + test "renders no album rows when no scrobbles exist", %{conn: conn} do + conn + |> visit("/") + |> unwrap(&render_async/1) + |> refute_has("#top-albums img") + end + end + + describe "album row" do + setup :two_marbles_scrobbles + + test "shows the album title, linked artist, and play count", %{conn: conn} do + conn + |> visit("/") + |> unwrap(&render_async/1) + |> assert_has("#top-albums p", text: "Marbles") + |> assert_has(~s|#top-albums a[href="/artists/#{@marillion_mbid}"]|, text: "Marillion") + |> assert_has("#top-albums span", text: "2") + end + + test "uses the Last.fm cover URL when no matching record exists", %{conn: conn} do + conn + |> visit("/") + |> unwrap(&render_async/1) + |> assert_has(~s|#top-albums img[src="https://example.com/cover.jpg"]|) + end + + test "uses the /assets transform URL when a matching record provides cover_hash", + %{conn: conn} do + _record = record(%{purchased_at: DateTime.utc_now()}) + + {:ok, view, _html} = live(conn, ~p"/") + html = render_async(view) + + assert html =~ ~r|]+src="/assets/[^"]+"[^>]+alt="Marbles"| + refute html =~ ~s|src="https://example.com/cover.jpg"| + end + end + + describe "matching records badges and navigation" do + setup :two_marbles_scrobbles + + test "single collected record: success badge and navigation to collection show", %{conn: conn} do + collected = record(%{purchased_at: DateTime.utc_now()}) + + {:ok, view, _html} = live(conn, ~p"/") + html = render_async(view) + + # The whole row is clickable, navigating to the collection show route. + assert html =~ ~s|/collection/#{collected.id}| + assert html =~ "cursor-pointer" + # The default `surface` badge variant for `success` uses `border-success`. + assert html =~ "border-success" + refute html =~ "border-warning" + end + + test "single wishlisted record: warning badge and navigation to wishlist show", + %{conn: conn} do + wishlisted = record(%{purchased_at: nil}) + + {:ok, view, _html} = live(conn, ~p"/") + html = render_async(view) + + assert html =~ ~s|/wishlist/#{wishlisted.id}| + assert html =~ "cursor-pointer" + assert html =~ "border-warning" + refute html =~ "border-success" + end + + test "two records under one release group: renders a dropdown with a link per record", + %{conn: conn} do + shared_mbid = Ecto.UUID.generate() + + collected = + record(%{ + title: "Marbles CD", + format: :cd, + musicbrainz_id: shared_mbid, + purchased_at: DateTime.utc_now() + }) + + wishlisted = + record(%{ + title: "Marbles Vinyl", + format: :vinyl, + musicbrainz_id: shared_mbid, + purchased_at: nil + }) + + session = + conn + |> visit("/") + |> unwrap(&render_async/1) + + session + |> assert_has("##{"top-album-#{@marbles_release_id}"}") + |> assert_has(~s|a[href="/collection/#{collected.id}"]|) + |> assert_has(~s|a[href="/wishlist/#{wishlisted.id}"]|) + # The row itself is not navigable when multiple records match. + |> refute_has("#top-albums div.cursor-pointer") + end + + test "no matching record: renders a plain badge and row is not navigable", %{conn: _conn} do + # Replace the Marbles scrobbles with an orphan album that has no record. + # The base setup created Marbles scrobbles — add one that will outrank + # them in play_count so it appears first. + now = System.system_time(:second) + + for i <- 0..2 do + track_fixture(%{ + title: "Orphan Track #{i}", + album_title: "Orphan Album", + album_musicbrainz_id: Ecto.UUID.generate(), + artist_name: "Orphan Artist", + artist_musicbrainz_id: Ecto.UUID.generate(), + scrobbled_at_uts: now - 10 - i + }) + end + + {:ok, view, _html} = live(build_conn() |> init_test_session(%{logged_in: true}), ~p"/") + html = render_async(view) + + assert html =~ "Orphan Album" + # Plain badge is primary/surface — no success/warning color classes on + # the Orphan Album row. Since there's no single record match, the row + # has no cursor-pointer and no /collection/:id or /wishlist/:id link. + refute html =~ ~r|phx-click="[^"]*/collection/| + refute html =~ ~r|phx-click="[^"]*/wishlist/| + end + end + + describe "period switching" do + test "switching to 30-day period re-fetches with the new cutoff", %{conn: conn} do + now = System.system_time(:second) + older_uts = now - 10 * 86_400 + + track_fixture(%{ + title: "Older Track", + album_title: "Older Album", + album_musicbrainz_id: Ecto.UUID.generate(), + artist_name: "Older Artist", + artist_musicbrainz_id: Ecto.UUID.generate(), + scrobbled_at_uts: older_uts + }) + + session = + conn + |> visit("/") + |> unwrap(&render_async/1) + + refute_has(session, "#top-albums", text: "Older Album") + + session + |> within("#top-albums", fn s -> + s + |> click_button("30d") + |> unwrap(&render_async/1) + end) + + assert_has(session, "#top-albums", text: "Older Album") + end + end +end