2025-12-03 21:56:50 +00:00
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
|
|
|
import userEvent from '@testing-library/user-event';
|
|
|
|
|
import { TrackListContainer } from './TrackListContainer';
|
|
|
|
|
import type { Track } from '../../player/types';
|
|
|
|
|
|
|
|
|
|
// Mock useTrackList
|
|
|
|
|
const mockUseTrackList = vi.fn();
|
|
|
|
|
vi.mock('../hooks/useTrackList', () => ({
|
|
|
|
|
useTrackList: (options: unknown) => mockUseTrackList(options),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
// Mock useSearchParams
|
|
|
|
|
const mockSetSearchParams = vi.fn();
|
|
|
|
|
const mockSearchParams = new URLSearchParams();
|
|
|
|
|
vi.mock('react-router-dom', async () => {
|
|
|
|
|
const actual = await vi.importActual('react-router-dom');
|
|
|
|
|
return {
|
|
|
|
|
...actual,
|
|
|
|
|
useSearchParams: () => [mockSearchParams, mockSetSearchParams],
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockTracks: Track[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 1,
|
|
|
|
|
title: 'Track 1',
|
|
|
|
|
artist: 'Artist 1',
|
|
|
|
|
album: 'Album 1',
|
|
|
|
|
duration: 180,
|
|
|
|
|
url: 'https://example.com/track1.mp3',
|
|
|
|
|
genre: 'Rock',
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 2,
|
|
|
|
|
title: 'Track 2',
|
|
|
|
|
artist: 'Artist 2',
|
|
|
|
|
album: 'Album 2',
|
|
|
|
|
duration: 240,
|
|
|
|
|
url: 'https://example.com/track2.mp3',
|
|
|
|
|
genre: 'Pop',
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const defaultTrackListReturn = {
|
|
|
|
|
tracks: mockTracks,
|
|
|
|
|
filteredTracks: mockTracks,
|
|
|
|
|
displayMode: 'list' as const,
|
|
|
|
|
sortOptions: { field: 'title' as const, order: 'asc' as const },
|
|
|
|
|
filterOptions: {},
|
|
|
|
|
isLoading: false,
|
|
|
|
|
error: null,
|
|
|
|
|
pagination: { page: 1, limit: 20 },
|
|
|
|
|
total: 2,
|
|
|
|
|
totalPages: 1,
|
|
|
|
|
setTracks: vi.fn(),
|
|
|
|
|
setDisplayMode: vi.fn(),
|
|
|
|
|
setSortField: vi.fn(),
|
|
|
|
|
setSortOrder: vi.fn(),
|
|
|
|
|
setFilterOptions: vi.fn(),
|
|
|
|
|
clearFilters: vi.fn(),
|
|
|
|
|
setPagination: vi.fn(),
|
|
|
|
|
setPage: vi.fn(),
|
|
|
|
|
setLimit: vi.fn(),
|
|
|
|
|
setSearchQuery: vi.fn(),
|
|
|
|
|
addTrack: vi.fn(),
|
|
|
|
|
removeTrack: vi.fn(),
|
|
|
|
|
updateTrack: vi.fn(),
|
|
|
|
|
loadTracks: vi.fn(),
|
|
|
|
|
refreshTracks: vi.fn(),
|
|
|
|
|
searchQuery: '',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
describe('TrackListContainer', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
mockUseTrackList.mockReturnValue(defaultTrackListReturn);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should render track list container', () => {
|
|
|
|
|
render(<TrackListContainer />);
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
screen.getByRole('group', { name: 'Options de tri' }),
|
|
|
|
|
).toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display filters when showFilters is true', () => {
|
|
|
|
|
render(<TrackListContainer showFilters={true} />);
|
|
|
|
|
expect(screen.getByLabelText('Rechercher des pistes')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not display filters when showFilters is false', () => {
|
|
|
|
|
render(<TrackListContainer showFilters={false} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
screen.queryByLabelText('Rechercher des pistes'),
|
|
|
|
|
).not.toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display sort when showSort is true', () => {
|
|
|
|
|
render(<TrackListContainer showSort={true} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
screen.getByLabelText('Sélectionner le champ de tri'),
|
|
|
|
|
).toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not display sort when showSort is false', () => {
|
|
|
|
|
render(<TrackListContainer showSort={false} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
screen.queryByLabelText('Sélectionner le champ de tri'),
|
|
|
|
|
).not.toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display view toggle when showViewToggle is true', () => {
|
|
|
|
|
render(<TrackListContainer showViewToggle={true} />);
|
|
|
|
|
expect(screen.getByLabelText('Vue liste')).toBeInTheDocument();
|
|
|
|
|
expect(screen.getByLabelText('Vue grille')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not display view toggle when showViewToggle is false', () => {
|
|
|
|
|
render(<TrackListContainer showViewToggle={false} />);
|
|
|
|
|
expect(screen.queryByLabelText('Vue liste')).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display list view by default', () => {
|
|
|
|
|
render(<TrackListContainer />);
|
|
|
|
|
// TrackList devrait être rendu (on peut vérifier via les tracks)
|
|
|
|
|
expect(mockUseTrackList).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should switch to grid view when displayMode changes', () => {
|
|
|
|
|
const { rerender } = render(<TrackListContainer />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
displayMode: 'grid',
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
rerender(<TrackListContainer />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(mockUseTrackList).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display pagination when showPagination is true and totalPages > 1', () => {
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
totalPages: 3,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer showPagination={true} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
screen.getByRole('navigation', { name: /pagination/i }),
|
|
|
|
|
).toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not display pagination when showPagination is false', () => {
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
totalPages: 3,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer showPagination={false} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
screen.queryByRole('navigation', { name: /pagination/i }),
|
|
|
|
|
).not.toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should not display pagination when totalPages <= 1', () => {
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
totalPages: 1,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer showPagination={true} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
expect(
|
|
|
|
|
screen.queryByRole('navigation', { name: /pagination/i }),
|
|
|
|
|
).not.toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call onTrackClick when track is clicked', async () => {
|
|
|
|
|
const mockOnTrackClick = vi.fn();
|
|
|
|
|
render(<TrackListContainer onTrackClick={mockOnTrackClick} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Le clic sera géré par TrackList/TrackGrid
|
|
|
|
|
// On vérifie que le callback est passé
|
|
|
|
|
expect(mockOnTrackClick).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call onTrackPlay when track play button is clicked', async () => {
|
|
|
|
|
const mockOnTrackPlay = vi.fn();
|
|
|
|
|
render(<TrackListContainer onTrackPlay={mockOnTrackPlay} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Le clic sera géré par TrackList/TrackGrid
|
|
|
|
|
// On vérifie que le callback est passé
|
|
|
|
|
expect(mockOnTrackPlay).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle filter changes', async () => {
|
|
|
|
|
const mockSetFilterOptions = vi.fn();
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
setFilterOptions: mockSetFilterOptions,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer showFilters={true} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Les changements de filtres seront gérés par TrackFilters
|
|
|
|
|
expect(mockSetFilterOptions).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle sort changes', async () => {
|
|
|
|
|
const mockSetSortField = vi.fn();
|
|
|
|
|
const mockSetSortOrder = vi.fn();
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
setSortField: mockSetSortField,
|
|
|
|
|
setSortOrder: mockSetSortOrder,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer showSort={true} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Les changements de tri seront gérés par TrackSort
|
|
|
|
|
expect(mockSetSortField).toBeDefined();
|
|
|
|
|
expect(mockSetSortOrder).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle view mode changes', async () => {
|
|
|
|
|
const mockSetDisplayMode = vi.fn();
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
setDisplayMode: mockSetDisplayMode,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer showViewToggle={true} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Les changements de vue seront gérés par ViewToggle
|
|
|
|
|
expect(mockSetDisplayMode).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display error state when error occurs', () => {
|
|
|
|
|
const mockRefreshTracks = vi.fn();
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
error: new Error('Test error'),
|
|
|
|
|
filteredTracks: [],
|
|
|
|
|
isLoading: false,
|
|
|
|
|
refreshTracks: mockRefreshTracks,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
const errorMessages = screen.getAllByText('Erreur lors du chargement');
|
|
|
|
|
expect(errorMessages.length).toBeGreaterThan(0);
|
|
|
|
|
expect(screen.getByLabelText('Réessayer')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call refreshTracks when retry button is clicked', async () => {
|
|
|
|
|
const user = userEvent.setup();
|
|
|
|
|
const mockRefreshTracks = vi.fn();
|
|
|
|
|
mockUseTrackList.mockReturnValue({
|
|
|
|
|
...defaultTrackListReturn,
|
|
|
|
|
error: new Error('Test error'),
|
|
|
|
|
filteredTracks: [],
|
|
|
|
|
isLoading: false,
|
|
|
|
|
refreshTracks: mockRefreshTracks,
|
|
|
|
|
});
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
render(<TrackListContainer />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
const retryButton = screen.getByLabelText('Réessayer');
|
|
|
|
|
await user.click(retryButton);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(mockRefreshTracks).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle track selection when showSelection is true', () => {
|
|
|
|
|
render(<TrackListContainer showSelection={true} />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// La sélection sera gérée par TrackList
|
|
|
|
|
expect(mockUseTrackList).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should pass available genres to filters', () => {
|
|
|
|
|
const availableGenres = ['Rock', 'Pop', 'Jazz'];
|
|
|
|
|
render(
|
|
|
|
|
<TrackListContainer
|
|
|
|
|
showFilters={true}
|
|
|
|
|
availableGenres={availableGenres}
|
2025-12-13 02:34:34 +00:00
|
|
|
/>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Les genres disponibles seront passés à TrackFilters
|
|
|
|
|
expect(mockUseTrackList).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should pass available artists to filters', () => {
|
|
|
|
|
const availableArtists = ['Artist 1', 'Artist 2'];
|
|
|
|
|
render(
|
|
|
|
|
<TrackListContainer
|
|
|
|
|
showFilters={true}
|
|
|
|
|
availableArtists={availableArtists}
|
2025-12-13 02:34:34 +00:00
|
|
|
/>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Les artistes disponibles seront passés à TrackFilters
|
|
|
|
|
expect(mockUseTrackList).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should apply custom className', () => {
|
2025-12-13 02:34:34 +00:00
|
|
|
const { container } = render(
|
|
|
|
|
<TrackListContainer className="custom-class" />,
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(container.firstChild).toHaveClass('custom-class');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should use useTrackList with correct options', () => {
|
|
|
|
|
render(
|
|
|
|
|
<TrackListContainer
|
|
|
|
|
useService={false}
|
|
|
|
|
autoLoad={false}
|
|
|
|
|
persistFilters={true}
|
|
|
|
|
persistSort={true}
|
|
|
|
|
syncUrlParams={true}
|
|
|
|
|
storageKeyPrefix="customPrefix"
|
2025-12-13 02:34:34 +00:00
|
|
|
/>,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(mockUseTrackList).toHaveBeenCalledWith(
|
|
|
|
|
expect.objectContaining({
|
|
|
|
|
useService: false,
|
|
|
|
|
autoLoad: false,
|
|
|
|
|
persistFilters: true,
|
|
|
|
|
persistSort: true,
|
|
|
|
|
syncUrlParams: true,
|
|
|
|
|
storageKeyPrefix: 'customPrefix',
|
2025-12-13 02:34:34 +00:00
|
|
|
}),
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|