veza/apps/web/src/features/tracks/components/TrackStatsDisplay.test.tsx
2026-01-07 19:39:21 +01:00

230 lines
6.5 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { TrackStatsDisplay } from './TrackStatsDisplay';
import { getTrackStats } from '../services/analyticsService';
import { TrackServiceError as TrackStatsError } from '../errors/trackErrors';
// Mock dependencies
vi.mock('../services/trackStatsService');
vi.mock('@/components/ui/loading-spinner', () => ({
LoadingSpinner: () => <div data-testid="loading-spinner">Loading...</div>,
}));
vi.mock('@/components/ui/alert', () => ({
Alert: ({ children, className }: any) => (
<div className={className}>{children}</div>
),
AlertDescription: ({ children }: any) => <div>{children}</div>,
}));
describe('TrackStatsDisplay', () => {
const mockStats = {
views: 1000,
likes: 250,
comments: 50,
total_play_time: 3665, // 1h 1m 5s
downloads: 25,
};
beforeEach(() => {
vi.clearAllMocks();
});
it('should render loading state initially', () => {
vi.mocked(getTrackStats).mockImplementation(
() => new Promise(() => { }), // Never resolves
);
render(<TrackStatsDisplay trackId={123} />);
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
});
it('should display stats in horizontal layout', async () => {
vi.mocked(getTrackStats).mockResolvedValue(mockStats);
render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(screen.getByText('1.0K')).toBeInTheDocument(); // views
expect(screen.getByText('250')).toBeInTheDocument(); // likes
expect(screen.getByText('50')).toBeInTheDocument(); // comments
expect(screen.getByText('25')).toBeInTheDocument(); // downloads
expect(screen.getByText('1h 1m')).toBeInTheDocument(); // total_play_time
});
// Check icons are present (Eye, Heart, MessageCircle, Download, Clock)
const icons = screen.getAllByRole('img', { hidden: true });
expect(icons.length).toBeGreaterThan(0);
});
it('should display stats in vertical layout', async () => {
vi.mocked(getTrackStats).mockResolvedValue(mockStats);
render(<TrackStatsDisplay trackId={123} variant="vertical" />);
await waitFor(() => {
expect(screen.getByText('1.0K')).toBeInTheDocument();
});
});
it('should show labels when showLabels is true', async () => {
vi.mocked(getTrackStats).mockResolvedValue(mockStats);
render(<TrackStatsDisplay trackId={123} showLabels={true} />);
await waitFor(() => {
expect(screen.getByText('Vues')).toBeInTheDocument();
expect(screen.getByText('Likes')).toBeInTheDocument();
expect(screen.getByText('Commentaires')).toBeInTheDocument();
expect(screen.getByText('Téléchargements')).toBeInTheDocument();
expect(screen.getByText("Temps d'écoute")).toBeInTheDocument();
});
});
it('should format large numbers correctly', async () => {
const largeStats = {
views: 1500000,
likes: 2500,
comments: 500,
total_play_time: 7200,
downloads: 100,
};
vi.mocked(getTrackStats).mockResolvedValue(largeStats);
render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(screen.getByText('1.5M')).toBeInTheDocument(); // views
expect(screen.getByText('2.5K')).toBeInTheDocument(); // likes
});
});
it('should format duration correctly', async () => {
const statsWithDuration = {
views: 100,
likes: 10,
comments: 5,
total_play_time: 3665, // 1h 1m 5s
downloads: 2,
};
vi.mocked(getTrackStats).mockResolvedValue(statsWithDuration);
render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(screen.getByText('1h 1m')).toBeInTheDocument();
});
});
it('should format duration with only minutes', async () => {
const statsWithMinutes = {
views: 100,
likes: 10,
comments: 5,
total_play_time: 125, // 2m 5s
downloads: 2,
};
vi.mocked(getTrackStats).mockResolvedValue(statsWithMinutes);
render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(screen.getByText('2m 5s')).toBeInTheDocument();
});
});
it('should format duration with only seconds', async () => {
const statsWithSeconds = {
views: 100,
likes: 10,
comments: 5,
total_play_time: 45, // 45s
downloads: 2,
};
vi.mocked(getTrackStats).mockResolvedValue(statsWithSeconds);
render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(screen.getByText('45s')).toBeInTheDocument();
});
});
it('should display error message on failure', async () => {
const error = new TrackStatsError('Track introuvable', 'NOT_FOUND', false);
vi.mocked(getTrackStats).mockRejectedValue(error);
render(<TrackStatsDisplay trackId={999} />);
await waitFor(() => {
expect(screen.getByText('Track introuvable')).toBeInTheDocument();
});
});
it('should display generic error message on unknown error', async () => {
vi.mocked(getTrackStats).mockRejectedValue(new Error('Unknown error'));
render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(
screen.getByText('Impossible de charger les statistiques'),
).toBeInTheDocument();
});
});
it('should reload stats when trackId changes', async () => {
vi.mocked(getTrackStats).mockResolvedValue(mockStats);
const { rerender } = render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(getTrackStats).toHaveBeenCalledWith(123);
});
vi.mocked(getTrackStats).mockResolvedValue({
...mockStats,
views: 2000,
});
rerender(<TrackStatsDisplay trackId={456} />);
await waitFor(() => {
expect(getTrackStats).toHaveBeenCalledWith(456);
});
});
it('should apply custom className', async () => {
vi.mocked(getTrackStats).mockResolvedValue(mockStats);
render(<TrackStatsDisplay trackId={123} className="custom-class" />);
await waitFor(() => {
const container = screen.getByText('1.0K').closest('div');
expect(container?.className).toContain('custom-class');
});
});
it('should handle zero values', async () => {
const zeroStats = {
views: 0,
likes: 0,
comments: 0,
total_play_time: 0,
downloads: 0,
};
vi.mocked(getTrackStats).mockResolvedValue(zeroStats);
render(<TrackStatsDisplay trackId={123} />);
await waitFor(() => {
expect(screen.getByText('0')).toBeInTheDocument();
expect(screen.getByText('0s')).toBeInTheDocument();
});
});
});