mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
fix(albums): scope All Albums genre filter to selected music library (#959)
* fix(albums): scope All Albums genre filter to selected music library When the sidebar narrows to one Subsonic library, the genre popover fell back to server-wide getGenres() instead of the scoped local index catalog. Load genre options from libraryGetGenreAlbumCounts for library-only scope and enable the catalog path whenever the index is on and a library is selected. * docs: credit All Albums genre filter fix (PR #959, report zunoz) * chore: drop settingsCredits entry for small genre-filter fix
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { AlbumBrowseQuery } from './albumBrowseTypes';
|
||||
|
||||
const libraryGetGenreAlbumCounts = vi.fn();
|
||||
const libraryIsReady = vi.fn();
|
||||
const libraryScopeForServer = vi.fn();
|
||||
const runLocalAlbumBrowse = vi.fn();
|
||||
|
||||
vi.mock('../../api/library', () => ({
|
||||
libraryGetGenreAlbumCounts: (...args: unknown[]) => libraryGetGenreAlbumCounts(...args),
|
||||
}));
|
||||
|
||||
vi.mock('./libraryReady', () => ({
|
||||
libraryIsReady: (...args: unknown[]) => libraryIsReady(...args),
|
||||
}));
|
||||
|
||||
vi.mock('../../api/subsonicClient', () => ({
|
||||
libraryScopeForServer: (...args: unknown[]) => libraryScopeForServer(...args),
|
||||
}));
|
||||
|
||||
vi.mock('./albumBrowseLocal', () => ({
|
||||
runLocalAlbumBrowse: (...args: unknown[]) => runLocalAlbumBrowse(...args),
|
||||
}));
|
||||
|
||||
import { fetchAlbumBrowseGenreOptions } from './albumBrowseLoad';
|
||||
|
||||
const baseQuery: AlbumBrowseQuery = {
|
||||
sort: 'alphabeticalByName',
|
||||
genres: [],
|
||||
losslessOnly: false,
|
||||
starredOnly: false,
|
||||
compFilter: 'all',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
libraryIsReady.mockResolvedValue(true);
|
||||
libraryScopeForServer.mockReturnValue('lib-a');
|
||||
});
|
||||
|
||||
describe('fetchAlbumBrowseGenreOptions', () => {
|
||||
it('uses scoped local genre counts when only the sidebar library is narrowed', async () => {
|
||||
libraryGetGenreAlbumCounts.mockResolvedValue([
|
||||
{ value: 'Rock', albumCount: 12, songCount: 40 },
|
||||
{ value: 'Jazz', albumCount: 3, songCount: 9 },
|
||||
]);
|
||||
|
||||
await expect(fetchAlbumBrowseGenreOptions('srv-1', true, baseQuery)).resolves.toEqual([
|
||||
{ genre: 'Rock', count: 12 },
|
||||
{ genre: 'Jazz', count: 3 },
|
||||
]);
|
||||
|
||||
expect(libraryGetGenreAlbumCounts).toHaveBeenCalledWith({
|
||||
serverId: 'srv-1',
|
||||
libraryScope: 'lib-a',
|
||||
});
|
||||
expect(runLocalAlbumBrowse).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('derives genres from filtered albums when combined filters are active', async () => {
|
||||
runLocalAlbumBrowse.mockResolvedValue({
|
||||
albums: [
|
||||
{ id: '1', name: 'A', artist: 'X', artistId: 'x', songCount: 1, duration: 1, genre: 'Rock' },
|
||||
{ id: '2', name: 'B', artist: 'Y', artistId: 'y', songCount: 1, duration: 1, genre: 'Jazz' },
|
||||
],
|
||||
hasMore: false,
|
||||
});
|
||||
|
||||
await expect(
|
||||
fetchAlbumBrowseGenreOptions('srv-1', true, { ...baseQuery, year: { from: 1990 } }),
|
||||
).resolves.toEqual([
|
||||
{ genre: 'Jazz', count: 1 },
|
||||
{ genre: 'Rock', count: 1 },
|
||||
]);
|
||||
|
||||
expect(libraryGetGenreAlbumCounts).not.toHaveBeenCalled();
|
||||
expect(runLocalAlbumBrowse).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -17,10 +17,13 @@ export {
|
||||
} from './albumBrowseFilters';
|
||||
export { runLocalAlbumBrowse } from './albumBrowseLocal';
|
||||
|
||||
import { countGenresFromAlbums, filterAlbumsByCompilation } from './albumBrowseFilters';
|
||||
import { albumBrowseHasServerFilters, countGenresFromAlbums, filterAlbumsByCompilation } from './albumBrowseFilters';
|
||||
import { runLocalAlbumBrowse } from './albumBrowseLocal';
|
||||
import { fetchAlbumBrowseNetwork } from './albumBrowseNetwork';
|
||||
import { fetchStarredAlbumBrowse } from './albumBrowseStarredFetch';
|
||||
import { libraryGetGenreAlbumCounts } from '../../api/library';
|
||||
import { libraryScopeForServer } from '../../api/subsonicClient';
|
||||
import { libraryIsReady } from './libraryReady';
|
||||
import type {
|
||||
AlbumBrowseFetchCallbacks,
|
||||
AlbumBrowsePageResult,
|
||||
@@ -55,6 +58,24 @@ export async function fetchAlbumBrowseGenreOptions(
|
||||
query: AlbumBrowseQuery,
|
||||
): Promise<GenreFilterOption[]> {
|
||||
const withoutGenre: AlbumBrowseQuery = { ...query, genres: [] };
|
||||
const scope = libraryScopeForServer(serverId);
|
||||
const hasCombinedFilters =
|
||||
albumBrowseHasServerFilters(withoutGenre) || query.compFilter !== 'all';
|
||||
|
||||
// Sidebar library scope only: use the full scoped genre catalog from the local
|
||||
// index instead of getGenres() (server-wide) or a 500-album sample.
|
||||
if (indexEnabled && serverId && scope && !hasCombinedFilters && (await libraryIsReady(serverId))) {
|
||||
try {
|
||||
const rows = await libraryGetGenreAlbumCounts({
|
||||
serverId,
|
||||
libraryScope: scope,
|
||||
});
|
||||
return rows.map(row => ({ genre: row.value, count: row.albumCount }));
|
||||
} catch {
|
||||
/* fall through to album-derived options */
|
||||
}
|
||||
}
|
||||
|
||||
const page = await fetchAlbumBrowsePage(
|
||||
serverId,
|
||||
indexEnabled,
|
||||
|
||||
Reference in New Issue
Block a user