import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import userEvent from '@testing-library/user-event';
import { FileUpload } from './file-upload';
describe('FileUpload Component', () => {
const mockOnFileSelect = vi.fn();
beforeEach(() => {
vi.clearAllMocks();
});
it('renders file upload component correctly', () => {
render();
expect(screen.getByText('Drag & drop files here, or click to select')).toBeInTheDocument();
expect(screen.getByText('Select Files')).toBeInTheDocument();
});
it('displays accepted file types', () => {
render();
expect(screen.getByText(/Accepted types:/)).toBeInTheDocument();
});
it('displays max file size', () => {
render();
expect(screen.getByText(/Max size: 5 MB/)).toBeInTheDocument();
});
it('displays multiple files allowed message', () => {
render();
expect(screen.getByText(/Multiple files allowed/)).toBeInTheDocument();
});
it('opens file dialog when button is clicked', async () => {
const user = userEvent.setup();
render();
const button = screen.getByText('Select Files');
await user.click(button);
// Vérifier que l'input file est présent (même s'il est caché)
const fileInput = document.querySelector('input[type="file"]');
expect(fileInput).toBeInTheDocument();
});
it('handles file selection via input', async () => {
const user = userEvent.setup();
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
expect(fileInput).toBeInTheDocument();
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
await user.upload(fileInput, file);
await waitFor(() => {
expect(mockOnFileSelect).toHaveBeenCalled();
});
const call = mockOnFileSelect.mock.calls[0][0];
expect(call).toHaveLength(1);
expect(call[0].name).toBe('test.txt');
});
it('handles multiple file selection', async () => {
const user = userEvent.setup();
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file1 = new File(['content1'], 'test1.txt', { type: 'text/plain' });
const file2 = new File(['content2'], 'test2.txt', { type: 'text/plain' });
await user.upload(fileInput, [file1, file2]);
await waitFor(() => {
expect(mockOnFileSelect).toHaveBeenCalled();
});
const call = mockOnFileSelect.mock.calls[0][0];
expect(call).toHaveLength(2);
});
it('handles drag and drop', async () => {
render();
const dropZone = screen.getByText('Drag & drop files here, or click to select').closest('.border-2');
expect(dropZone).toBeInTheDocument();
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
const dataTransfer = {
files: [file],
};
fireEvent.dragEnter(dropZone!, {
dataTransfer,
});
await waitFor(() => {
expect(dropZone).toHaveClass('border-primary');
});
fireEvent.drop(dropZone!, {
dataTransfer,
});
await waitFor(() => {
expect(mockOnFileSelect).toHaveBeenCalled();
});
});
it('validates file type and rejects invalid files', async () => {
const user = userEvent.setup();
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const invalidFile = new File(['content'], 'test.txt', { type: 'text/plain' });
await user.upload(fileInput, invalidFile);
await waitFor(() => {
const errorMessages = screen.queryAllByText(/File type.*is not allowed/);
expect(errorMessages.length).toBeGreaterThan(0);
});
// onFileSelect ne devrait pas être appelé pour les fichiers invalides
await waitFor(() => {
// Si des fichiers valides sont présents, onFileSelect est appelé, sinon non
// Dans ce cas, aucun fichier valide, donc onFileSelect peut ne pas être appelé
}, { timeout: 500 });
});
it('validates file size and rejects oversized files', async () => {
const user = userEvent.setup();
const maxSize = 1024; // 1KB
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
// Créer un fichier plus grand que maxSize
const largeContent = new Array(2048).fill('a').join('');
const oversizedFile = new File([largeContent], 'large.txt', { type: 'text/plain' });
await user.upload(fileInput, oversizedFile);
await waitFor(() => {
const errorMessages = screen.queryAllByText(/exceeds maximum size/);
expect(errorMessages.length).toBeGreaterThan(0);
});
});
it('shows file preview for images', async () => {
const user = userEvent.setup();
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
// Créer une image fictive
const imageBlob = new Blob(['image content'], { type: 'image/png' });
const imageFile = new File([imageBlob], 'test.png', { type: 'image/png' });
await user.upload(fileInput, imageFile);
await waitFor(() => {
expect(screen.getByText('test.png')).toBeInTheDocument();
}, { timeout: 1000 });
});
it('removes file from list when remove button is clicked', async () => {
const user = userEvent.setup();
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
await user.upload(fileInput, file);
await waitFor(() => {
expect(screen.getByText('test.txt')).toBeInTheDocument();
});
const removeButtons = document.querySelectorAll('button[type="button"]');
const removeButton = Array.from(removeButtons).find(btn => {
const svg = btn.querySelector('svg');
return svg && svg.getAttribute('d')?.includes('m18 6-6 6');
});
if (removeButton) {
await user.click(removeButton);
await waitFor(() => {
expect(screen.queryByText('test.txt')).not.toBeInTheDocument();
});
}
});
it('disables component when disabled prop is true', () => {
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
expect(fileInput).toBeDisabled();
const button = screen.getByText('Select Files');
expect(button).toBeDisabled();
});
it('does not show preview when showPreview is false', async () => {
const user = userEvent.setup();
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = new File(['content'], 'test.txt', { type: 'text/plain' });
await user.upload(fileInput, file);
await waitFor(() => {
expect(mockOnFileSelect).toHaveBeenCalled();
});
// La liste de preview ne devrait pas être affichée
expect(screen.queryByText('test.txt')).not.toBeInTheDocument();
});
it('replaces files when multiple is false', async () => {
const user = userEvent.setup();
render();
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file1 = new File(['content1'], 'test1.txt', { type: 'text/plain' });
const file2 = new File(['content2'], 'test2.txt', { type: 'text/plain' });
await user.upload(fileInput, file1);
await waitFor(() => {
expect(screen.getByText('test1.txt')).toBeInTheDocument();
});
await user.upload(fileInput, file2);
await waitFor(() => {
expect(screen.queryByText('test1.txt')).not.toBeInTheDocument();
expect(screen.getByText('test2.txt')).toBeInTheDocument();
});
});
});