veza/apps/web/src/components/ui/avatar-upload.test.tsx
senke c4111ac059 refactor(ui): extract AvatarUpload into avatar-upload module
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 22:21:02 +01:00

147 lines
3.8 KiB
TypeScript

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AvatarUpload } from './avatar-upload';
// Mock useToast
const mockShowSuccess = vi.fn();
const mockShowError = vi.fn();
vi.mock('@/hooks/useToast', () => ({
useToast: () => ({
success: mockShowSuccess,
error: mockShowError,
}),
}));
// Mock avatarService
vi.mock('@/features/profile/services/avatarService', () => ({
uploadAvatar: vi.fn().mockResolvedValue({ avatar_url: 'new-avatar.jpg' }),
deleteAvatar: vi.fn().mockResolvedValue({}),
}));
describe('AvatarUpload Component', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('renders avatar upload component', () => {
render(<AvatarUpload userId="123" />);
expect(screen.getByRole('button', { name: /Cliquez pour uploader/i })).toBeInTheDocument();
});
it('renders current avatar when provided', () => {
render(<AvatarUpload userId="123" currentAvatarUrl="avatar.jpg" />);
const image = screen.getByAltText('Avatar preview');
expect(image).toBeInTheDocument();
expect(image).toHaveAttribute('src', 'avatar.jpg');
});
it('shows upload button when no avatar', () => {
render(<AvatarUpload userId="123" />);
expect(screen.getByText(/Cliquez pour uploader/i)).toBeInTheDocument();
});
it('shows change button when avatar exists', () => {
render(<AvatarUpload userId="123" currentAvatarUrl="avatar.jpg" />);
expect(screen.getByText(/Changer/i)).toBeInTheDocument();
});
it('handles file selection', async () => {
const mockOnAvatarUpdated = vi.fn();
const file = new File(['test'], 'test.jpg', { type: 'image/jpeg' });
render(<AvatarUpload userId="123" onAvatarUpdated={mockOnAvatarUpdated} />);
const input = document.querySelector('input[type="file"]');
expect(input).toBeInTheDocument();
if (input) {
Object.defineProperty(input, 'files', {
value: [file],
writable: false,
});
fireEvent.change(input);
await waitFor(
() => {
expect(mockOnAvatarUpdated).toHaveBeenCalled();
},
{ timeout: 2000 },
);
}
});
it('validates file type', async () => {
const file = new File(['test'], 'test.txt', { type: 'text/plain' });
render(<AvatarUpload userId="123" />);
const input = document.querySelector('input[type="file"]');
if (input) {
Object.defineProperty(input, 'files', {
value: [file],
writable: false,
});
fireEvent.change(input);
await waitFor(() => {
expect(mockShowError).toHaveBeenCalled();
});
}
});
it('validates file size', async () => {
const largeFile = new File(['x'.repeat(10 * 1024 * 1024)], 'large.jpg', {
type: 'image/jpeg',
});
render(<AvatarUpload userId="123" maxSize={5 * 1024 * 1024} />);
const input = document.querySelector('input[type="file"]');
if (input) {
Object.defineProperty(input, 'files', {
value: [largeFile],
writable: false,
});
fireEvent.change(input);
await waitFor(() => {
expect(mockShowError).toHaveBeenCalled();
});
}
});
it('handles delete action', async () => {
const mockOnAvatarDeleted = vi.fn();
render(
<AvatarUpload
userId="123"
currentAvatarUrl="avatar.jpg"
onAvatarDeleted={mockOnAvatarDeleted}
/>,
);
const deleteButton = screen.getByText(/Supprimer/i);
fireEvent.click(deleteButton);
await waitFor(() => {
expect(mockOnAvatarDeleted).toHaveBeenCalled();
});
});
it('disables when disabled prop is true', () => {
render(<AvatarUpload userId="123" disabled={true} />);
const button = screen.getByText(/Cliquez pour uploader/i);
expect(button).toBeDisabled();
});
});