271 lines
7.9 KiB
TypeScript
271 lines
7.9 KiB
TypeScript
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 { TrackDetailPage } from './TrackDetailPage';
|
|
import { getTrack } from '../services/trackService';
|
|
import { TrackServiceError as TrackUploadError } from '../errors/trackErrors';
|
|
import { usePlayerStore } from '@/stores/player';
|
|
import { useToast } from '@/hooks/useToast';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import type { Track } from '../types/track';
|
|
|
|
// Mock dependencies
|
|
vi.mock('../services/trackService');
|
|
vi.mock('@/stores/player');
|
|
vi.mock('@/hooks/useToast');
|
|
vi.mock('react-router-dom', async () => {
|
|
const actual = await vi.importActual('react-router-dom');
|
|
return {
|
|
...actual,
|
|
useParams: vi.fn(),
|
|
useNavigate: vi.fn(),
|
|
};
|
|
});
|
|
|
|
describe('TrackDetailPage', () => {
|
|
const mockNavigate = vi.fn();
|
|
const mockToast = {
|
|
success: vi.fn(),
|
|
error: vi.fn(),
|
|
warning: vi.fn(),
|
|
info: vi.fn(),
|
|
toast: vi.fn(),
|
|
};
|
|
|
|
const mockTrack: Track = {
|
|
id: 1,
|
|
creator_id: 123,
|
|
title: 'Test Track',
|
|
artist: 'Test Artist',
|
|
album: 'Test Album',
|
|
duration: 180,
|
|
genre: 'Rock',
|
|
year: 2024,
|
|
file_path: '/uploads/track.mp3',
|
|
file_size: 1024,
|
|
format: 'MP3',
|
|
bitrate: 320,
|
|
sample_rate: 44100,
|
|
waveform_path: '/waveforms/track.png',
|
|
cover_art_path: '/covers/track.jpg',
|
|
is_public: true,
|
|
play_count: 100,
|
|
like_count: 50,
|
|
created_at: '2024-01-01T00:00:00Z',
|
|
updated_at: '2024-01-01T00:00:00Z',
|
|
};
|
|
|
|
const mockPlayerStore = {
|
|
play: vi.fn(),
|
|
pause: vi.fn(),
|
|
currentTrack: null,
|
|
isPlaying: false,
|
|
addToQueue: vi.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(useParams).mockReturnValue({ id: '1' });
|
|
vi.mocked(useNavigate).mockReturnValue(mockNavigate);
|
|
vi.mocked(useToast).mockReturnValue(mockToast);
|
|
vi.mocked(usePlayerStore).mockReturnValue(mockPlayerStore as any);
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('should render loading state initially', async () => {
|
|
vi.mocked(getTrack).mockImplementation(
|
|
() =>
|
|
new Promise((resolve) => {
|
|
setTimeout(() => resolve(mockTrack), 100);
|
|
}),
|
|
);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
expect(screen.getByText(/chargement du track/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render track details after loading', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Track')).toBeInTheDocument();
|
|
expect(screen.getByText('Test Artist')).toBeInTheDocument();
|
|
expect(screen.getByText('Test Album')).toBeInTheDocument();
|
|
});
|
|
|
|
expect(screen.getByText('3:00')).toBeInTheDocument(); // 180 seconds
|
|
expect(screen.getByText('Rock')).toBeInTheDocument();
|
|
expect(screen.getByText('2024')).toBeInTheDocument();
|
|
expect(screen.getByText('100')).toBeInTheDocument(); // play_count
|
|
expect(screen.getByText('50')).toBeInTheDocument(); // like_count
|
|
});
|
|
|
|
it('should display error message on load failure', async () => {
|
|
const error = new TrackUploadError('Track not found', 'VALIDATION', false);
|
|
vi.mocked(getTrack).mockRejectedValue(error);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/track introuvable/i)).toBeInTheDocument();
|
|
expect(mockToast.error).toHaveBeenCalledWith('Track not found');
|
|
});
|
|
});
|
|
|
|
it('should handle missing track ID', async () => {
|
|
vi.mocked(useParams).mockReturnValue({ id: undefined });
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/track id is required/i)).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should play track when play button is clicked', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Track')).toBeInTheDocument();
|
|
});
|
|
|
|
const playButton = screen.getByRole('button', { name: /play/i });
|
|
await userEvent.click(playButton);
|
|
|
|
expect(mockPlayerStore.play).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should pause track when pause button is clicked if track is playing', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
vi.mocked(usePlayerStore).mockReturnValue({
|
|
...mockPlayerStore,
|
|
currentTrack: { id: '1' } as any,
|
|
isPlaying: true,
|
|
} as any);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Track')).toBeInTheDocument();
|
|
});
|
|
|
|
const pauseButton = screen.getByRole('button', { name: /pause/i });
|
|
await userEvent.click(pauseButton);
|
|
|
|
expect(mockPlayerStore.pause).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should add track to queue when queue button is clicked', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Track')).toBeInTheDocument();
|
|
});
|
|
|
|
const queueButtons = screen.getAllByRole('button');
|
|
const queueButton = queueButtons.find((btn) =>
|
|
btn.getAttribute('title')?.includes("file d'attente"),
|
|
);
|
|
|
|
if (queueButton) {
|
|
await userEvent.click(queueButton);
|
|
expect(mockPlayerStore.addToQueue).toHaveBeenCalled();
|
|
expect(mockToast.success).toHaveBeenCalledWith(
|
|
expect.stringContaining('ajouté à la file'),
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should copy share link when share button is clicked', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
const mockWriteText = vi.fn().mockResolvedValue(undefined);
|
|
Object.assign(navigator, {
|
|
clipboard: {
|
|
writeText: mockWriteText,
|
|
},
|
|
});
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Track')).toBeInTheDocument();
|
|
});
|
|
|
|
const shareButtons = screen.getAllByRole('button');
|
|
const shareButton = shareButtons.find((btn) =>
|
|
btn.getAttribute('title')?.includes('Partager'),
|
|
);
|
|
|
|
if (shareButton) {
|
|
await userEvent.click(shareButton);
|
|
expect(mockWriteText).toHaveBeenCalled();
|
|
expect(mockToast.success).toHaveBeenCalledWith(
|
|
expect.stringContaining('copié'),
|
|
);
|
|
}
|
|
});
|
|
|
|
it('should navigate back when back button is clicked', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Track')).toBeInTheDocument();
|
|
});
|
|
|
|
const backButton = screen.getByRole('button', { name: /retour/i });
|
|
await userEvent.click(backButton);
|
|
|
|
expect(mockNavigate).toHaveBeenCalledWith(-1);
|
|
});
|
|
|
|
it('should display waveform if available', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
const waveform = screen.getByAltText('Waveform');
|
|
expect(waveform).toBeInTheDocument();
|
|
expect(waveform).toHaveAttribute('src', '/waveforms/track.png');
|
|
});
|
|
});
|
|
|
|
it('should display cover art if available', async () => {
|
|
vi.mocked(getTrack).mockResolvedValue(mockTrack);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
const coverArt = screen.getByAltText('Test Track');
|
|
expect(coverArt).toBeInTheDocument();
|
|
expect(coverArt).toHaveAttribute('src', '/covers/track.jpg');
|
|
});
|
|
});
|
|
|
|
it('should display placeholder if no cover art', async () => {
|
|
const trackWithoutCover = { ...mockTrack, cover_art_path: undefined };
|
|
vi.mocked(getTrack).mockResolvedValue(trackWithoutCover);
|
|
|
|
render(<TrackDetailPage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test Track')).toBeInTheDocument();
|
|
});
|
|
|
|
// Should have Music icon placeholder
|
|
const musicIcons = screen.getAllByRole('img', { hidden: true });
|
|
expect(musicIcons.length).toBeGreaterThan(0);
|
|
});
|
|
});
|