import * as React from 'react'
import { render, screen, fireEvent, cleanup } from '@testing-library/react'
import { SelectLibraryInput } from './SelectLibraryInput'
import { useGetList } from 'react-admin'
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
vi.mock('@material-ui/core', () => ({
List: ({ children }) => <div>{children}</div>,
ListItem: ({ children, button, onClick, dense, className }) => (
<button onClick={onClick} className={className}>
{children}
</button>
),
ListItemIcon: ({ children }) => <span>{children}</span>,
ListItemText: ({ primary }) => <span>{primary}</span>,
Typography: ({ children, variant }) => <span>{children}</span>,
Box: ({ children, className }) => <div className={className}>{children}</div>,
Checkbox: ({
checked,
indeterminate,
onChange,
size,
className,
...props
}) => (
<input
type="checkbox"
checked={checked}
ref={(el) => {
if (el) el.indeterminate = indeterminate
}}
onChange={onChange}
className={className}
{...props}
/>
),
makeStyles: () => () => ({}),
}))
vi.mock('@material-ui/icons', () => ({
CheckBox: () => <span>☑</span>,
CheckBoxOutlineBlank: () => <span>☐</span>,
}))
vi.mock('react-admin', () => ({
useGetList: vi.fn(),
useTranslate: vi.fn(() => (key) => key),
}))
describe('<SelectLibraryInput />', () => {
const mockOnChange = vi.fn()
beforeEach(() => {
mockOnChange.mockClear()
})
afterEach(cleanup)
it('should render empty message when no libraries available', () => {
useGetList.mockReturnValue({
ids: [],
data: {},
})
render(<SelectLibraryInput onChange={mockOnChange} value={[]} />)
expect(screen.getByText('No libraries available')).not.toBeNull()
})
it('should render libraries when available', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={[]} />)
expect(screen.getByText('Library 1')).not.toBeNull()
expect(screen.getByText('Library 2')).not.toBeNull()
})
it('should toggle selection when a library is clicked', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={[]} />)
const library1Button = screen.getByText('Library 1').closest('button')
fireEvent.click(library1Button)
expect(mockOnChange).toHaveBeenCalledWith(['1'])
cleanup()
mockOnChange.mockClear()
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={['1']} />)
const library1ButtonDeselect = screen
.getByText('Library 1')
.closest('button')
fireEvent.click(library1ButtonDeselect)
expect(mockOnChange).toHaveBeenCalledWith([])
})
it('should correctly initialize with provided values', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={['1']} />)
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes[1].checked).toBe(true)
expect(checkboxes[2].checked).toBe(false)
})
it('should handle value as array of objects', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={[{ id: '2' }]} />)
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes[1].checked).toBe(false)
expect(checkboxes[2].checked).toBe(true)
})
it('should render master checkbox when there are multiple libraries', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={[]} />)
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(3)
expect(
screen.getByText('resources.user.message.selectAllLibraries'),
).not.toBeNull()
})
it('should not render master checkbox when there is only one library', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
}
useGetList.mockReturnValue({
ids: ['1'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={[]} />)
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(1)
})
it('should handle master checkbox selection and deselection', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={[]} />)
const checkboxes = screen.getAllByRole('checkbox')
const masterCheckbox = checkboxes[0]
fireEvent.click(masterCheckbox)
expect(mockOnChange).toHaveBeenCalledWith(['1', '2'])
cleanup()
mockOnChange.mockClear()
render(<SelectLibraryInput onChange={mockOnChange} value={['1', '2']} />)
const checkboxes2 = screen.getAllByRole('checkbox')
const masterCheckbox2 = checkboxes2[0]
fireEvent.click(masterCheckbox2)
expect(mockOnChange).toHaveBeenCalledWith([])
})
it('should show master checkbox as indeterminate when some libraries are selected', () => {
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render(<SelectLibraryInput onChange={mockOnChange} value={['1']} />)
const checkboxes = screen.getAllByRole('checkbox')
const masterCheckbox = checkboxes[0]
expect(masterCheckbox.checked).toBe(false)
})
describe('New User Default Library Selection', () => {
const createMockLibraries = (libraryConfigs) => {
const libraries = {}
const ids = []
libraryConfigs.forEach(({ id, name, defaultNewUsers }) => {
libraries[id] = {
id,
name,
...(defaultNewUsers !== undefined && { defaultNewUsers }),
}
ids.push(id)
})
return { libraries, ids }
}
const setupMockLibraries = (libraryConfigs, isLoading = false) => {
const { libraries, ids } = createMockLibraries(libraryConfigs)
useGetList.mockReturnValue({
ids,
data: libraries,
isLoading,
})
return { libraries, ids }
}
beforeEach(() => {
mockOnChange.mockClear()
})
it('should pre-select default libraries for new users', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: true },
{ id: '2', name: 'Library 2', defaultNewUsers: false },
{ id: '3', name: 'Library 3', defaultNewUsers: true },
])
render(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={true}
/>,
)
expect(mockOnChange).toHaveBeenCalledWith(['1', '3'])
})
it('should not pre-select default libraries if new user already has values', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: true },
{ id: '2', name: 'Library 2', defaultNewUsers: false },
])
render(
<SelectLibraryInput
onChange={mockOnChange}
value={['2']}
isNewUser={true}
/>,
)
expect(mockOnChange).not.toHaveBeenCalled()
})
it('should not pre-select libraries while data is still loading', () => {
setupMockLibraries(
[{ id: '1', name: 'Library 1', defaultNewUsers: true }],
true,
)
render(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={true}
/>,
)
expect(mockOnChange).not.toHaveBeenCalled()
})
it('should not pre-select anything if no libraries have defaultNewUsers flag', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: false },
{ id: '2', name: 'Library 2', defaultNewUsers: false },
])
render(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={true}
/>,
)
expect(mockOnChange).not.toHaveBeenCalled()
})
it('should reset initialization state when isNewUser prop changes', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: true },
])
const { rerender } = render(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={false}
/>,
)
expect(mockOnChange).not.toHaveBeenCalled()
rerender(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={true}
/>,
)
expect(mockOnChange).toHaveBeenCalledWith(['1'])
})
it('should not override pre-selection when value prop is empty for new users', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: true },
{ id: '2', name: 'Library 2', defaultNewUsers: false },
])
const { rerender } = render(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={true}
/>,
)
expect(mockOnChange).toHaveBeenCalledWith(['1'])
mockOnChange.mockClear()
rerender(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={true}
/>,
)
expect(mockOnChange).not.toHaveBeenCalled()
})
it('should sync from value prop for existing users even when empty', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: true },
])
render(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={false}
/>,
)
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes[0].checked).toBe(false)
})
it('should handle libraries with missing defaultNewUsers property', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: true },
{ id: '2', name: 'Library 2' },
{ id: '3', name: 'Library 3', defaultNewUsers: false },
])
render(
<SelectLibraryInput
onChange={mockOnChange}
value={[]}
isNewUser={true}
/>,
)
expect(mockOnChange).toHaveBeenCalledWith(['1'])
})
})
})