import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor, fireEvent } from '@testing-library/react'; import { TrackHistory } from './TrackHistory'; import { getTrackHistory, TrackHistoryError, TrackHistoryItem, } from '../services/trackHistoryService'; import { useToast } from '@/hooks/useToast'; // Mock services vi.mock('../services/trackHistoryService'); vi.mock('@/hooks/useToast'); describe('TrackHistory', () => { const mockToast = { success: vi.fn(), error: vi.fn(), }; beforeEach(() => { vi.clearAllMocks(); vi.mocked(useToast).mockReturnValue(mockToast); }); const mockHistoryItems: TrackHistoryItem[] = [ { id: 1, track_id: 123, user_id: 1, action: 'created', old_value: undefined, new_value: '{"title": "Test Track"}', created_at: '2024-01-01T00:00:00Z', }, { id: 2, track_id: 123, user_id: 1, action: 'updated', old_value: '{"title": "Test Track"}', new_value: '{"title": "Updated Track"}', created_at: '2024-01-02T00:00:00Z', }, { id: 3, track_id: 123, user_id: 1, action: 'published', old_value: undefined, new_value: undefined, created_at: '2024-01-03T00:00:00Z', }, ]; it('should render loading state initially', async () => { vi.mocked(getTrackHistory).mockImplementation( () => new Promise((resolve) => setTimeout( () => resolve({ history: mockHistoryItems, total: 3, limit: 50, offset: 0, }), 100, ), ), ); render(); expect(screen.getByRole('status')).toBeInTheDocument(); await waitFor(() => { expect(getTrackHistory).toHaveBeenCalledWith(123, { limit: 50, offset: 0, }); }); }); it('should display history timeline', async () => { vi.mocked(getTrackHistory).mockResolvedValue({ history: mockHistoryItems, total: 3, limit: 50, offset: 0, }); render(); await waitFor(() => { expect( screen.getByText('Historique des modifications'), ).toBeInTheDocument(); }); expect(screen.getByText('Créé')).toBeInTheDocument(); expect(screen.getByText('Modifié')).toBeInTheDocument(); expect(screen.getByText('Publié')).toBeInTheDocument(); }); it('should display empty state when no history', async () => { vi.mocked(getTrackHistory).mockResolvedValue({ history: [], total: 0, limit: 50, offset: 0, }); render(); await waitFor(() => { expect( screen.getByText('Aucune modification enregistrée'), ).toBeInTheDocument(); }); }); it('should display error message on load error', async () => { const error = new TrackHistoryError('Failed to load', 'SERVER', true); vi.mocked(getTrackHistory).mockRejectedValue(error); render(); await waitFor(() => { expect(screen.getByText('Failed to load')).toBeInTheDocument(); }); }); it('should display old and new values when available', async () => { vi.mocked(getTrackHistory).mockResolvedValue({ history: [mockHistoryItems[1]], // Updated action with values total: 1, limit: 50, offset: 0, }); render(); await waitFor(() => { expect(screen.getByText('Ancienne valeur:')).toBeInTheDocument(); expect(screen.getByText('Nouvelle valeur:')).toBeInTheDocument(); }); }); it('should handle pagination', async () => { const largeHistory: TrackHistoryItem[] = Array.from( { length: 10 }, (_, i) => ({ id: i + 1, track_id: 123, user_id: 1, action: 'updated' as const, created_at: `2024-01-0${i + 1}T00:00:00Z`, }), ); vi.mocked(getTrackHistory).mockResolvedValue({ history: largeHistory.slice(0, 5), total: 10, limit: 5, offset: 0, }); render(); await waitFor(() => { expect(screen.getByText(/Affichage 1 - 5 sur 10/)).toBeInTheDocument(); }); const nextButton = screen.getByText('Suivant'); expect(nextButton).toBeInTheDocument(); expect(nextButton).not.toBeDisabled(); // Mock next page response vi.mocked(getTrackHistory).mockResolvedValue({ history: largeHistory.slice(5, 10), total: 10, limit: 5, offset: 5, }); fireEvent.click(nextButton); await waitFor(() => { expect(getTrackHistory).toHaveBeenCalledWith(123, { limit: 5, offset: 5, }); }); }); it('should disable previous button on first page', async () => { vi.mocked(getTrackHistory).mockResolvedValue({ history: mockHistoryItems.slice(0, 2), total: 10, limit: 2, offset: 0, }); render(); await waitFor(() => { const previousButton = screen.getByText('Précédent'); expect(previousButton).toBeDisabled(); }); }); it('should disable next button on last page', async () => { vi.mocked(getTrackHistory).mockResolvedValue({ history: mockHistoryItems.slice(0, 2), total: 2, limit: 2, offset: 0, }); render(); await waitFor(() => { const nextButton = screen.getByText('Suivant'); expect(nextButton).toBeDisabled(); }); }); it('should display all action types correctly', async () => { const allActions: TrackHistoryItem[] = [ { id: 1, track_id: 123, user_id: 1, action: 'created', created_at: '2024-01-01T00:00:00Z', }, { id: 2, track_id: 123, user_id: 1, action: 'updated', created_at: '2024-01-02T00:00:00Z', }, { id: 3, track_id: 123, user_id: 1, action: 'deleted', created_at: '2024-01-03T00:00:00Z', }, { id: 4, track_id: 123, user_id: 1, action: 'published', created_at: '2024-01-04T00:00:00Z', }, { id: 5, track_id: 123, user_id: 1, action: 'unpublished', created_at: '2024-01-05T00:00:00Z', }, { id: 6, track_id: 123, user_id: 1, action: 'restored', created_at: '2024-01-06T00:00:00Z', }, ]; vi.mocked(getTrackHistory).mockResolvedValue({ history: allActions, total: 6, limit: 50, offset: 0, }); render(); await waitFor(() => { expect(screen.getByText('Créé')).toBeInTheDocument(); expect(screen.getByText('Modifié')).toBeInTheDocument(); expect(screen.getByText('Supprimé')).toBeInTheDocument(); expect(screen.getByText('Publié')).toBeInTheDocument(); expect(screen.getByText('Dépublié')).toBeInTheDocument(); expect(screen.getByText('Restauré')).toBeInTheDocument(); }); }); it('should format dates correctly', async () => { vi.mocked(getTrackHistory).mockResolvedValue({ history: [mockHistoryItems[0]], total: 1, limit: 50, offset: 0, }); render(); await waitFor(() => { // Check that date is formatted (should contain month name in French) const dateElement = screen.getByText(/janvier/i); expect(dateElement).toBeInTheDocument(); }); }); });