fix: every library album is matched to MusicBrainz before it is listed, by its tags, its artist's discography or a search, and checked against its tracks (M502 #5569)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m28s
release / android (push) Successful in 5m44s
release / Build signed APK (releases and dev) (push) Successful in 5m58s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m28s
release / android (push) Successful in 5m44s
release / Build signed APK (releases and dev) (push) Successful in 5m58s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
The one-time listing took only albums whose tags already named their release group and artist, and left 340 off the list. The rest are now matched: the release their tags name, else the artist's release groups called what the album is called, else a MusicBrainz search by artist and title. A group found by name is taken only when its tracks agree with the album's (by disc and number, then title; length and title, or recording), so a partial copy still matches and a same-titled other album does not. library_album_matches records each album the listing decided, listed or unmatched with why, so an album taken off the list is never re-listed. The albums no match was found for are named on the Held tab. Album rows with no tracks are no longer counted. The length and title checks move from the importer to release, shared by both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,35 +1,23 @@
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { render, screen } from "@testing-library/svelte";
|
||||
import { createRawSnippet } from "svelte";
|
||||
import { mockInfiniteQuery } from "../../test-utils/query";
|
||||
import { pageUrlModule } from "#test-utils/mocks/appState.js";
|
||||
import type { WantCounts, WantsPage } from "#lib/api/types.js";
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
import { render, screen } from '@testing-library/svelte';
|
||||
import { createRawSnippet } from 'svelte';
|
||||
import { mockInfiniteQuery } from '../../test-utils/query';
|
||||
import { pageUrlModule } from '#test-utils/mocks/appState.js';
|
||||
import type { WantCounts, WantsPage } from '#lib/api/types.js';
|
||||
|
||||
const state = vi.hoisted(() => ({
|
||||
pageUrl: new URL("http://localhost/wanted"),
|
||||
}));
|
||||
vi.mock("$app/state", () => pageUrlModule(state));
|
||||
vi.mock("#lib/api/wanted.js", () => ({ createWantsQuery: vi.fn() }));
|
||||
const state = vi.hoisted(() => ({ pageUrl: new URL('http://localhost/wanted') }));
|
||||
vi.mock('$app/state', () => pageUrlModule(state));
|
||||
vi.mock('#lib/api/wanted.js', () => ({ createWantsQuery: vi.fn() }));
|
||||
|
||||
import WantedLayout from "./+layout.svelte";
|
||||
import { createWantsQuery } from "#lib/api/wanted.js";
|
||||
import WantedLayout from './+layout.svelte';
|
||||
import { createWantsQuery } from '#lib/api/wanted.js';
|
||||
|
||||
const counts: WantCounts = {
|
||||
wanted: 5,
|
||||
missing: 1,
|
||||
held: 9,
|
||||
following: 2,
|
||||
activity: 3,
|
||||
attention: 0,
|
||||
blocked: 4,
|
||||
};
|
||||
const children = createRawSnippet(() => ({
|
||||
render: () => `<p data-testid="tab-body">body</p>`,
|
||||
}));
|
||||
const counts: WantCounts = { wanted: 5, missing: 1, held: 9, following: 2, activity: 3, attention: 0, blocked: 4 };
|
||||
const children = createRawSnippet(() => ({ render: () => `<p data-testid="tab-body">body</p>` }));
|
||||
|
||||
afterEach(() => vi.clearAllMocks());
|
||||
|
||||
function setup(c: WantCounts = counts, path = "/wanted") {
|
||||
function setup(c: WantCounts = counts, path = '/wanted') {
|
||||
state.pageUrl = new URL(`http://localhost${path}`);
|
||||
const page: WantsPage = {
|
||||
items: [],
|
||||
@@ -37,58 +25,44 @@ function setup(c: WantCounts = counts, path = "/wanted") {
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
counts: c,
|
||||
library: { listed_at: null, unlisted: 0 },
|
||||
library: { listed_at: null, unlisted: 0, unmatched: [] }
|
||||
};
|
||||
vi.mocked(createWantsQuery).mockReturnValue(
|
||||
mockInfiniteQuery({ pages: [page] }) as unknown as ReturnType<
|
||||
typeof createWantsQuery
|
||||
>,
|
||||
mockInfiniteQuery({ pages: [page] }) as unknown as ReturnType<typeof createWantsQuery>
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return render(WantedLayout, { children } as any);
|
||||
}
|
||||
|
||||
describe("/wanted layout", () => {
|
||||
test("tabs are links carrying their counts, the current one marked", () => {
|
||||
setup(counts, "/wanted/activity");
|
||||
const nav = screen.getByRole("navigation", { name: /wanted sections/i });
|
||||
const links = Array.from(nav.querySelectorAll("a")).map((a) => [
|
||||
a.getAttribute("href"),
|
||||
a.textContent?.replace(/\s+/g, " ").trim(),
|
||||
]);
|
||||
describe('/wanted layout', () => {
|
||||
test('tabs are links carrying their counts, the current one marked', () => {
|
||||
setup(counts, '/wanted/activity');
|
||||
const nav = screen.getByRole('navigation', { name: /wanted sections/i });
|
||||
const links = Array.from(nav.querySelectorAll('a')).map((a) => [a.getAttribute('href'), a.textContent?.replace(/\s+/g, ' ').trim()]);
|
||||
expect(links).toEqual([
|
||||
["/wanted", "Wanted 5"],
|
||||
["/wanted/activity", "Activity 3"],
|
||||
["/wanted/missing", "Missing files 1"],
|
||||
["/wanted/held", "Held 9"],
|
||||
["/wanted/following", "Following 2"],
|
||||
["/wanted/blocked", "Blocked 4"],
|
||||
['/wanted', 'Wanted 5'],
|
||||
['/wanted/activity', 'Activity 3'],
|
||||
['/wanted/missing', 'Missing files 1'],
|
||||
['/wanted/held', 'Held 9'],
|
||||
['/wanted/following', 'Following 2'],
|
||||
['/wanted/blocked', 'Blocked 4']
|
||||
]);
|
||||
expect(screen.getByRole("link", { name: /activity/i })).toHaveAttribute(
|
||||
"aria-current",
|
||||
"page",
|
||||
);
|
||||
expect(screen.getByRole("link", { name: /^wanted/i })).not.toHaveAttribute(
|
||||
"aria-current",
|
||||
);
|
||||
expect(screen.getByTestId("tab-body")).toBeTruthy();
|
||||
expect(screen.getByRole('link', { name: /activity/i })).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByRole('link', { name: /^wanted/i })).not.toHaveAttribute('aria-current');
|
||||
expect(screen.getByTestId('tab-body')).toBeTruthy();
|
||||
});
|
||||
|
||||
test("when nothing needs a person the verdict says so", () => {
|
||||
test('when nothing needs a person the verdict says so', () => {
|
||||
setup();
|
||||
expect(screen.getByTestId("wanted-verdict").textContent).toMatch(
|
||||
/5 albums wanted, 3 downloading\. Nothing needs you\./,
|
||||
);
|
||||
expect(screen.queryByTestId("tab-attention")).toBeNull();
|
||||
expect(screen.getByTestId('wanted-verdict').textContent).toMatch(/5 albums wanted, 3 downloading\. Nothing needs you\./);
|
||||
expect(screen.queryByTestId('tab-attention')).toBeNull();
|
||||
});
|
||||
|
||||
test("downloads waiting on a person lead the page and mark Activity", () => {
|
||||
test('downloads waiting on a person lead the page and mark Activity', () => {
|
||||
setup({ ...counts, attention: 2 });
|
||||
const verdict = screen.getByTestId("wanted-verdict");
|
||||
const verdict = screen.getByTestId('wanted-verdict');
|
||||
expect(verdict.textContent).toMatch(/2 downloads need you/);
|
||||
expect(
|
||||
screen.getByRole("link", { name: /see activity/i }).getAttribute("href"),
|
||||
).toBe("/wanted/activity");
|
||||
expect(screen.getByTestId("tab-attention").textContent).toBe("2");
|
||||
expect(screen.getByRole('link', { name: /see activity/i }).getAttribute('href')).toBe('/wanted/activity');
|
||||
expect(screen.getByTestId('tab-attention').textContent).toBe('2');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user