feat: Collection › Unmatched lists the library albums no MusicBrainz album matched, each matched by hand from a search or a link, or left off (M502 #5579)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m17s
release / go (push) Successful in 1m34s
release / integration (push) Failing after 5m34s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m44s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m17s
release / go (push) Successful in 1m34s
release / integration (push) Failing after 5m34s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m44s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped
Like Lidarr's Unmapped Files. A local band's album, which MusicBrainz will never have, is left off: never matched again and not counted. A re-tagged album is matched again on the next listing. The Collection's verdict now says when library albums need matching. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,8 +21,14 @@
|
||||
}
|
||||
const parts = [counts.wanted === 1 ? '1 album wanted' : `${counts.wanted} albums wanted`];
|
||||
if (counts.activity) parts.push(`${counts.activity} downloading`);
|
||||
// Library albums no MusicBrainz album matched wait on a person too (#5579).
|
||||
if (counts.unmatched) {
|
||||
const n = counts.unmatched === 1 ? '1 album in your library needs' : `${counts.unmatched} albums in your library need`;
|
||||
return parts.join(', ') + `. ${n} matching.`;
|
||||
}
|
||||
return parts.join(', ') + '. Nothing needs you.';
|
||||
});
|
||||
const needsYou = $derived(!!counts?.attention || !!counts?.unmatched);
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -36,10 +42,12 @@
|
||||
<a href="/admin/integrations" class="text-accent-fg hover:underline">Integrations</a>.
|
||||
</p>
|
||||
{#if verdict}
|
||||
<p class="text-sm {counts?.attention ? 'text-text-primary' : 'text-text-secondary'}" data-testid="collection-verdict">
|
||||
<p class="text-sm {needsYou ? 'text-text-primary' : 'text-text-secondary'}" data-testid="collection-verdict">
|
||||
{verdict}
|
||||
{#if counts?.attention}
|
||||
<a href="/collection/activity" class="ml-1 text-accent-fg hover:underline">See Activity</a>
|
||||
{:else if counts?.unmatched}
|
||||
<a href="/collection/unmatched" class="ml-1 text-accent-fg hover:underline">See Unmatched</a>
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
@@ -12,7 +12,7 @@ vi.mock('#lib/api/wanted.js', () => ({ createWantArtistsQuery: vi.fn() }));
|
||||
import WantedLayout from './+layout.svelte';
|
||||
import { createWantArtistsQuery } from '#lib/api/wanted.js';
|
||||
|
||||
const counts: WantCounts = { artists: 7, wanted: 5, missing: 1, held: 9, following: 2, activity: 3, attention: 0, blocked: 4 };
|
||||
const counts: WantCounts = { artists: 7, wanted: 5, missing: 1, held: 9, following: 2, activity: 3, attention: 0, blocked: 4, unmatched: 3 };
|
||||
const children = createRawSnippet(() => ({ render: () => `<p data-testid="tab-body">body</p>` }));
|
||||
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
@@ -25,7 +25,7 @@ function setup(c: WantCounts = counts, path = '/collection') {
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
counts: c,
|
||||
library: { listed_at: null, unlisted: 0, unmatched: [] }
|
||||
library: { listed_at: null, unlisted: 0 }
|
||||
};
|
||||
vi.mocked(createWantArtistsQuery).mockReturnValue(
|
||||
mockInfiniteQuery({ pages: [page] }) as unknown as ReturnType<typeof createWantArtistsQuery>
|
||||
@@ -45,7 +45,8 @@ describe('/collection layout', () => {
|
||||
['/collection/missing', 'Missing files 1'],
|
||||
['/collection/activity', 'Activity 3'],
|
||||
['/collection/following', 'Following 2'],
|
||||
['/collection/blocked', 'Blocked 4']
|
||||
['/collection/blocked', 'Blocked 4'],
|
||||
['/collection/unmatched', 'Unmatched 3']
|
||||
]);
|
||||
expect(screen.getByRole('link', { name: /activity/i })).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByRole('link', { name: /^catalogue/i })).not.toHaveAttribute('aria-current');
|
||||
@@ -53,7 +54,7 @@ describe('/collection layout', () => {
|
||||
});
|
||||
|
||||
test('when nothing needs a person the verdict says so', () => {
|
||||
setup();
|
||||
setup({ ...counts, unmatched: 0 });
|
||||
expect(screen.getByTestId('collection-verdict').textContent).toMatch(/5 albums wanted, 3 downloading\. Nothing needs you\./);
|
||||
expect(screen.queryByTestId('tab-attention')).toBeNull();
|
||||
});
|
||||
@@ -66,6 +67,14 @@ describe('/collection layout', () => {
|
||||
expect(screen.getByTestId('tab-attention').textContent).toBe('2');
|
||||
});
|
||||
|
||||
test('library albums waiting to be matched need a person, and link to Unmatched', () => {
|
||||
setup();
|
||||
expect(screen.getByTestId('collection-verdict').textContent).toMatch(
|
||||
/5 albums wanted, 3 downloading\. 3 albums in your library need matching\./
|
||||
);
|
||||
expect(screen.getByRole('link', { name: /see unmatched/i }).getAttribute('href')).toBe('/collection/unmatched');
|
||||
});
|
||||
|
||||
test("an artist's page sits under Catalogue", () => {
|
||||
setup(counts, '/collection/artist/blur');
|
||||
expect(screen.getByRole('link', { name: /^catalogue/i })).toHaveAttribute('aria-current', 'page');
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { pageTitle } from '#lib/branding.js';
|
||||
import UnmatchedList from '#lib/components/wanted/UnmatchedList.svelte';
|
||||
</script>
|
||||
|
||||
<svelte:head><title>{pageTitle('Collection · Unmatched')}</title></svelte:head>
|
||||
|
||||
<UnmatchedList />
|
||||
Reference in New Issue
Block a user