veza/apps/web/src/features/playlists/components/PlaylistBatchActions.test.tsx
2025-12-12 21:34:34 -05:00

191 lines
5 KiB
TypeScript

/**
* Tests pour PlaylistBatchActions
* T0506: Create Playlist Batch Operations
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { PlaylistBatchActions } from './PlaylistBatchActions';
import type { Playlist } from '../types';
// Mock hooks
vi.mock('../hooks/usePlaylist', () => ({
useDeletePlaylist: vi.fn(() => ({
mutateAsync: vi.fn().mockResolvedValue(undefined),
})),
useCreateShareLink: vi.fn(() => ({
mutateAsync: vi
.fn()
.mockResolvedValue({ share_link: 'https://example.com/share/123' }),
})),
}));
vi.mock('@/hooks/useToast', () => ({
useToast: vi.fn(() => ({
toast: vi.fn(),
})),
}));
// Mock clipboard API
Object.assign(navigator, {
clipboard: {
writeText: vi.fn().mockResolvedValue(undefined),
},
});
describe('PlaylistBatchActions', () => {
const mockPlaylists: Playlist[] = [
{
id: 1,
user_id: 1,
title: 'Playlist 1',
description: 'Description 1',
is_public: true,
track_count: 5,
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
},
{
id: 2,
user_id: 1,
title: 'Playlist 2',
description: 'Description 2',
is_public: false,
track_count: 10,
created_at: '2024-01-02T00:00:00Z',
updated_at: '2024-01-02T00:00:00Z',
},
];
const mockOnSelectionClear = vi.fn();
const mockOnPlaylistsDeleted = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it('should not render when no playlists are selected', () => {
const { container } = render(
<PlaylistBatchActions
selectedPlaylists={[]}
onSelectionClear={mockOnSelectionClear}
/>,
);
expect(container.firstChild).toBeNull();
});
it('should render when playlists are selected', () => {
render(
<PlaylistBatchActions
selectedPlaylists={[mockPlaylists[0]]}
onSelectionClear={mockOnSelectionClear}
/>,
);
expect(screen.getByText(/1 playlist sélectionnée/)).toBeInTheDocument();
});
it('should display correct count for multiple playlists', () => {
render(
<PlaylistBatchActions
selectedPlaylists={mockPlaylists}
onSelectionClear={mockOnSelectionClear}
/>,
);
expect(screen.getByText(/2 playlists sélectionnées/)).toBeInTheDocument();
});
it('should call onSelectionClear when clear button is clicked', async () => {
const user = userEvent.setup();
render(
<PlaylistBatchActions
selectedPlaylists={[mockPlaylists[0]]}
onSelectionClear={mockOnSelectionClear}
/>,
);
const clearButton = screen.getByLabelText(
'Désélectionner toutes les playlists',
);
await user.click(clearButton);
expect(mockOnSelectionClear).toHaveBeenCalledTimes(1);
});
it('should export playlists to JSON', async () => {
const user = userEvent.setup();
const createElementSpy = vi.spyOn(document, 'createElement');
const appendChildSpy = vi.spyOn(document.body, 'appendChild');
const removeChildSpy = vi.spyOn(document.body, 'removeChild');
render(
<PlaylistBatchActions
selectedPlaylists={[mockPlaylists[0]]}
onSelectionClear={mockOnSelectionClear}
/>,
);
const exportButton = screen.getByLabelText('Exporter en JSON');
await user.click(exportButton);
await waitFor(() => {
expect(createElementSpy).toHaveBeenCalledWith('a');
expect(appendChildSpy).toHaveBeenCalled();
expect(removeChildSpy).toHaveBeenCalled();
});
});
it('should export playlists to CSV', async () => {
const user = userEvent.setup();
const createElementSpy = vi.spyOn(document, 'createElement');
render(
<PlaylistBatchActions
selectedPlaylists={[mockPlaylists[0]]}
onSelectionClear={mockOnSelectionClear}
/>,
);
const exportButton = screen.getByLabelText('Exporter en CSV');
await user.click(exportButton);
await waitFor(() => {
expect(createElementSpy).toHaveBeenCalledWith('a');
});
});
it('should show delete confirmation dialog', async () => {
const user = userEvent.setup();
render(
<PlaylistBatchActions
selectedPlaylists={[mockPlaylists[0]]}
onSelectionClear={mockOnSelectionClear}
onPlaylistsDeleted={mockOnPlaylistsDeleted}
/>,
);
const deleteButton = screen.getByLabelText(
'Supprimer les playlists sélectionnées',
);
await user.click(deleteButton);
expect(screen.getByText(/Supprimer les playlists \?/)).toBeInTheDocument();
expect(screen.getByText(/1 playlist/)).toBeInTheDocument();
});
it('should have accessible labels', () => {
render(
<PlaylistBatchActions
selectedPlaylists={mockPlaylists}
onSelectionClear={mockOnSelectionClear}
/>,
);
expect(screen.getByRole('region')).toHaveAttribute(
'aria-label',
expect.stringContaining('2 playlists sélectionnées'),
);
});
});