diff --git a/ui/src/i18n/en.json b/ui/src/i18n/en.json index 0695d62c2..c40197277 100644 --- a/ui/src/i18n/en.json +++ b/ui/src/i18n/en.json @@ -191,6 +191,7 @@ "sync": "Auto-import", "path": "Import from" }, + "byOwner": "by %{name}", "actions": { "selectPlaylist": "Select a playlist:", "addNewPlaylist": "Create \"%{name}\"", diff --git a/ui/src/playlist/PlaylistDetails.jsx b/ui/src/playlist/PlaylistDetails.jsx index 87ca11546..634804d4f 100644 --- a/ui/src/playlist/PlaylistDetails.jsx +++ b/ui/src/playlist/PlaylistDetails.jsx @@ -6,7 +6,7 @@ import { useMediaQuery, } from '@material-ui/core' import { makeStyles } from '@material-ui/core/styles' -import { useTranslate } from 'react-admin' +import { useTranslate, usePermissions } from 'react-admin' import { useCallback, useState, useEffect } from 'react' import Lightbox from 'react-image-lightbox' import 'react-image-lightbox/style.css' @@ -82,6 +82,7 @@ const useStyles = makeStyles( const PlaylistDetails = (props) => { const { record = {} } = props const translate = useTranslate() + const { permissions } = usePermissions() const classes = useStyles() const isDesktop = useMediaQuery((theme) => theme.breakpoints.up('lg')) const [isLightboxOpen, setLightboxOpen] = useState(false) @@ -159,6 +160,14 @@ const PlaylistDetails = (props) => {   )} + {(record.public || permissions === 'admin') && ( + + {translate('resources.playlist.byOwner', { + name: record.ownerName, + _: `by ${record.ownerName}`, + })} + + )} diff --git a/ui/src/playlist/PlaylistDetails.test.jsx b/ui/src/playlist/PlaylistDetails.test.jsx new file mode 100644 index 000000000..a77d46fdd --- /dev/null +++ b/ui/src/playlist/PlaylistDetails.test.jsx @@ -0,0 +1,61 @@ +import React from 'react' +import { render, screen, cleanup } from '@testing-library/react' +import PlaylistDetails from './PlaylistDetails' +import { usePermissions } from 'react-admin' +import { useMediaQuery } from '@material-ui/core' + +vi.mock('react-admin', () => ({ + usePermissions: vi.fn(), + useTranslate: () => (key, opts) => { + if (key === 'resources.playlist.byOwner') { + return `by ${opts.name}` + } + if (key === 'resources.song.name') { + return opts.smart_count === 1 ? 'Song' : 'Songs' + } + return key + }, + useRecordContext: (props) => props.record || {}, +})) + +vi.mock('@material-ui/core', async (importOriginal) => { + const actual = await importOriginal() + return { ...actual, useMediaQuery: vi.fn() } +}) + +describe('', () => { + beforeEach(() => { + vi.clearAllMocks() + useMediaQuery.mockReturnValue(false) + }) + + afterEach(cleanup) + + const baseRecord = { + id: 'pl1', + name: 'My Playlist', + songCount: 1, + duration: 60, + size: 1024, + ownerName: 'Owner', + public: false, + } + + it('shows owner for admin users', () => { + usePermissions.mockReturnValue({ permissions: 'admin' }) + render() + expect(screen.getByText('by Owner')).toBeInTheDocument() + }) + + it('shows owner for public playlists', () => { + usePermissions.mockReturnValue({ permissions: 'user' }) + render() + expect(screen.getByText('by Owner')).toBeInTheDocument() + }) + + it('hides owner for private playlists when not admin', () => { + usePermissions.mockReturnValue({ permissions: 'user' }) + render() + expect(screen.queryByText('by Owner')).toBeNull() + }) +})