2025-12-03 21:56:50 +00:00
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
|
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
|
|
|
import userEvent from '@testing-library/user-event';
|
|
|
|
|
import { TrackUpload } from './TrackUpload';
|
2025-12-13 02:34:34 +00:00
|
|
|
import {
|
|
|
|
|
uploadTrack,
|
|
|
|
|
getUploadProgress,
|
|
|
|
|
TrackUploadError,
|
|
|
|
|
} from '../services/trackService';
|
2025-12-03 21:56:50 +00:00
|
|
|
import { useToast } from '@/hooks/useToast';
|
|
|
|
|
|
|
|
|
|
// Mock dependencies
|
|
|
|
|
vi.mock('../services/trackService');
|
|
|
|
|
vi.mock('@/hooks/useToast');
|
|
|
|
|
vi.mock('../services/chunkedUploadService', () => ({
|
|
|
|
|
ChunkedUploadManager: vi.fn(),
|
|
|
|
|
calculateTotalChunks: vi.fn((size) => Math.ceil(size / (5 * 1024 * 1024))),
|
|
|
|
|
CHUNK_SIZE: 5 * 1024 * 1024,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
describe('TrackUpload', () => {
|
|
|
|
|
const mockToast = {
|
|
|
|
|
success: vi.fn(),
|
|
|
|
|
error: vi.fn(),
|
|
|
|
|
warning: vi.fn(),
|
|
|
|
|
info: vi.fn(),
|
|
|
|
|
toast: vi.fn(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks();
|
|
|
|
|
vi.mocked(useToast).mockReturnValue(mockToast);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
vi.restoreAllMocks();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should render upload area', () => {
|
|
|
|
|
render(<TrackUpload />);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
|
|
|
|
expect(
|
|
|
|
|
screen.getByText(/glissez-déposez un fichier audio/i),
|
|
|
|
|
).toBeInTheDocument();
|
2025-12-03 21:56:50 +00:00
|
|
|
expect(screen.getByText(/sélectionner un fichier/i)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should validate file format', async () => {
|
|
|
|
|
const user = userEvent.setup();
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
2025-12-03 21:56:50 +00:00
|
|
|
const invalidFile = new File(['test'], 'test.txt', { type: 'text/plain' });
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [invalidFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith(
|
2025-12-13 02:34:34 +00:00
|
|
|
expect.stringContaining('Format non supporté'),
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should validate file size', async () => {
|
|
|
|
|
const user = userEvent.setup();
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const largeFile = new File(['x'.repeat(101 * 1024 * 1024)], 'large.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [largeFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith(
|
2025-12-13 02:34:34 +00:00
|
|
|
expect.stringContaining('Fichier trop volumineux'),
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should upload valid file successfully', async () => {
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: 'Test Artist',
|
|
|
|
|
duration: 180,
|
|
|
|
|
file_path: '/uploads/tracks/test.mp3',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
|
|
|
|
|
vi.mocked(getUploadProgress).mockResolvedValue({
|
|
|
|
|
track_id: 1,
|
|
|
|
|
status: 'completed',
|
|
|
|
|
progress: 100,
|
|
|
|
|
message: 'Upload completed',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(uploadTrack).toHaveBeenCalledWith(validFile, expect.any(Function));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Wait for polling to complete
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(getUploadProgress).toHaveBeenCalled();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 3000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should show file preview after selection', async () => {
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue({
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading',
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByText('test.mp3')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display progress bar during upload', async () => {
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let progressCallback: ((progress: number) => void) | undefined;
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockImplementation(async (file, onProgress) => {
|
|
|
|
|
progressCallback = onProgress;
|
|
|
|
|
// Simulate progress
|
|
|
|
|
if (onProgress) {
|
|
|
|
|
onProgress(50);
|
|
|
|
|
}
|
|
|
|
|
return mockTrack;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle upload errors', async () => {
|
|
|
|
|
const errorMessage = 'Upload failed';
|
|
|
|
|
vi.mocked(uploadTrack).mockRejectedValue(new Error(errorMessage));
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith(errorMessage);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should call onUploadComplete callback when upload completes', async () => {
|
|
|
|
|
const onUploadComplete = vi.fn();
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'completed' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
|
|
|
|
|
vi.mocked(getUploadProgress).mockResolvedValue({
|
|
|
|
|
track_id: 1,
|
|
|
|
|
status: 'completed',
|
|
|
|
|
progress: 100,
|
|
|
|
|
message: 'Upload completed',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload onUploadComplete={onUploadComplete} />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(getUploadProgress).toHaveBeenCalled();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 3000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
// Wait for callback to be called
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(onUploadComplete).toHaveBeenCalled();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 5000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display upload speed and time remaining during upload', async () => {
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 10 * 1024 * 1024, // 10MB
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let progressCallback: ((progress: number) => void) | undefined;
|
|
|
|
|
vi.mocked(uploadTrack).mockImplementation(async (file, onProgress) => {
|
|
|
|
|
progressCallback = onProgress;
|
|
|
|
|
// Simulate progress
|
|
|
|
|
if (onProgress) {
|
|
|
|
|
setTimeout(() => onProgress(50), 100);
|
|
|
|
|
setTimeout(() => onProgress(100), 200);
|
|
|
|
|
}
|
|
|
|
|
return mockTrack;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
vi.mocked(getUploadProgress).mockResolvedValue({
|
|
|
|
|
track_id: 1,
|
|
|
|
|
status: 'completed',
|
|
|
|
|
progress: 100,
|
|
|
|
|
message: 'Upload completed',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
Object.defineProperty(validFile, 'size', { value: 10 * 1024 * 1024 }); // 10MB
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(uploadTrack).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Wait for progress updates
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
// Check that progress bar is displayed
|
|
|
|
|
expect(screen.getByRole('progressbar')).toBeInTheDocument();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 500 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display current step (uploading/processing)', async () => {
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'processing' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
|
|
|
|
|
vi.mocked(getUploadProgress).mockResolvedValue({
|
|
|
|
|
track_id: 1,
|
|
|
|
|
status: 'processing',
|
|
|
|
|
progress: 100,
|
|
|
|
|
message: 'Processing track',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(getUploadProgress).toHaveBeenCalled();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 3000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle drag and drop', async () => {
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const dropZone = screen
|
|
|
|
|
.getByText(/glissez-déposez un fichier audio/i)
|
|
|
|
|
.closest('div');
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
fireEvent.dragOver(dropZone!, {
|
|
|
|
|
dataTransfer: {
|
|
|
|
|
files: [validFile],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.drop(dropZone!, {
|
|
|
|
|
dataTransfer: {
|
|
|
|
|
files: [validFile],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(uploadTrack).toHaveBeenCalled();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should validate empty file', async () => {
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
2025-12-03 21:56:50 +00:00
|
|
|
const emptyFile = new File([], 'empty.mp3', { type: 'audio/mpeg' });
|
|
|
|
|
Object.defineProperty(emptyFile, 'size', { value: 0 });
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [emptyFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith(
|
2025-12-13 02:34:34 +00:00
|
|
|
expect.stringContaining('Le fichier est vide'),
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle TrackUploadError with retry', async () => {
|
2025-12-13 02:34:34 +00:00
|
|
|
const networkError = new TrackUploadError('Erreur réseau', 'NETWORK', true);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
// First attempt fails, second succeeds
|
|
|
|
|
vi.mocked(uploadTrack)
|
|
|
|
|
.mockRejectedValueOnce(networkError)
|
|
|
|
|
.mockResolvedValueOnce({
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
vi.mocked(getUploadProgress).mockResolvedValue({
|
|
|
|
|
track_id: 1,
|
|
|
|
|
status: 'completed',
|
|
|
|
|
progress: 100,
|
|
|
|
|
message: 'Upload completed',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
// Wait for retry
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(mockToast.warning).toHaveBeenCalled();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 3000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
// Eventually succeeds
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(uploadTrack).toHaveBeenCalledTimes(2);
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 5000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle TrackUploadError without retry (validation error)', async () => {
|
|
|
|
|
const validationError = new TrackUploadError(
|
|
|
|
|
'Format invalide',
|
|
|
|
|
'VALIDATION',
|
2025-12-13 02:34:34 +00:00
|
|
|
false,
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockRejectedValue(validationError);
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(mockToast.error).toHaveBeenCalledWith(
|
2025-12-13 02:34:34 +00:00
|
|
|
expect.stringContaining('Format invalide'),
|
2025-12-03 21:56:50 +00:00
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Should not retry
|
|
|
|
|
expect(uploadTrack).toHaveBeenCalledTimes(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should display retry button after error', async () => {
|
|
|
|
|
const error = new TrackUploadError('Erreur upload', 'SERVER', false);
|
|
|
|
|
vi.mocked(uploadTrack).mockRejectedValue(error);
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByText(/réessayer/i)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should reset upload state', async () => {
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue({
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.getByText('test.mp3')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Find and click reset button
|
|
|
|
|
const resetButton = screen.getByRole('button', { name: /x/i });
|
|
|
|
|
fireEvent.click(resetButton);
|
|
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
|
|
|
expect(screen.queryByText('test.mp3')).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle polling errors gracefully', async () => {
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
|
|
|
|
|
vi.mocked(getUploadProgress).mockRejectedValue(new Error('Network error'));
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
|
|
|
|
// Should not crash even if polling fails
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(getUploadProgress).toHaveBeenCalled();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 3000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle failed status from polling', async () => {
|
|
|
|
|
const mockTrack = {
|
|
|
|
|
id: 1,
|
2025-12-22 21:00:50 +00:00
|
|
|
creator_id: 123,
|
2025-12-03 21:56:50 +00:00
|
|
|
title: 'test',
|
|
|
|
|
artist: '',
|
|
|
|
|
duration: 0,
|
|
|
|
|
file_path: '',
|
|
|
|
|
file_size: 1024,
|
|
|
|
|
format: 'MP3',
|
|
|
|
|
is_public: true,
|
|
|
|
|
play_count: 0,
|
|
|
|
|
like_count: 0,
|
|
|
|
|
status: 'uploading' as const,
|
|
|
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
vi.mocked(uploadTrack).mockResolvedValue(mockTrack);
|
|
|
|
|
vi.mocked(getUploadProgress).mockResolvedValue({
|
|
|
|
|
track_id: 1,
|
|
|
|
|
status: 'failed',
|
|
|
|
|
progress: 0,
|
|
|
|
|
message: 'Upload failed',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
render(<TrackUpload />);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
const fileInput = document.querySelector(
|
|
|
|
|
'input[type="file"]',
|
|
|
|
|
) as HTMLInputElement;
|
|
|
|
|
const validFile = new File(['test audio'], 'test.mp3', {
|
|
|
|
|
type: 'audio/mpeg',
|
|
|
|
|
});
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
Object.defineProperty(fileInput, 'files', {
|
|
|
|
|
value: [validFile],
|
|
|
|
|
writable: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fireEvent.change(fileInput);
|
|
|
|
|
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(getUploadProgress).toHaveBeenCalled();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 3000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
// Should stop polling and show error
|
2025-12-13 02:34:34 +00:00
|
|
|
await waitFor(
|
|
|
|
|
() => {
|
|
|
|
|
expect(screen.getByText(/upload failed/i)).toBeInTheDocument();
|
|
|
|
|
},
|
|
|
|
|
{ timeout: 5000 },
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
});
|
|
|
|
|
});
|