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'
// Mock Material-UI components
vi.mock('@material-ui/core', () => ({
List: ({ children }) =>
{children}
,
ListItem: ({ children, button, onClick, dense, className }) => (
),
ListItemIcon: ({ children }) => {children},
ListItemText: ({ primary }) => {primary},
Typography: ({ children, variant }) => {children},
Box: ({ children, className }) => {children}
,
Checkbox: ({
checked,
indeterminate,
onChange,
size,
className,
...props
}) => (
{
if (el) el.indeterminate = indeterminate
}}
onChange={onChange}
className={className}
{...props}
/>
),
makeStyles: () => () => ({}),
}))
// Mock Material-UI icons
vi.mock('@material-ui/icons', () => ({
CheckBox: () => ☑,
CheckBoxOutlineBlank: () => ☐,
}))
// Mock the react-admin hook
vi.mock('react-admin', () => ({
useGetList: vi.fn(),
useTranslate: vi.fn(() => (key) => key), // Simple translation mock
}))
describe('', () => {
const mockOnChange = vi.fn()
beforeEach(() => {
// Reset the mock before each test
mockOnChange.mockClear()
})
afterEach(cleanup)
it('should render empty message when no libraries available', () => {
// Mock empty library response
useGetList.mockReturnValue({
ids: [],
data: {},
})
render()
expect(screen.getByText('No libraries available')).not.toBeNull()
})
it('should render libraries when available', () => {
// Mock libraries
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render()
expect(screen.getByText('Library 1')).not.toBeNull()
expect(screen.getByText('Library 2')).not.toBeNull()
})
it('should toggle selection when a library is clicked', () => {
// Mock libraries
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
// Test selecting an item
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render()
// Find the library buttons by their text content
const library1Button = screen.getByText('Library 1').closest('button')
fireEvent.click(library1Button)
expect(mockOnChange).toHaveBeenCalledWith(['1'])
// Clean up to avoid DOM duplication
cleanup()
mockOnChange.mockClear()
// Test deselecting an item
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render()
// Find the library button again and click to deselect
const library1ButtonDeselect = screen
.getByText('Library 1')
.closest('button')
fireEvent.click(library1ButtonDeselect)
expect(mockOnChange).toHaveBeenCalledWith([])
})
it('should correctly initialize with provided values', () => {
// Mock libraries
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
// Initial value as array of IDs
render()
// Check that checkbox for Library 1 is checked
const checkboxes = screen.getAllByRole('checkbox')
// With master checkbox, individual checkboxes start at index 1
expect(checkboxes[1].checked).toBe(true) // Library 1
expect(checkboxes[2].checked).toBe(false) // Library 2
})
it('should handle value as array of objects', () => {
// Mock libraries
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
// Initial value as array of objects with id property
render()
// Check that checkbox for Library 2 is checked
const checkboxes = screen.getAllByRole('checkbox')
// With master checkbox, index shifts by 1
expect(checkboxes[1].checked).toBe(false) // Library 1
expect(checkboxes[2].checked).toBe(true) // Library 2
})
it('should render master checkbox when there are multiple libraries', () => {
// Mock libraries
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render()
// Should render master checkbox plus individual checkboxes
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(3) // 1 master + 2 individual
expect(
screen.getByText('resources.user.message.selectAllLibraries'),
).not.toBeNull()
})
it('should not render master checkbox when there is only one library', () => {
// Mock single library
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
}
useGetList.mockReturnValue({
ids: ['1'],
data: mockLibraries,
})
render()
// Should render only individual checkbox
const checkboxes = screen.getAllByRole('checkbox')
expect(checkboxes).toHaveLength(1) // Only 1 individual checkbox
})
it('should handle master checkbox selection and deselection', () => {
// Mock libraries
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render()
const checkboxes = screen.getAllByRole('checkbox')
const masterCheckbox = checkboxes[0] // Master is first
// Click master checkbox to select all
fireEvent.click(masterCheckbox)
expect(mockOnChange).toHaveBeenCalledWith(['1', '2'])
// Clean up and test deselect all
cleanup()
mockOnChange.mockClear()
render()
const checkboxes2 = screen.getAllByRole('checkbox')
const masterCheckbox2 = checkboxes2[0]
// Click master checkbox to deselect all
fireEvent.click(masterCheckbox2)
expect(mockOnChange).toHaveBeenCalledWith([])
})
it('should show master checkbox as indeterminate when some libraries are selected', () => {
// Mock libraries
const mockLibraries = {
1: { id: '1', name: 'Library 1' },
2: { id: '2', name: 'Library 2' },
}
useGetList.mockReturnValue({
ids: ['1', '2'],
data: mockLibraries,
})
render()
const checkboxes = screen.getAllByRole('checkbox')
const masterCheckbox = checkboxes[0] // Master is first
// Master checkbox should not be checked when only some libraries are selected
expect(masterCheckbox.checked).toBe(false)
// Note: Testing indeterminate property directly through JSDOM can be unreliable
// The important behavior is that it's not checked when only some are selected
})
describe('New User Default Library Selection', () => {
// Helper function to create mock libraries with configurable default settings
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 }
}
// Helper function to setup useGetList mock
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(
,
)
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(
,
)
expect(mockOnChange).not.toHaveBeenCalled()
})
it('should not pre-select libraries while data is still loading', () => {
setupMockLibraries(
[{ id: '1', name: 'Library 1', defaultNewUsers: true }],
true,
) // isLoading = true
render(
,
)
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(
,
)
expect(mockOnChange).not.toHaveBeenCalled()
})
it('should reset initialization state when isNewUser prop changes', () => {
setupMockLibraries([
{ id: '1', name: 'Library 1', defaultNewUsers: true },
])
const { rerender } = render(
,
)
expect(mockOnChange).not.toHaveBeenCalled()
// Change to new user
rerender(
,
)
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(
,
)
expect(mockOnChange).toHaveBeenCalledWith(['1'])
mockOnChange.mockClear()
// Re-render with empty value prop (simulating form state update)
rerender(
,
)
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(
,
)
// Check that no libraries are selected (checkboxes should be unchecked)
const checkboxes = screen.getAllByRole('checkbox')
// Only one checkbox since there's only one library and no master checkbox for single library
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' }, // Missing defaultNewUsers property
{ id: '3', name: 'Library 3', defaultNewUsers: false },
])
render(
,
)
expect(mockOnChange).toHaveBeenCalledWith(['1'])
})
})
})