mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
fix(ui): split album and track artists (OpenSubsonic) (#696)
* fix(ui): split OpenSubsonic album and track artists in header and player Album detail header uses albumArtists from album or child songs; player bar, mobile player, and mini player use structured track artists with per-id links. Adds deriveAlbumHeaderArtistRefs helper and OpenArtistRefInline. Fixes #552 * docs: changelog and credits for OpenSubsonic artist links (PR #696)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { deriveAlbumHeaderArtistRefs } from './deriveAlbumHeaderArtistRefs';
|
||||
import type { SubsonicAlbum } from '../../api/subsonicTypes';
|
||||
import { makeSubsonicSong } from '@/test/helpers/factories';
|
||||
|
||||
const baseAlbum = (): SubsonicAlbum => ({
|
||||
id: 'al-1',
|
||||
name: 'Test Album',
|
||||
artist: 'Joined A / B',
|
||||
artistId: 'ar-first',
|
||||
songCount: 2,
|
||||
duration: 100,
|
||||
});
|
||||
|
||||
describe('deriveAlbumHeaderArtistRefs', () => {
|
||||
it('prefers album-level albumArtists when present', () => {
|
||||
const album: SubsonicAlbum = {
|
||||
...baseAlbum(),
|
||||
albumArtists: [{ id: 'a1', name: 'One' }, { id: 'a2', name: 'Two' }],
|
||||
};
|
||||
expect(deriveAlbumHeaderArtistRefs(album, [])).toEqual(album.albumArtists);
|
||||
});
|
||||
|
||||
it('falls back to the first song with albumArtists', () => {
|
||||
const album = baseAlbum();
|
||||
const songs = [
|
||||
makeSubsonicSong({
|
||||
albumId: album.id,
|
||||
album: album.name,
|
||||
albumArtists: [{ id: 'b1', name: 'Beta' }, { name: 'Gamma' }],
|
||||
}),
|
||||
];
|
||||
expect(deriveAlbumHeaderArtistRefs(album, songs)).toEqual(songs[0].albumArtists);
|
||||
});
|
||||
|
||||
it('uses legacy artist + artistId when no structured refs', () => {
|
||||
const album = baseAlbum();
|
||||
const songs = [makeSubsonicSong({ albumId: album.id, album: album.name })];
|
||||
expect(deriveAlbumHeaderArtistRefs(album, songs)).toEqual([{ id: 'ar-first', name: 'Joined A / B' }]);
|
||||
});
|
||||
|
||||
it('omits id when artistId is blank', () => {
|
||||
const album: SubsonicAlbum = { ...baseAlbum(), artistId: ' ', artist: 'Solo' };
|
||||
expect(deriveAlbumHeaderArtistRefs(album, [])).toEqual([{ name: 'Solo' }]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { SubsonicAlbum, SubsonicOpenArtistRef, SubsonicSong } from '../../api/subsonicTypes';
|
||||
|
||||
function nonEmpty(refs: SubsonicOpenArtistRef[] | undefined): refs is SubsonicOpenArtistRef[] {
|
||||
return !!refs && refs.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenSubsonic album credits for the album-detail header.
|
||||
* Prefer `albumArtists` on the album payload, then on any child song (Navidrome
|
||||
* often attaches the structured list only on songs); fall back to legacy
|
||||
* `artist` + `artistId` strings.
|
||||
*/
|
||||
export function deriveAlbumHeaderArtistRefs(
|
||||
album: SubsonicAlbum,
|
||||
songs: SubsonicSong[],
|
||||
): SubsonicOpenArtistRef[] {
|
||||
if (nonEmpty(album.albumArtists)) return album.albumArtists;
|
||||
for (const s of songs) {
|
||||
if (nonEmpty(s.albumArtists)) return s.albumArtists;
|
||||
}
|
||||
const name = album.artist?.trim() || '—';
|
||||
const id = album.artistId?.trim();
|
||||
return id ? [{ id, name }] : [{ name }];
|
||||
}
|
||||
Reference in New Issue
Block a user