mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-15 13:57:09 +02:00
* feat(ui): add Share button to artist detail page * feat(ui): add Download button to artist detail page * fix(ui): scope artist share/download to album-artist content Gate the artist Share/Download actions on album-artist stats and show the album-artist size, since ZipArtist and the share query only cover album_artist_id songs. Previously the total (role-inclusive) size was shown and guest-only artists could produce an empty archive. Applies to the artist toolbar, the shared context menu, and the download dialog title. * fix: match artist download/share to album-artist participation ZipArtist and the artist share query filtered the deprecated album_artist_id column, which only stores the first album artist of a track. Secondary album-artists (co-credited but not first) got an empty download/share even though the UI offered it. Filter by the album-artist role participation instead, matching the artist's album-artist stats used to gate the actions. Also cover the artist-specific size branch of the download dialog. * fix(share): scope artist shares to the owner's libraries The artist share query broadened to album-artist participation, which could pull a secondary album artist's tracks from libraries the (non-admin) share owner cannot access into the public share. Load the artist share as the owner so their library access is applied, mirroring how playlist shares already work. Adds a repository test covering co-album-artist inclusion and library scoping. * test(share): assert album participation branch of artist shares Link the co-album-artist fixtures to albums and assert share.Albums (used by Subsonic getShares) includes the accessible album and excludes the one in a library the owner cannot access, so the album participation + scoping branch is covered too. * fix: exclude missing files from artist download/share actions An artist's stats still count files that went missing, so the toolbar/context menu could offer Download/Share for an artist whose files are all gone, while the share query (missing=false) returns nothing and downloads open dead paths. Hide the actions when the artist is missing and exclude missing files from ZipArtist, matching the share semantics. * refactor: dedupe artist download-size and share-owner lookups Extract the 'album-artist download size (or none when missing)' rule into a single artistDownloadSize() helper shared by the toolbar, context menu, and download dialog, and factor the duplicated share-owner context lookup into a shareRepository.ownerContext() method used by both the artist and playlist share cases. * refactor(ui): move artistDownloadSize helper to common utils is for domain-agnostic, potentially portable code; this helper is Navidrome-specific (artist stats shape), so it belongs in common. Consumers import it directly from common/artist to avoid pulling in the common barrel.
78 lines
2.3 KiB
JavaScript
78 lines
2.3 KiB
JavaScript
import React from 'react'
|
|
import { render, fireEvent, screen } from '@testing-library/react'
|
|
import { TestContext } from 'ra-test'
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import { ThemeProvider, createTheme } from '@material-ui/core/styles'
|
|
import { AlbumContextMenu, ArtistContextMenu } from './ContextMenus'
|
|
|
|
const mockDispatch = vi.fn()
|
|
vi.mock('react-redux', () => ({ useDispatch: () => mockDispatch }))
|
|
|
|
const { mockConfig } = vi.hoisted(() => ({
|
|
mockConfig: {
|
|
enableSharing: true,
|
|
enableDownloads: true,
|
|
enableFavourites: false,
|
|
},
|
|
}))
|
|
vi.mock('../config', () => ({ default: mockConfig }))
|
|
|
|
vi.mock('react-admin', async (importOriginal) => {
|
|
const actual = await importOriginal()
|
|
return {
|
|
...actual,
|
|
useNotify: () => vi.fn(),
|
|
useDataProvider: () => ({ getList: vi.fn() }),
|
|
useTranslate: () => (x) => x,
|
|
}
|
|
})
|
|
|
|
describe('ContextMenus', () => {
|
|
const renderMenu = (Menu, record) => {
|
|
render(
|
|
<TestContext>
|
|
<ThemeProvider theme={createTheme()}>
|
|
<Menu record={record} />
|
|
</ThemeProvider>
|
|
</TestContext>,
|
|
)
|
|
fireEvent.click(screen.getByLabelText('more'))
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockConfig.enableSharing = true
|
|
mockConfig.enableDownloads = true
|
|
})
|
|
|
|
describe('ArtistContextMenu', () => {
|
|
const withAlbumArtist = {
|
|
id: 'ar1',
|
|
name: 'Artist',
|
|
stats: { albumartist: { songCount: 3, albumCount: 1, size: 1024 } },
|
|
}
|
|
|
|
it('shows the album-artist size on the download item', () => {
|
|
renderMenu(ArtistContextMenu, withAlbumArtist)
|
|
expect(screen.getByText('ra.action.download (1 KB)')).toBeInTheDocument()
|
|
})
|
|
|
|
it('hides download and share for artists with no album-artist content', () => {
|
|
renderMenu(ArtistContextMenu, { id: 'ar1', name: 'Artist', stats: {} })
|
|
expect(screen.queryByText(/ra\.action\.download/)).not.toBeInTheDocument()
|
|
expect(screen.queryByText('ra.action.share')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('AlbumContextMenu', () => {
|
|
it('uses the total size on the album download item', () => {
|
|
renderMenu(AlbumContextMenu, {
|
|
id: 'al1',
|
|
name: 'Album',
|
|
duration: 100,
|
|
size: 1024 * 1024,
|
|
})
|
|
expect(screen.getByText('ra.action.download (1 MB)')).toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|