mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-30 06:57:57 +01:00
223 lines
7.3 KiB
JavaScript
223 lines
7.3 KiB
JavaScript
import * as React from 'react'
|
|
import { render, screen } from '@testing-library/react'
|
|
import UserEdit from './UserEdit'
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
|
|
const defaultUser = {
|
|
id: 'user1',
|
|
userName: 'testuser',
|
|
name: 'Test User',
|
|
email: 'test@example.com',
|
|
isAdmin: false,
|
|
libraries: [
|
|
{ id: 1, name: 'Library 1', path: '/music1' },
|
|
{ id: 2, name: 'Library 2', path: '/music2' },
|
|
],
|
|
lastLoginAt: '2023-01-01T12:00:00Z',
|
|
lastAccessAt: '2023-01-02T12:00:00Z',
|
|
updatedAt: '2023-01-03T12:00:00Z',
|
|
createdAt: '2023-01-04T12:00:00Z',
|
|
}
|
|
|
|
const adminUser = {
|
|
...defaultUser,
|
|
id: 'admin1',
|
|
userName: 'admin',
|
|
name: 'Admin User',
|
|
isAdmin: true,
|
|
}
|
|
|
|
const hooks = vi.hoisted(() => ({
|
|
save: null,
|
|
mutate: vi.fn(),
|
|
notify: vi.fn(),
|
|
redirect: vi.fn(),
|
|
refresh: vi.fn(),
|
|
}))
|
|
|
|
// Mock React-Admin completely with simpler implementations
|
|
vi.mock('react-admin', () => ({
|
|
Edit: ({ children, title }) => (
|
|
<div data-testid="edit-component">
|
|
{title}
|
|
{children}
|
|
</div>
|
|
),
|
|
SimpleForm: ({ children, save }) => {
|
|
hooks.save = save
|
|
return <form data-testid="simple-form">{children}</form>
|
|
},
|
|
TextInput: ({ source }) => <input data-testid={`text-input-${source}`} />,
|
|
BooleanInput: ({ source }) => (
|
|
<input type="checkbox" data-testid={`boolean-input-${source}`} />
|
|
),
|
|
PasswordInput: ({ source }) => (
|
|
<input type="password" data-testid={`password-input-${source}`} />
|
|
),
|
|
Toolbar: ({ children }) => <div data-testid="toolbar">{children}</div>,
|
|
SaveButton: () => <button data-testid="save-button">Save</button>,
|
|
FormDataConsumer: ({ children }) => children({ formData: {} }),
|
|
Typography: ({ children }) => <p>{children}</p>,
|
|
required: () => () => null,
|
|
email: () => () => null,
|
|
useMutation: () => [hooks.mutate],
|
|
useNotify: () => hooks.notify,
|
|
useRedirect: () => hooks.redirect,
|
|
useRefresh: () => hooks.refresh,
|
|
usePermissions: () => ({ permissions: 'admin' }),
|
|
useTranslate: () => (key) => key,
|
|
}))
|
|
|
|
vi.mock('./LibrarySelectionField.jsx', () => ({
|
|
LibrarySelectionField: () => <div data-testid="library-selection-field" />,
|
|
}))
|
|
|
|
vi.mock('./DeleteUserButton', () => ({
|
|
__esModule: true,
|
|
default: () => <button data-testid="delete-user-button">Delete</button>,
|
|
}))
|
|
|
|
vi.mock('../common', () => ({
|
|
Title: ({ subTitle }) => <div data-testid="title">{subTitle}</div>,
|
|
ReadOnlyDateField: ({ source }) => (
|
|
<div data-testid={`date-field-${source}`}>Date</div>
|
|
),
|
|
}))
|
|
|
|
// Mock Material-UI
|
|
vi.mock('@material-ui/core/styles', () => ({
|
|
makeStyles: () => () => ({}),
|
|
}))
|
|
|
|
vi.mock('@material-ui/core', () => ({
|
|
Typography: ({ children }) => <p>{children}</p>,
|
|
}))
|
|
|
|
describe('<UserEdit />', () => {
|
|
it('should render the user edit form', () => {
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
// Check if the edit component renders
|
|
expect(screen.getByTestId('edit-component')).toBeInTheDocument()
|
|
expect(screen.getByTestId('simple-form')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render text inputs for admin users', () => {
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
// Should render username input for admin
|
|
expect(screen.getByTestId('text-input-userName')).toBeInTheDocument()
|
|
expect(screen.getByTestId('text-input-name')).toBeInTheDocument()
|
|
expect(screen.getByTestId('text-input-email')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render admin checkbox for admin permissions', () => {
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
// Should render isAdmin checkbox for admin users
|
|
expect(screen.getByTestId('boolean-input-isAdmin')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render date fields', () => {
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
expect(screen.getByTestId('date-field-lastLoginAt')).toBeInTheDocument()
|
|
expect(screen.getByTestId('date-field-lastAccessAt')).toBeInTheDocument()
|
|
expect(screen.getByTestId('date-field-updatedAt')).toBeInTheDocument()
|
|
expect(screen.getByTestId('date-field-createdAt')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render the scrobble filter input', () => {
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
expect(screen.getByTestId('text-input-scrobbleFilter')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render username input for non-admin users', () => {
|
|
render(<UserEdit id="user1" permissions="user" />)
|
|
|
|
// Should not render username input for non-admin
|
|
expect(screen.queryByTestId('text-input-userName')).not.toBeInTheDocument()
|
|
// But should still render name and email
|
|
expect(screen.getByTestId('text-input-name')).toBeInTheDocument()
|
|
expect(screen.getByTestId('text-input-email')).toBeInTheDocument()
|
|
})
|
|
|
|
describe('save', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
hooks.save = null
|
|
localStorage.clear()
|
|
})
|
|
|
|
it('notifies success and redirects when the update succeeds', async () => {
|
|
hooks.mutate.mockResolvedValue({ data: defaultUser })
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
await hooks.save({ id: 'user1', name: 'New Name' })
|
|
|
|
expect(hooks.notify).toHaveBeenCalledWith(
|
|
'resources.user.notifications.updated',
|
|
'info',
|
|
{ smart_count: 1 },
|
|
)
|
|
expect(hooks.redirect).toHaveBeenCalledWith('/user')
|
|
})
|
|
|
|
it('returns field errors when the update fails validation', async () => {
|
|
const fieldErrors = { currentPassword: 'ra.validation.required' }
|
|
hooks.mutate.mockRejectedValue({ body: { errors: fieldErrors } })
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
const result = await hooks.save({ id: 'user1' })
|
|
|
|
expect(result).toEqual(fieldErrors)
|
|
expect(hooks.notify).not.toHaveBeenCalledWith(
|
|
'resources.user.notifications.updated',
|
|
'info',
|
|
{ smart_count: 1 },
|
|
)
|
|
})
|
|
|
|
it('notifies an error when the update fails without field errors', async () => {
|
|
hooks.mutate.mockRejectedValue(new Error('Forbidden'))
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
await hooks.save({ id: 'user1' })
|
|
|
|
expect(hooks.notify).toHaveBeenCalledWith('ra.page.error', 'warning')
|
|
expect(hooks.redirect).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('notifies an error when the update rejects with a non-object error', async () => {
|
|
hooks.mutate.mockRejectedValue(undefined)
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
await hooks.save({ id: 'user1' })
|
|
|
|
expect(hooks.notify).toHaveBeenCalledWith('ra.page.error', 'warning')
|
|
expect(hooks.redirect).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('syncs the stored identity name when editing your own profile', async () => {
|
|
localStorage.setItem('userId', 'user1')
|
|
hooks.mutate.mockResolvedValue({ data: defaultUser })
|
|
render(<UserEdit id="user1" permissions="user" />)
|
|
|
|
await hooks.save({ id: 'user1', name: 'Renamed' })
|
|
|
|
expect(localStorage.getItem('name')).toBe('Renamed')
|
|
})
|
|
|
|
it('does not touch the stored identity name when editing another user', async () => {
|
|
localStorage.setItem('userId', 'admin1')
|
|
localStorage.setItem('name', 'Admin User')
|
|
hooks.mutate.mockResolvedValue({ data: defaultUser })
|
|
render(<UserEdit id="user1" permissions="admin" />)
|
|
|
|
await hooks.save({ id: 'user1', name: 'Renamed' })
|
|
|
|
expect(localStorage.getItem('name')).toBe('Admin User')
|
|
})
|
|
})
|
|
})
|